♻️ refactor: standardize tool management and API responses
- Rename built_in_tools field to tools for consistency - Add type field to Tool schema (builtin/mcp/custom) - Consolidate tool handling in BaseService with listMcpTools method - Remove unused CreateSessionResponse and related schemas - Clean up unused imports and dead code in session handlers - Unify agent and session tool resolution logic
This commit is contained in:
@@ -365,11 +365,11 @@ export const updateAgent = async (req: Request, res: Response): Promise<Response
|
||||
* small_model:
|
||||
* type: string
|
||||
* description: Optional small/fast model ID
|
||||
* built_in_tools:
|
||||
* tools:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Built-in tool IDs
|
||||
* description: Tools
|
||||
* mcps:
|
||||
* type: array
|
||||
* items:
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { AgentModelValidationError, sessionMessageService, sessionService } from '@main/services/agents'
|
||||
import {
|
||||
CreateSessionResponse,
|
||||
ListAgentSessionsResponse,
|
||||
type ReplaceSessionRequest,
|
||||
UpdateSessionResponse
|
||||
} from '@types'
|
||||
import { ListAgentSessionsResponse, type ReplaceSessionRequest, UpdateSessionResponse } from '@types'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import type { ValidationRequest } from '../validators/zodValidator'
|
||||
@@ -28,9 +23,9 @@ export const createSession = async (req: Request, res: Response): Promise<Respon
|
||||
logger.info(`Creating new session for agent: ${agentId}`)
|
||||
logger.debug('Session data:', sessionData)
|
||||
|
||||
const session = (await sessionService.createSession(agentId, sessionData)) satisfies CreateSessionResponse
|
||||
const session = await sessionService.createSession(agentId, sessionData)
|
||||
|
||||
logger.info(`Session created successfully: ${session.id}`)
|
||||
logger.info(`Session created successfully: ${session?.id}`)
|
||||
return res.status(201).json(session)
|
||||
} catch (error: any) {
|
||||
if (error instanceof AgentModelValidationError) {
|
||||
@@ -332,35 +327,3 @@ export const listAllSessions = async (req: Request, res: Response): Promise<Resp
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const getSessionById = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
logger.info(`Getting session: ${sessionId}`)
|
||||
|
||||
const session = await sessionService.getSessionById(sessionId)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session retrieved successfully: ${sessionId}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get session',
|
||||
type: 'internal_error',
|
||||
code: 'session_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,41 +115,6 @@ class MCPApiService extends EventEmitter {
|
||||
|
||||
logger.info(`Server with id ${id} info:`, { tools: JSON.stringify(tools) })
|
||||
|
||||
// const [version, tools, prompts, resources] = await Promise.all([
|
||||
// () => {
|
||||
// try {
|
||||
// return client.getServerVersion()
|
||||
// } catch (error) {
|
||||
// logger.error(`Failed to get server version for id ${id}:`, { error: error })
|
||||
// return '1.0.0'
|
||||
// }
|
||||
// },
|
||||
// (() => {
|
||||
// try {
|
||||
// return client.listTools()
|
||||
// } catch (error) {
|
||||
// logger.error(`Failed to list tools for id ${id}:`, { error: error })
|
||||
// return []
|
||||
// }
|
||||
// })(),
|
||||
// (() => {
|
||||
// try {
|
||||
// return client.listPrompts()
|
||||
// } catch (error) {
|
||||
// logger.error(`Failed to list prompts for id ${id}:`, { error: error })
|
||||
// return []
|
||||
// }
|
||||
// })(),
|
||||
// (() => {
|
||||
// try {
|
||||
// return client.listResources()
|
||||
// } catch (error) {
|
||||
// logger.error(`Failed to list resources for id ${id}:`, { error: error })
|
||||
// return []
|
||||
// }
|
||||
// })()
|
||||
// ])
|
||||
|
||||
return {
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { type Client, createClient } from '@libsql/client'
|
||||
import { loggerService } from '@logger'
|
||||
import { mcpApiService } from '@main/apiServer/services/mcp'
|
||||
import { ModelValidationError, validateModelId } from '@main/apiServer/utils'
|
||||
import { AgentType, objectKeys, Provider } from '@types'
|
||||
import { AgentType, MCPTool, objectKeys, Provider, Tool } from '@types'
|
||||
import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
@@ -10,6 +11,7 @@ import { MigrationService } from './database/MigrationService'
|
||||
import * as schema from './database/schema'
|
||||
import { dbPath } from './drizzle.config'
|
||||
import { AgentModelField, AgentModelValidationError } from './errors'
|
||||
import { builtinTools } from './services/claudecode/tools'
|
||||
|
||||
const logger = loggerService.withContext('BaseService')
|
||||
|
||||
@@ -30,7 +32,7 @@ export abstract class BaseService {
|
||||
protected static db: LibSQLDatabase<typeof schema> | null = null
|
||||
protected static isInitialized = false
|
||||
protected static initializationPromise: Promise<void> | null = null
|
||||
protected jsonFields: string[] = ['built_in_tools', 'mcps', 'configuration', 'accessible_paths', 'allowed_tools']
|
||||
protected jsonFields: string[] = ['tools', 'mcps', 'configuration', 'accessible_paths', 'allowed_tools']
|
||||
|
||||
/**
|
||||
* Initialize database with retry logic and proper error handling
|
||||
@@ -49,6 +51,31 @@ export abstract class BaseService {
|
||||
return BaseService.initializationPromise
|
||||
}
|
||||
|
||||
public async listMcpTools(agentType: AgentType, ids?: string[]): Promise<Tool[]> {
|
||||
const tools: Tool[] = []
|
||||
if (agentType === 'claude-code') {
|
||||
tools.push(...builtinTools)
|
||||
}
|
||||
if (ids && ids.length > 0) {
|
||||
for (const id of ids) {
|
||||
const server = await mcpApiService.getServerInfo(id)
|
||||
if (server) {
|
||||
server.tools.forEach((tool: MCPTool) => {
|
||||
tools.push({
|
||||
id: `mcp_${id}_${tool.name}`,
|
||||
name: tool.name,
|
||||
type: 'mcp',
|
||||
description: tool.description || '',
|
||||
requirePermissions: true
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tools
|
||||
}
|
||||
|
||||
private static async performInitialization(): Promise<void> {
|
||||
const maxRetries = 3
|
||||
let lastError: Error
|
||||
|
||||
@@ -16,7 +16,6 @@ import { count, eq } from 'drizzle-orm'
|
||||
import { BaseService } from '../BaseService'
|
||||
import { type AgentRow, agentsTable, type InsertAgentRow } from '../database/schema'
|
||||
import { AgentModelField } from '../errors'
|
||||
import { builtinTools } from './claudecode/tools'
|
||||
|
||||
export class AgentService extends BaseService {
|
||||
private static instance: AgentService | null = null
|
||||
@@ -92,10 +91,7 @@ export class AgentService extends BaseService {
|
||||
}
|
||||
|
||||
const agent = this.deserializeJsonFields(result[0]) as GetAgentResponse
|
||||
if (agent.type === 'claude-code') {
|
||||
agent.built_in_tools = builtinTools
|
||||
}
|
||||
|
||||
agent.tools = await this.listMcpTools(agent.type, agent.mcps)
|
||||
return agent
|
||||
}
|
||||
|
||||
@@ -115,11 +111,9 @@ export class AgentService extends BaseService {
|
||||
|
||||
const agents = result.map((row) => this.deserializeJsonFields(row)) as GetAgentResponse[]
|
||||
|
||||
agents.forEach((agent) => {
|
||||
if (agent.type === 'claude-code') {
|
||||
agent.built_in_tools = builtinTools
|
||||
}
|
||||
})
|
||||
for (const agent of agents) {
|
||||
agent.tools = await this.listMcpTools(agent.type, agent.mcps)
|
||||
}
|
||||
|
||||
return { agents, total: totalResult[0].count }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
type AgentEntity,
|
||||
type AgentSessionEntity,
|
||||
type CreateSessionRequest,
|
||||
type CreateSessionResponse,
|
||||
type GetAgentSessionResponse,
|
||||
type ListOptions,
|
||||
type UpdateSessionRequest,
|
||||
@@ -30,7 +29,7 @@ export class SessionService extends BaseService {
|
||||
await BaseService.initialize()
|
||||
}
|
||||
|
||||
async createSession(agentId: string, req: CreateSessionRequest): Promise<CreateSessionResponse> {
|
||||
async createSession(agentId: string, req: CreateSessionRequest): Promise<GetAgentSessionResponse | null> {
|
||||
this.ensureInitialized()
|
||||
|
||||
// Validate agent exists - we'll need to import AgentService for this check
|
||||
@@ -89,7 +88,8 @@ export class SessionService extends BaseService {
|
||||
throw new Error('Failed to create session')
|
||||
}
|
||||
|
||||
return this.deserializeJsonFields(result[0]) as AgentSessionEntity
|
||||
const session = this.deserializeJsonFields(result[0])
|
||||
return await this.getSession(agentId, session.id)
|
||||
}
|
||||
|
||||
async getSession(agentId: string, id: string): Promise<GetAgentSessionResponse | null> {
|
||||
@@ -106,21 +106,7 @@ export class SessionService extends BaseService {
|
||||
}
|
||||
|
||||
const session = this.deserializeJsonFields(result[0]) as GetAgentSessionResponse
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
async getSessionById(id: string): Promise<GetAgentSessionResponse | null> {
|
||||
this.ensureInitialized()
|
||||
|
||||
const result = await this.database.select().from(sessionsTable).where(eq(sessionsTable.id, id)).limit(1)
|
||||
|
||||
if (!result[0]) {
|
||||
return null
|
||||
}
|
||||
|
||||
const session = this.deserializeJsonFields(result[0]) as GetAgentSessionResponse
|
||||
|
||||
session.tools = await this.listMcpTools(session.agent_type, session.mcps)
|
||||
return session
|
||||
}
|
||||
|
||||
|
||||
@@ -2,47 +2,83 @@ import { Tool } from '@types'
|
||||
|
||||
// https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude
|
||||
export const builtinTools: Tool[] = [
|
||||
{ id: 'Bash', name: 'Bash', description: 'Executes shell commands in your environment', requirePermissions: true },
|
||||
{ id: 'Edit', name: 'Edit', description: 'Makes targeted edits to specific files', requirePermissions: true },
|
||||
{ id: 'Glob', name: 'Glob', description: 'Finds files based on pattern matching', requirePermissions: false },
|
||||
{ id: 'Grep', name: 'Grep', description: 'Searches for patterns in file contents', requirePermissions: false },
|
||||
{
|
||||
id: 'Bash',
|
||||
name: 'Bash',
|
||||
description: 'Executes shell commands in your environment',
|
||||
requirePermissions: true,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'Edit',
|
||||
name: 'Edit',
|
||||
description: 'Makes targeted edits to specific files',
|
||||
requirePermissions: true,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'Glob',
|
||||
name: 'Glob',
|
||||
description: 'Finds files based on pattern matching',
|
||||
requirePermissions: false,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'Grep',
|
||||
name: 'Grep',
|
||||
description: 'Searches for patterns in file contents',
|
||||
requirePermissions: false,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'MultiEdit',
|
||||
name: 'MultiEdit',
|
||||
description: 'Performs multiple edits on a single file atomically',
|
||||
requirePermissions: true
|
||||
requirePermissions: true,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'NotebookEdit',
|
||||
name: 'NotebookEdit',
|
||||
description: 'Modifies Jupyter notebook cells',
|
||||
requirePermissions: true
|
||||
requirePermissions: true,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'NotebookRead',
|
||||
name: 'NotebookRead',
|
||||
description: 'Reads and displays Jupyter notebook contents',
|
||||
requirePermissions: false
|
||||
requirePermissions: false,
|
||||
type: 'builtin'
|
||||
},
|
||||
{ id: 'Read', name: 'Read', description: 'Reads the contents of files', requirePermissions: false },
|
||||
{ id: 'Read', name: 'Read', description: 'Reads the contents of files', requirePermissions: false, type: 'builtin' },
|
||||
{
|
||||
id: 'Task',
|
||||
name: 'Task',
|
||||
description: 'Runs a sub-agent to handle complex, multi-step tasks',
|
||||
requirePermissions: false
|
||||
requirePermissions: false,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'TodoWrite',
|
||||
name: 'TodoWrite',
|
||||
description: 'Creates and manages structured task lists',
|
||||
requirePermissions: false
|
||||
requirePermissions: false,
|
||||
type: 'builtin'
|
||||
},
|
||||
{
|
||||
id: 'WebFetch',
|
||||
name: 'WebFetch',
|
||||
description: 'Fetches content from a specified URL',
|
||||
requirePermissions: true,
|
||||
type: 'builtin'
|
||||
},
|
||||
{ id: 'WebFetch', name: 'WebFetch', description: 'Fetches content from a specified URL', requirePermissions: true },
|
||||
{
|
||||
id: 'WebSearch',
|
||||
name: 'WebSearch',
|
||||
description: 'Performs web searches with domain filtering',
|
||||
requirePermissions: true
|
||||
requirePermissions: true,
|
||||
type: 'builtin'
|
||||
},
|
||||
{ id: 'Write', name: 'Write', description: 'Creates or overwrites files', requirePermissions: true }
|
||||
{ id: 'Write', name: 'Write', description: 'Creates or overwrites files', requirePermissions: true, type: 'builtin' }
|
||||
]
|
||||
|
||||
@@ -11,11 +11,10 @@ import {
|
||||
CreateAgentResponseSchema,
|
||||
CreateSessionForm,
|
||||
CreateSessionRequest,
|
||||
CreateSessionResponse,
|
||||
CreateSessionResponseSchema,
|
||||
GetAgentResponse,
|
||||
GetAgentResponseSchema,
|
||||
GetAgentSessionResponse,
|
||||
GetAgentSessionResponseSchema,
|
||||
ListAgentSessionsResponse,
|
||||
ListAgentSessionsResponseSchema,
|
||||
type ListAgentsResponse,
|
||||
@@ -27,9 +26,7 @@ import {
|
||||
UpdateAgentResponse,
|
||||
UpdateAgentResponseSchema,
|
||||
UpdateSessionForm,
|
||||
UpdateSessionRequest,
|
||||
UpdateSessionResponse,
|
||||
UpdateSessionResponseSchema
|
||||
UpdateSessionRequest
|
||||
} from '@types'
|
||||
import axios, { Axios, AxiosRequestConfig, isAxiosError } from 'axios'
|
||||
import { ZodError } from 'zod'
|
||||
@@ -172,12 +169,12 @@ export class AgentApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
public async createSession(agentId: string, session: CreateSessionForm): Promise<CreateSessionResponse> {
|
||||
public async createSession(agentId: string, session: CreateSessionForm): Promise<GetAgentSessionResponse> {
|
||||
const url = this.getSessionPaths(agentId).base
|
||||
try {
|
||||
const payload = session satisfies CreateSessionRequest
|
||||
const response = await this.axios.post(url, payload)
|
||||
const data = CreateSessionResponseSchema.parse(response.data)
|
||||
const data = GetAgentSessionResponseSchema.parse(response.data)
|
||||
return data
|
||||
} catch (error) {
|
||||
throw processError(error, 'Failed to add session.')
|
||||
@@ -209,12 +206,12 @@ export class AgentApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
public async updateSession(agentId: string, session: UpdateSessionForm): Promise<UpdateSessionResponse> {
|
||||
public async updateSession(agentId: string, session: UpdateSessionForm): Promise<GetAgentSessionResponse> {
|
||||
const url = this.getSessionPaths(agentId).withId(session.id)
|
||||
try {
|
||||
const payload = session satisfies UpdateSessionRequest
|
||||
const response = await this.axios.patch(url, payload)
|
||||
const data = UpdateSessionResponseSchema.parse(response.data)
|
||||
const data = GetAgentSessionResponseSchema.parse(response.data)
|
||||
if (session.id !== data.id) {
|
||||
throw new Error('Session ID mismatch in response')
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export const isAgentType = (type: unknown): type is AgentType => {
|
||||
export const ToolSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
type: z.enum(['builtin', 'mcp', 'custom']),
|
||||
description: z.string().optional(),
|
||||
requirePermissions: z.boolean().optional()
|
||||
})
|
||||
@@ -201,7 +202,7 @@ export interface UpdateAgentRequest extends Partial<AgentBase> {}
|
||||
export type ReplaceAgentRequest = AgentBase
|
||||
|
||||
export const GetAgentResponseSchema = AgentEntitySchema.extend({
|
||||
built_in_tools: z.array(ToolSchema).optional() // Built-in tools available to the agent
|
||||
tools: z.array(ToolSchema).optional() // All tools available to the agent (including built-in and custom)
|
||||
})
|
||||
|
||||
export type GetAgentResponse = z.infer<typeof GetAgentResponseSchema>
|
||||
@@ -224,7 +225,7 @@ export type CreateSessionRequest = z.infer<typeof CreateSessionRequestSchema>
|
||||
export interface UpdateSessionRequest extends Partial<AgentBase> {}
|
||||
|
||||
export const GetAgentSessionResponseSchema = AgentSessionEntitySchema.extend({
|
||||
built_in_tools: z.array(ToolSchema).optional(), // Built-in tools available to the agent
|
||||
tools: z.array(ToolSchema).optional(), // All tools available to the session (including built-in and custom)
|
||||
messages: z.array(AgentSessionMessageEntitySchema).optional() // Messages in the session
|
||||
})
|
||||
|
||||
@@ -241,12 +242,6 @@ export type ListAgentSessionsResponse = z.infer<typeof ListAgentSessionsResponse
|
||||
|
||||
export type CreateSessionMessageRequest = z.infer<typeof CreateSessionMessageRequestSchema>
|
||||
|
||||
export const CreateSessionResponseSchema = AgentSessionEntitySchema
|
||||
|
||||
export type CreateSessionResponse = AgentSessionEntity
|
||||
|
||||
export const UpdateSessionResponseSchema = GetAgentSessionResponseSchema
|
||||
|
||||
export type UpdateSessionResponse = GetAgentSessionResponse
|
||||
|
||||
export const AgentServerErrorSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user