mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 22:10:21 +08:00
feat(agents): enhance error messages for agent and session operations; update accessible paths handling
This commit is contained in:
parent
d6468f33c5
commit
f0ac74dccf
@ -71,7 +71,7 @@ export const createAgent = async (req: Request, res: Response): Promise<Response
|
|||||||
logger.error('Error creating agent:', error)
|
logger.error('Error creating agent:', error)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: {
|
error: {
|
||||||
message: 'Failed to create agent',
|
message: `Failed to create agent: ${error.message}`,
|
||||||
type: 'internal_error',
|
type: 'internal_error',
|
||||||
code: 'agent_creation_failed'
|
code: 'agent_creation_failed'
|
||||||
}
|
}
|
||||||
@ -315,7 +315,7 @@ export const updateAgent = async (req: Request, res: Response): Promise<Response
|
|||||||
logger.error('Error updating agent:', error)
|
logger.error('Error updating agent:', error)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: {
|
error: {
|
||||||
message: 'Failed to update agent',
|
message: 'Failed to update agent: ' + error.message,
|
||||||
type: 'internal_error',
|
type: 'internal_error',
|
||||||
code: 'agent_update_failed'
|
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)
|
logger.error('Error partially updating agent:', error)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: {
|
error: {
|
||||||
message: 'Failed to partially update agent',
|
message: `Failed to partially update agent: ${error.message}`,
|
||||||
type: 'internal_error',
|
type: 'internal_error',
|
||||||
code: 'agent_patch_failed'
|
code: 'agent_patch_failed'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export const createSession = async (req: Request, res: Response): Promise<Respon
|
|||||||
logger.error('Error creating session:', error)
|
logger.error('Error creating session:', error)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: {
|
error: {
|
||||||
message: 'Failed to create session',
|
message: `Failed to create session: ${error.message}`,
|
||||||
type: 'internal_error',
|
type: 'internal_error',
|
||||||
code: 'session_creation_failed'
|
code: 'session_creation_failed'
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ export const updateSession = async (req: Request, res: Response): Promise<Respon
|
|||||||
logger.error('Error updating session:', error)
|
logger.error('Error updating session:', error)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: {
|
error: {
|
||||||
message: 'Failed to update session',
|
message: `Failed to update session: ${error.message}`,
|
||||||
type: 'internal_error',
|
type: 'internal_error',
|
||||||
code: 'session_update_failed'
|
code: 'session_update_failed'
|
||||||
}
|
}
|
||||||
@ -254,7 +254,7 @@ export const patchSession = async (req: Request, res: Response): Promise<Respons
|
|||||||
logger.error('Error patching session:', error)
|
logger.error('Error patching session:', error)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: {
|
error: {
|
||||||
message: 'Failed to patch session',
|
message: `Failed to patch session, ${error.message}`,
|
||||||
type: 'internal_error',
|
type: 'internal_error',
|
||||||
code: 'session_patch_failed'
|
code: 'session_patch_failed'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -163,20 +163,36 @@ export abstract class BaseService {
|
|||||||
return deserialized
|
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) {
|
if (!paths?.length) {
|
||||||
return
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sanitizedPaths: string[] = []
|
||||||
|
const seenPaths = new Set<string>()
|
||||||
|
|
||||||
for (const rawPath of paths) {
|
for (const rawPath of paths) {
|
||||||
if (!rawPath) {
|
if (!rawPath) {
|
||||||
continue
|
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
|
let stats: fs.Stats | null = null
|
||||||
try {
|
try {
|
||||||
|
// Attempt to stat the path to understand whether it already exists and if it is a file.
|
||||||
if (fs.existsSync(resolvedPath)) {
|
if (fs.existsSync(resolvedPath)) {
|
||||||
stats = fs.statSync(resolvedPath)
|
stats = fs.statSync(resolvedPath)
|
||||||
}
|
}
|
||||||
@ -190,6 +206,7 @@ export abstract class BaseService {
|
|||||||
const looksLikeFile =
|
const looksLikeFile =
|
||||||
(stats && stats.isFile()) || (!stats && path.extname(resolvedPath) !== '' && !resolvedPath.endsWith(path.sep))
|
(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
|
const directoryToEnsure = looksLikeFile ? path.dirname(resolvedPath) : resolvedPath
|
||||||
|
|
||||||
if (!fs.existsSync(directoryToEnsure)) {
|
if (!fs.existsSync(directoryToEnsure)) {
|
||||||
@ -203,7 +220,15 @@ export abstract class BaseService {
|
|||||||
throw error
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -45,14 +45,16 @@ export class AgentService extends BaseService {
|
|||||||
req.accessible_paths = [defaultPath]
|
req.accessible_paths = [defaultPath]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.accessible_paths !== undefined) {
|
||||||
|
req.accessible_paths = this.ensurePathsExist(req.accessible_paths)
|
||||||
|
}
|
||||||
|
|
||||||
await this.validateAgentModels(req.type, {
|
await this.validateAgentModels(req.type, {
|
||||||
model: req.model,
|
model: req.model,
|
||||||
plan_model: req.plan_model,
|
plan_model: req.plan_model,
|
||||||
small_model: req.small_model
|
small_model: req.small_model
|
||||||
})
|
})
|
||||||
|
|
||||||
this.ensurePathsExist(req.accessible_paths)
|
|
||||||
|
|
||||||
const serializedReq = this.serializeJsonFields(req)
|
const serializedReq = this.serializeJsonFields(req)
|
||||||
|
|
||||||
const insertData: InsertAgentRow = {
|
const insertData: InsertAgentRow = {
|
||||||
@ -137,8 +139,8 @@ export class AgentService extends BaseService {
|
|||||||
|
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
if (updates.accessible_paths) {
|
if (updates.accessible_paths !== undefined) {
|
||||||
this.ensurePathsExist(updates.accessible_paths)
|
updates.accessible_paths = this.ensurePathsExist(updates.accessible_paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelUpdates: Partial<Record<AgentModelField, string | undefined>> = {}
|
const modelUpdates: Partial<Record<AgentModelField, string | undefined>> = {}
|
||||||
|
|||||||
@ -58,7 +58,9 @@ export class SessionService extends BaseService {
|
|||||||
small_model: sessionData.small_model
|
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)
|
const serializedData = this.serializeJsonFields(sessionData)
|
||||||
|
|
||||||
@ -179,8 +181,8 @@ export class SessionService extends BaseService {
|
|||||||
|
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
if (updates.accessible_paths) {
|
if (updates.accessible_paths !== undefined) {
|
||||||
this.ensurePathsExist(updates.accessible_paths)
|
updates.accessible_paths = this.ensurePathsExist(updates.accessible_paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelUpdates: Partial<Record<AgentModelField, string | undefined>> = {}
|
const modelUpdates: Partial<Record<AgentModelField, string | undefined>> = {}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
@host=http://localhost:23333
|
@host=http://localhost:23333
|
||||||
@token=cs-sk-af798ed4-7cf5-4fd7-ae4b-df203b164194
|
@token=cs-sk-af798ed4-7cf5-4fd7-ae4b-df203b164194
|
||||||
@agent_id=agent_1758092281575_tn9dxio9k
|
@agent_id=agent_1758092281575_tn9dxio9k
|
||||||
@session_id=session_1758252305914_9kef8yven
|
@session_id=session_1758278828236_mqj91e7c0
|
||||||
|
|
||||||
### List Sessions
|
### List Sessions
|
||||||
GET {{host}}/v1/agents/{{agent_id}}/sessions
|
GET {{host}}/v1/agents/{{agent_id}}/sessions
|
||||||
@ -16,11 +16,11 @@ Authorization: Bearer {{token}}
|
|||||||
Content-Type: application/json
|
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.",
|
"instructions": "You are a creative writing assistant. Help me brainstorm and write engaging stories.",
|
||||||
"model": "anthropic:claude-sonnet-4",
|
"model": "anthropic:claude-sonnet-4",
|
||||||
"accessible_paths": [
|
"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.",
|
"instructions": "You are a creative writing assistant. Help me brainstorm and write engaging stories.",
|
||||||
"model": "anthropic:claude-sonnet-4",
|
"model": "anthropic:claude-sonnet-4",
|
||||||
"accessible_paths": [
|
"accessible_paths": [
|
||||||
"~/Documents/stories"
|
"/tmp/Documents/stories"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,10 @@ Authorization: Bearer {{token}}
|
|||||||
Content-Type: application/json
|
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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user