feat(agents): enhance error messages for agent and session operations; update accessible paths handling

This commit is contained in:
Vaayne 2025-09-19 20:20:14 +08:00
parent d6468f33c5
commit f0ac74dccf
6 changed files with 53 additions and 21 deletions

View File

@ -71,7 +71,7 @@ export const createAgent = async (req: Request, res: Response): Promise<Response
logger.error('Error creating agent:', error)
return res.status(500).json({
error: {
message: 'Failed to create agent',
message: `Failed to create agent: ${error.message}`,
type: 'internal_error',
code: 'agent_creation_failed'
}
@ -315,7 +315,7 @@ export const updateAgent = async (req: Request, res: Response): Promise<Response
logger.error('Error updating agent:', error)
return res.status(500).json({
error: {
message: 'Failed to update agent',
message: 'Failed to update agent: ' + error.message,
type: 'internal_error',
code: 'agent_update_failed'
}
@ -461,7 +461,7 @@ export const patchAgent = async (req: Request, res: Response): Promise<Response>
logger.error('Error partially updating agent:', error)
return res.status(500).json({
error: {
message: 'Failed to partially update agent',
message: `Failed to partially update agent: ${error.message}`,
type: 'internal_error',
code: 'agent_patch_failed'
}

View File

@ -51,7 +51,7 @@ export const createSession = async (req: Request, res: Response): Promise<Respon
logger.error('Error creating session:', error)
return res.status(500).json({
error: {
message: 'Failed to create session',
message: `Failed to create session: ${error.message}`,
type: 'internal_error',
code: 'session_creation_failed'
}
@ -195,7 +195,7 @@ export const updateSession = async (req: Request, res: Response): Promise<Respon
logger.error('Error updating session:', error)
return res.status(500).json({
error: {
message: 'Failed to update session',
message: `Failed to update session: ${error.message}`,
type: 'internal_error',
code: 'session_update_failed'
}
@ -254,7 +254,7 @@ export const patchSession = async (req: Request, res: Response): Promise<Respons
logger.error('Error patching session:', error)
return res.status(500).json({
error: {
message: 'Failed to patch session',
message: `Failed to patch session, ${error.message}`,
type: 'internal_error',
code: 'session_patch_failed'
}

View File

@ -163,20 +163,36 @@ export abstract class BaseService {
return deserialized
}
protected ensurePathsExist(paths?: string[]): void {
/**
* Validate, normalize, and ensure filesystem access for a set of absolute paths.
*
* - Requires every entry to be an absolute path and throws if not.
* - Normalizes each path and deduplicates while preserving order.
* - Creates missing directories (or parent directories for file-like paths).
*/
protected ensurePathsExist(paths?: string[]): string[] {
if (!paths?.length) {
return
return []
}
const sanitizedPaths: string[] = []
const seenPaths = new Set<string>()
for (const rawPath of paths) {
if (!rawPath) {
continue
}
const resolvedPath = path.resolve(rawPath)
if (!path.isAbsolute(rawPath)) {
throw new Error(`Accessible path must be absolute: ${rawPath}`)
}
// Normalize to provide consistent values to downstream consumers.
const resolvedPath = path.normalize(rawPath)
let stats: fs.Stats | null = null
try {
// Attempt to stat the path to understand whether it already exists and if it is a file.
if (fs.existsSync(resolvedPath)) {
stats = fs.statSync(resolvedPath)
}
@ -190,6 +206,7 @@ export abstract class BaseService {
const looksLikeFile =
(stats && stats.isFile()) || (!stats && path.extname(resolvedPath) !== '' && !resolvedPath.endsWith(path.sep))
// For file-like targets create the parent directory; otherwise ensure the directory itself.
const directoryToEnsure = looksLikeFile ? path.dirname(resolvedPath) : resolvedPath
if (!fs.existsSync(directoryToEnsure)) {
@ -203,7 +220,15 @@ export abstract class BaseService {
throw error
}
}
// Preserve the first occurrence only to avoid duplicates while keeping caller order stable.
if (!seenPaths.has(resolvedPath)) {
seenPaths.add(resolvedPath)
sanitizedPaths.push(resolvedPath)
}
}
return sanitizedPaths
}
/**

View File

@ -45,14 +45,16 @@ export class AgentService extends BaseService {
req.accessible_paths = [defaultPath]
}
if (req.accessible_paths !== undefined) {
req.accessible_paths = this.ensurePathsExist(req.accessible_paths)
}
await this.validateAgentModels(req.type, {
model: req.model,
plan_model: req.plan_model,
small_model: req.small_model
})
this.ensurePathsExist(req.accessible_paths)
const serializedReq = this.serializeJsonFields(req)
const insertData: InsertAgentRow = {
@ -137,8 +139,8 @@ export class AgentService extends BaseService {
const now = new Date().toISOString()
if (updates.accessible_paths) {
this.ensurePathsExist(updates.accessible_paths)
if (updates.accessible_paths !== undefined) {
updates.accessible_paths = this.ensurePathsExist(updates.accessible_paths)
}
const modelUpdates: Partial<Record<AgentModelField, string | undefined>> = {}

View File

@ -58,7 +58,9 @@ export class SessionService extends BaseService {
small_model: sessionData.small_model
})
this.ensurePathsExist(sessionData.accessible_paths)
if (sessionData.accessible_paths !== undefined) {
sessionData.accessible_paths = this.ensurePathsExist(sessionData.accessible_paths)
}
const serializedData = this.serializeJsonFields(sessionData)
@ -179,8 +181,8 @@ export class SessionService extends BaseService {
const now = new Date().toISOString()
if (updates.accessible_paths) {
this.ensurePathsExist(updates.accessible_paths)
if (updates.accessible_paths !== undefined) {
updates.accessible_paths = this.ensurePathsExist(updates.accessible_paths)
}
const modelUpdates: Partial<Record<AgentModelField, string | undefined>> = {}

View File

@ -2,7 +2,7 @@
@host=http://localhost:23333
@token=cs-sk-af798ed4-7cf5-4fd7-ae4b-df203b164194
@agent_id=agent_1758092281575_tn9dxio9k
@session_id=session_1758252305914_9kef8yven
@session_id=session_1758278828236_mqj91e7c0
### List Sessions
GET {{host}}/v1/agents/{{agent_id}}/sessions
@ -16,11 +16,11 @@ Authorization: Bearer {{token}}
Content-Type: application/json
{
"name": "Story Writing Session 1",
"name": "Story Writing Session 2",
"instructions": "You are a creative writing assistant. Help me brainstorm and write engaging stories.",
"model": "anthropic:claude-sonnet-4",
"accessible_paths": [
"~/Documents/stories"
"/tmp/Documents/stories"
]
}
@ -44,7 +44,7 @@ Content-Type: application/json
"instructions": "You are a creative writing assistant. Help me brainstorm and write engaging stories.",
"model": "anthropic:claude-sonnet-4",
"accessible_paths": [
"~/Documents/stories"
"/tmp/Documents/stories"
]
}
@ -55,7 +55,10 @@ Authorization: Bearer {{token}}
Content-Type: application/json
{
"instructions": "You are a creative writing assistant. Help me brainstorm and write engaging stories. Focus on character development and plot structure.",
"model": "anthropic:claude-sonnet-4-20250514",
"accessible_paths": [
"/tmp/Documents/stories2"
]
}