mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 13:59:28 +08:00
refactor: Rename message stream handler and update session creation logic
This commit is contained in:
parent
219d162e1a
commit
d1ff8591a6
@ -24,13 +24,13 @@ const verifyAgentAndSession = async (agentId: string, sessionId: string) => {
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createMessageStream = async (req: Request, res: Response): Promise<void> => {
|
export const createMessage = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { agentId, sessionId } = req.params
|
const { agentId, sessionId } = req.params
|
||||||
|
|
||||||
const session = await verifyAgentAndSession(agentId, sessionId)
|
const session = await verifyAgentAndSession(agentId, sessionId)
|
||||||
|
|
||||||
const messageData = { ...req.body, session_id: sessionId }
|
const messageData = req.body
|
||||||
|
|
||||||
logger.info(`Creating streaming message for session: ${sessionId}`)
|
logger.info(`Creating streaming message for session: ${sessionId}`)
|
||||||
logger.debug('Streaming message data:', messageData)
|
logger.debug('Streaming message data:', messageData)
|
||||||
@ -45,7 +45,7 @@ export const createMessageStream = async (req: Request, res: Response): Promise<
|
|||||||
// Send initial connection event
|
// Send initial connection event
|
||||||
res.write('data: {"type":"start"}\n\n')
|
res.write('data: {"type":"start"}\n\n')
|
||||||
|
|
||||||
const messageStream = sessionMessageService.createSessionMessageStream(session, messageData)
|
const messageStream = sessionMessageService.createSessionMessage(session, messageData)
|
||||||
|
|
||||||
// Track if the response has ended to prevent further writes
|
// Track if the response has ended to prevent further writes
|
||||||
let responseEnded = false
|
let responseEnded = false
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { sessionMessageService, sessionService } from '@main/services/agents'
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
import { sessionMessageService, sessionService } from '../../../../services/agents'
|
|
||||||
|
|
||||||
const logger = loggerService.withContext('ApiServerSessionsHandlers')
|
const logger = loggerService.withContext('ApiServerSessionsHandlers')
|
||||||
|
|
||||||
export const createSession = async (req: Request, res: Response): Promise<Response> => {
|
export const createSession = async (req: Request, res: Response): Promise<Response> => {
|
||||||
try {
|
try {
|
||||||
const { agentId } = req.params
|
const { agentId } = req.params
|
||||||
const sessionData = { ...req.body, main_agent_id: agentId }
|
const sessionData = req.body
|
||||||
|
|
||||||
logger.info(`Creating new session for agent: ${agentId}`)
|
logger.info(`Creating new session for agent: ${agentId}`)
|
||||||
logger.debug('Session data:', sessionData)
|
logger.debug('Session data:', sessionData)
|
||||||
|
|
||||||
const session = await sessionService.createSession(sessionData)
|
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)
|
return res.status(201).json(session)
|
||||||
@ -38,7 +37,7 @@ export const listSessions = async (req: Request, res: Response): Promise<Respons
|
|||||||
|
|
||||||
logger.info(`Listing sessions for agent: ${agentId} with limit=${limit}, offset=${offset}, status=${status}`)
|
logger.info(`Listing sessions for agent: ${agentId} with limit=${limit}, offset=${offset}, status=${status}`)
|
||||||
|
|
||||||
const result = await sessionService.listSessions(agentId, { limit, offset, status })
|
const result = await sessionService.listSessions(agentId, { limit, offset })
|
||||||
|
|
||||||
logger.info(`Retrieved ${result.sessions.length} sessions (total: ${result.total}) for agent: ${agentId}`)
|
logger.info(`Retrieved ${result.sessions.length} sessions (total: ${result.total}) for agent: ${agentId}`)
|
||||||
return res.json({
|
return res.json({
|
||||||
@ -77,16 +76,16 @@ export const getSession = async (req: Request, res: Response): Promise<Response>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify session belongs to the agent
|
// // Verify session belongs to the agent
|
||||||
logger.warn(`Session ${sessionId} does not belong to agent ${agentId}`)
|
// logger.warn(`Session ${sessionId} does not belong to agent ${agentId}`)
|
||||||
return res.status(404).json({
|
// return res.status(404).json({
|
||||||
error: {
|
// error: {
|
||||||
message: 'Session not found for this agent',
|
// message: 'Session not found for this agent',
|
||||||
type: 'not_found',
|
// type: 'not_found',
|
||||||
code: 'session_not_found'
|
// code: 'session_not_found'
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fetch session messages
|
// Fetch session messages
|
||||||
logger.info(`Fetching messages for session: ${sessionId}`)
|
logger.info(`Fetching messages for session: ${sessionId}`)
|
||||||
@ -261,7 +260,7 @@ export const listAllSessions = async (req: Request, res: Response): Promise<Resp
|
|||||||
|
|
||||||
logger.info(`Listing all sessions with limit=${limit}, offset=${offset}, status=${status}`)
|
logger.info(`Listing all sessions with limit=${limit}, offset=${offset}, status=${status}`)
|
||||||
|
|
||||||
const result = await sessionService.listSessions(undefined, { limit, offset, status })
|
const result = await sessionService.listSessions(undefined, { limit, offset })
|
||||||
|
|
||||||
logger.info(`Retrieved ${result.sessions.length} sessions (total: ${result.total})`)
|
logger.info(`Retrieved ${result.sessions.length} sessions (total: ${result.total})`)
|
||||||
return res.json({
|
return res.json({
|
||||||
|
|||||||
@ -188,7 +188,7 @@ const createMessagesRouter = (): express.Router => {
|
|||||||
const messagesRouter = express.Router({ mergeParams: true })
|
const messagesRouter = express.Router({ mergeParams: true })
|
||||||
|
|
||||||
// Message CRUD routes (nested under agent/session)
|
// Message CRUD routes (nested under agent/session)
|
||||||
messagesRouter.post('/', validateSessionMessage, handleValidationErrors, messageHandlers.createMessageStream)
|
messagesRouter.post('/', validateSessionMessage, handleValidationErrors, messageHandlers.createMessage)
|
||||||
return messagesRouter
|
return messagesRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { body } from 'express-validator'
|
import { body } from 'express-validator'
|
||||||
|
|
||||||
export const validateSessionMessage = [
|
export const validateSessionMessage = [
|
||||||
body('role').notEmpty().isIn(['user', 'agent', 'system', 'tool']).withMessage('Valid role is required'),
|
|
||||||
body('content').notEmpty().isString().withMessage('Content must be a valid string')
|
body('content').notEmpty().isString().withMessage('Content must be a valid string')
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { type Client, createClient } from '@libsql/client'
|
import { type Client, createClient } from '@libsql/client'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { drizzle } from 'drizzle-orm/libsql'
|
import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
import * as schema from './database/schema'
|
|
||||||
import { MigrationService } from './database/MigrationService'
|
import { MigrationService } from './database/MigrationService'
|
||||||
|
import * as schema from './database/schema'
|
||||||
import { dbPath } from './drizzle.config'
|
import { dbPath } from './drizzle.config'
|
||||||
|
|
||||||
const logger = loggerService.withContext('BaseService')
|
const logger = loggerService.withContext('BaseService')
|
||||||
@ -24,7 +24,7 @@ const logger = loggerService.withContext('BaseService')
|
|||||||
*/
|
*/
|
||||||
export abstract class BaseService {
|
export abstract class BaseService {
|
||||||
protected static client: Client | null = null
|
protected static client: Client | null = null
|
||||||
protected static db: ReturnType<typeof drizzle> | null = null
|
protected static db: LibSQLDatabase<typeof schema> | null = null
|
||||||
protected static isInitialized = false
|
protected static isInitialized = false
|
||||||
protected static initializationPromise: Promise<void> | null = null
|
protected static initializationPromise: Promise<void> | null = null
|
||||||
protected jsonFields: string[] = ['built_in_tools', 'mcps', 'configuration', 'accessible_paths']
|
protected jsonFields: string[] = ['built_in_tools', 'mcps', 'configuration', 'accessible_paths']
|
||||||
@ -110,7 +110,7 @@ export abstract class BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get database(): ReturnType<typeof drizzle> {
|
protected get database(): LibSQLDatabase<typeof schema> {
|
||||||
this.ensureInitialized()
|
this.ensureInitialized()
|
||||||
return BaseService.db!
|
return BaseService.db!
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './agents.schema'
|
export * from './agents.schema'
|
||||||
export * from './sessions.schema'
|
|
||||||
export * from './messages.schema'
|
export * from './messages.schema'
|
||||||
export * from './migrations.schema'
|
export * from './migrations.schema'
|
||||||
|
export * from './sessions.schema'
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { foreignKey, index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
import { foreignKey, index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
import { sessionsTable } from './sessions.schema'
|
import { sessionsTable } from './sessions.schema'
|
||||||
|
|
||||||
// session_messages table to log all messages, thoughts, actions, observations in a session
|
// session_messages table to log all messages, thoughts, actions, observations in a session
|
||||||
|
|||||||
@ -3,10 +3,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { foreignKey, index, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
import { foreignKey, index, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
import { agentsTable } from './agents.schema'
|
import { agentsTable } from './agents.schema'
|
||||||
|
|
||||||
export const sessionsTable = sqliteTable('sessions', {
|
export const sessionsTable = sqliteTable('sessions', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
|
agent_type: text('agent_type').notNull(),
|
||||||
agent_id: text('agent_id').notNull(), // Primary agent ID for the session
|
agent_id: text('agent_id').notNull(), // Primary agent ID for the session
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
description: text('description'),
|
description: text('description'),
|
||||||
|
|||||||
@ -16,12 +16,16 @@ function getDbPath() {
|
|||||||
return path.join(app.getPath('userData'), 'agents.db')
|
return path.join(app.getPath('userData'), 'agents.db')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedDbPath = getDbPath()
|
||||||
|
|
||||||
|
export const dbPath = resolvedDbPath
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: 'sqlite',
|
dialect: 'sqlite',
|
||||||
schema: './src/main/services/agents/database/schema/index.ts',
|
schema: './src/main/services/agents/database/schema/index.ts',
|
||||||
out: './src/main/services/agents/database/drizzle',
|
out: './src/main/services/agents/database/drizzle',
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: `file:${getDbPath()}`
|
url: `file:${resolvedDbPath}`
|
||||||
},
|
},
|
||||||
verbose: true,
|
verbose: true,
|
||||||
strict: true
|
strict: true
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { UIMessageChunk } from 'ai'
|
|||||||
import { count, eq } from 'drizzle-orm'
|
import { count, eq } from 'drizzle-orm'
|
||||||
|
|
||||||
import { BaseService } from '../BaseService'
|
import { BaseService } from '../BaseService'
|
||||||
import { type InsertSessionMessageRow, sessionMessagesTable } from '../database/schema'
|
import { sessionMessagesTable } from '../database/schema'
|
||||||
import ClaudeCodeService from './claudecode'
|
import ClaudeCodeService from './claudecode'
|
||||||
|
|
||||||
const logger = loggerService.withContext('SessionMessageService')
|
const logger = loggerService.withContext('SessionMessageService')
|
||||||
@ -76,19 +76,19 @@ export class SessionMessageService extends BaseService {
|
|||||||
return { messages, total }
|
return { messages, total }
|
||||||
}
|
}
|
||||||
|
|
||||||
createSessionMessageStream(session: GetAgentSessionResponse, messageData: CreateSessionMessageRequest): EventEmitter {
|
createSessionMessage(session: GetAgentSessionResponse, messageData: CreateSessionMessageRequest): EventEmitter {
|
||||||
this.ensureInitialized()
|
this.ensureInitialized()
|
||||||
|
|
||||||
// Create a new EventEmitter to manage the session message lifecycle
|
// Create a new EventEmitter to manage the session message lifecycle
|
||||||
const sessionStream = new EventEmitter()
|
const sessionStream = new EventEmitter()
|
||||||
|
|
||||||
// No parent validation needed, start immediately
|
// No parent validation needed, start immediately
|
||||||
this.startClaudeCodeStream(session, messageData, sessionStream)
|
this.startSessionMessageStream(session, messageData, sessionStream)
|
||||||
|
|
||||||
return sessionStream
|
return sessionStream
|
||||||
}
|
}
|
||||||
|
|
||||||
private startClaudeCodeStream(
|
private startSessionMessageStream(
|
||||||
session: GetAgentSessionResponse,
|
session: GetAgentSessionResponse,
|
||||||
req: CreateSessionMessageRequest,
|
req: CreateSessionMessageRequest,
|
||||||
sessionStream: EventEmitter
|
sessionStream: EventEmitter
|
||||||
@ -99,7 +99,12 @@ export class SessionMessageService extends BaseService {
|
|||||||
session_id = previousMessages[0].session_id
|
session_id = previousMessages[0].session_id
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('Claude Code stream message data:', { message: req, session_id })
|
logger.debug('Session Message stream message data:', { message: req, session_id })
|
||||||
|
|
||||||
|
if (session.agent_type !== 'claude-code') {
|
||||||
|
logger.error('Unsupported agent type for streaming:', { agent_type: session.agent_type })
|
||||||
|
throw new Error('Unsupported agent type for streaming')
|
||||||
|
}
|
||||||
|
|
||||||
// Create the streaming agent invocation (using invokeStream for streaming)
|
// Create the streaming agent invocation (using invokeStream for streaming)
|
||||||
const claudeStream = this.cc.invoke(req.content, session.accessible_paths[0], session_id, {
|
const claudeStream = this.cc.invoke(req.content, session.accessible_paths[0], session_id, {
|
||||||
@ -107,7 +112,6 @@ export class SessionMessageService extends BaseService {
|
|||||||
maxTurns: session.configuration?.maxTurns || 10
|
maxTurns: session.configuration?.maxTurns || 10
|
||||||
})
|
})
|
||||||
|
|
||||||
let sessionMessage: AgentSessionMessageEntity | null = null
|
|
||||||
const streamedChunks: UIMessageChunk[] = []
|
const streamedChunks: UIMessageChunk[] = []
|
||||||
const rawAgentMessages: any[] = [] // Generic agent messages storage
|
const rawAgentMessages: any[] = [] // Generic agent messages storage
|
||||||
|
|
||||||
@ -202,6 +206,10 @@ export class SessionMessageService extends BaseService {
|
|||||||
// error: new Error('Failed to save session message to database')
|
// error: new Error('Failed to save session message to database')
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
sessionStream.emit('data', {
|
||||||
|
type: 'complete',
|
||||||
|
result: structuredContent
|
||||||
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
AgentEntity,
|
||||||
AgentSessionEntity,
|
AgentSessionEntity,
|
||||||
CreateSessionRequest,
|
CreateSessionRequest,
|
||||||
GetAgentSessionResponse,
|
GetAgentSessionResponse,
|
||||||
@ -25,18 +26,18 @@ export class SessionService extends BaseService {
|
|||||||
await BaseService.initialize()
|
await BaseService.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(req: CreateSessionRequest): Promise<AgentSessionEntity> {
|
async createSession(agentId: string, req: CreateSessionRequest): Promise<AgentSessionEntity> {
|
||||||
this.ensureInitialized()
|
this.ensureInitialized()
|
||||||
|
|
||||||
// Validate agent exists - we'll need to import AgentService for this check
|
// Validate agent exists - we'll need to import AgentService for this check
|
||||||
// For now, we'll skip this validation to avoid circular dependencies
|
// For now, we'll skip this validation to avoid circular dependencies
|
||||||
// The database foreign key constraint will handle this
|
// The database foreign key constraint will handle this
|
||||||
|
|
||||||
const agents = await this.database.select().from(agentsTable).where(eq(agentsTable.id, req.agent_id)).limit(1)
|
const agents = await this.database.select().from(agentsTable).where(eq(agentsTable.id, agentId)).limit(1)
|
||||||
if (!agents[0]) {
|
if (!agents[0]) {
|
||||||
throw new Error('Agent not found')
|
throw new Error('Agent not found')
|
||||||
}
|
}
|
||||||
const agent = this.deserializeJsonFields(agents[0]) as AgentSessionEntity
|
const agent = this.deserializeJsonFields(agents[0]) as AgentEntity
|
||||||
|
|
||||||
const id = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
const id = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
@ -51,15 +52,17 @@ export class SessionService extends BaseService {
|
|||||||
|
|
||||||
const insertData: InsertSessionRow = {
|
const insertData: InsertSessionRow = {
|
||||||
id,
|
id,
|
||||||
|
agent_id: agentId,
|
||||||
|
agent_type: agent.type,
|
||||||
name: serializedData.name || null,
|
name: serializedData.name || null,
|
||||||
agent_id: serializedData.agent_id,
|
|
||||||
description: serializedData.description || null,
|
description: serializedData.description || null,
|
||||||
|
accessible_paths: serializedData.accessible_paths || null,
|
||||||
|
instructions: serializedData.instructions || null,
|
||||||
model: serializedData.model || null,
|
model: serializedData.model || null,
|
||||||
plan_model: serializedData.plan_model || null,
|
plan_model: serializedData.plan_model || null,
|
||||||
small_model: serializedData.small_model || null,
|
small_model: serializedData.small_model || null,
|
||||||
mcps: serializedData.mcps || null,
|
mcps: serializedData.mcps || null,
|
||||||
configuration: serializedData.configuration || null,
|
configuration: serializedData.configuration || null,
|
||||||
accessible_paths: serializedData.accessible_paths || null,
|
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now
|
updated_at: now
|
||||||
}
|
}
|
||||||
|
|||||||
@ -242,155 +242,6 @@ class ClaudeCodeService implements AgentServiceInterface {
|
|||||||
process.on('error', () => clearTimeout(timeout))
|
process.on('error', () => clearTimeout(timeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up process event handlers and return a promise that resolves with complete output
|
|
||||||
*/
|
|
||||||
private setupProcessHandlers(process: ChildProcess): Promise<ClaudeCodeResult> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let stdoutData = ''
|
|
||||||
let stderrData = ''
|
|
||||||
const jsonOutput: any[] = []
|
|
||||||
let hasResolved = false
|
|
||||||
|
|
||||||
const startTime = Date.now()
|
|
||||||
|
|
||||||
// Handle stdout with proper encoding and buffering
|
|
||||||
if (process.stdout) {
|
|
||||||
process.stdout.setEncoding('utf8')
|
|
||||||
process.stdout.on('data', (data: string) => {
|
|
||||||
stdoutData += data
|
|
||||||
logger.debug('Agent stdout chunk:', { length: data.length })
|
|
||||||
|
|
||||||
// Parse JSON stream output line by line
|
|
||||||
const lines = data.split('\n')
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.trim()) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(line.trim())
|
|
||||||
jsonOutput.push(parsed)
|
|
||||||
logger.silly('Parsed JSON output:', parsed)
|
|
||||||
} catch (e) {
|
|
||||||
// Not JSON, might be plain text output
|
|
||||||
logger.debug('Non-JSON stdout line:', { line: line.trim() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
process.stdout.on('end', () => {
|
|
||||||
logger.debug('Agent stdout stream ended')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle stderr with proper encoding
|
|
||||||
if (process.stderr) {
|
|
||||||
process.stderr.setEncoding('utf8')
|
|
||||||
process.stderr.on('data', (data: string) => {
|
|
||||||
stderrData += data
|
|
||||||
logger.warn('Agent stderr chunk:', { data: data.trim() })
|
|
||||||
})
|
|
||||||
|
|
||||||
process.stderr.on('end', () => {
|
|
||||||
logger.debug('Agent stderr stream ended')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle process exit
|
|
||||||
process.on('exit', (code, signal) => {
|
|
||||||
const duration = Date.now() - startTime
|
|
||||||
const success = code === 0
|
|
||||||
const status = success ? 'completed' : 'failed'
|
|
||||||
|
|
||||||
logger.info('Agent process exited', {
|
|
||||||
code,
|
|
||||||
signal,
|
|
||||||
success,
|
|
||||||
status,
|
|
||||||
duration,
|
|
||||||
stdoutLength: stdoutData.length,
|
|
||||||
stderrLength: stderrData.length,
|
|
||||||
jsonItems: jsonOutput.length
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!hasResolved) {
|
|
||||||
hasResolved = true
|
|
||||||
resolve({
|
|
||||||
success,
|
|
||||||
stdout: stdoutData,
|
|
||||||
stderr: stderrData,
|
|
||||||
jsonOutput,
|
|
||||||
exitCode: code || undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle process errors
|
|
||||||
process.on('error', (error) => {
|
|
||||||
const duration = Date.now() - startTime
|
|
||||||
logger.error('Agent process error:', {
|
|
||||||
error: error.message,
|
|
||||||
duration,
|
|
||||||
stdoutLength: stdoutData.length,
|
|
||||||
stderrLength: stderrData.length
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!hasResolved) {
|
|
||||||
hasResolved = true
|
|
||||||
reject({
|
|
||||||
success: false,
|
|
||||||
stdout: stdoutData,
|
|
||||||
stderr: stderrData,
|
|
||||||
jsonOutput,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle close event as a fallback
|
|
||||||
process.on('close', (code, signal) => {
|
|
||||||
const duration = Date.now() - startTime
|
|
||||||
logger.debug('Agent process closed', { code, signal, duration })
|
|
||||||
|
|
||||||
// Only resolve here if exit event hasn't fired
|
|
||||||
if (!hasResolved) {
|
|
||||||
hasResolved = true
|
|
||||||
const success = code === 0
|
|
||||||
resolve({
|
|
||||||
success,
|
|
||||||
stdout: stdoutData,
|
|
||||||
stderr: stderrData,
|
|
||||||
jsonOutput,
|
|
||||||
exitCode: code || undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set a timeout to prevent hanging indefinitely (reduced for debugging)
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
if (!hasResolved) {
|
|
||||||
hasResolved = true
|
|
||||||
logger.error('Agent process timeout after 30 seconds', {
|
|
||||||
pid: process.pid,
|
|
||||||
stdoutLength: stdoutData.length,
|
|
||||||
stderrLength: stderrData.length,
|
|
||||||
jsonItems: jsonOutput.length
|
|
||||||
})
|
|
||||||
process.kill('SIGTERM')
|
|
||||||
reject({
|
|
||||||
success: false,
|
|
||||||
stdout: stdoutData,
|
|
||||||
stderr: stderrData,
|
|
||||||
jsonOutput,
|
|
||||||
error: new Error('Process timeout after 30 seconds')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 30 * 1000) // 30 seconds timeout for debugging
|
|
||||||
|
|
||||||
// Clear timeout when process ends
|
|
||||||
process.on('exit', () => clearTimeout(timeout))
|
|
||||||
process.on('error', () => clearTimeout(timeout))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ClaudeCodeService
|
export default ClaudeCodeService
|
||||||
|
|||||||
@ -16,5 +16,10 @@ export { sessionMessageService } from './SessionMessageService'
|
|||||||
export { sessionService } from './SessionService'
|
export { sessionService } from './SessionService'
|
||||||
|
|
||||||
// Type definitions for service requests and responses
|
// Type definitions for service requests and responses
|
||||||
|
export type { AgentEntity, AgentSessionEntity,CreateAgentRequest, UpdateAgentRequest } from '@types'
|
||||||
export type { CreateSessionRequest, ListSessionsOptions, UpdateSessionRequest } from './SessionService'
|
export type {
|
||||||
|
AgentSessionMessageEntity,
|
||||||
|
CreateSessionRequest,
|
||||||
|
GetAgentSessionResponse,
|
||||||
|
ListOptions as SessionListOptions,
|
||||||
|
UpdateSessionRequest} from '@types'
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { useTimer } from '@renderer/hooks/useTimer'
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { AgentEntity, AgentType, isAgentType } from '@renderer/types'
|
import { AgentEntity, AgentType, isAgentType } from '@renderer/types'
|
||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { ChangeEvent, FormEvent, ReactNode, useCallback, useMemo, useRef, useState } from 'react'
|
import { ChangeEvent, FormEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { ErrorBoundary } from '../ErrorBoundary'
|
import { ErrorBoundary } from '../ErrorBoundary'
|
||||||
@ -45,13 +45,23 @@ interface AgentTypeOption extends Option {
|
|||||||
type ModelOption = Option
|
type ModelOption = Option
|
||||||
|
|
||||||
type AgentForm = {
|
type AgentForm = {
|
||||||
type: AgentEntity['type']
|
type: AgentType
|
||||||
name: AgentEntity['name']
|
name: string
|
||||||
description?: AgentEntity['description']
|
description?: string
|
||||||
instructions?: AgentEntity['instructions']
|
instructions?: string
|
||||||
model?: AgentEntity['model']
|
model: string
|
||||||
|
accessible_paths: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildAgentForm = (existing?: AgentEntity): AgentForm => ({
|
||||||
|
type: existing?.type ?? 'claude-code',
|
||||||
|
name: existing?.name ?? 'Claude Code',
|
||||||
|
description: existing?.description,
|
||||||
|
instructions: existing?.instructions,
|
||||||
|
model: existing?.model ?? 'claude-4-sonnet',
|
||||||
|
accessible_paths: existing?.accessible_paths ? [...existing.accessible_paths] : []
|
||||||
|
})
|
||||||
|
|
||||||
interface BaseProps {
|
interface BaseProps {
|
||||||
agent?: AgentEntity
|
agent?: AgentEntity
|
||||||
}
|
}
|
||||||
@ -88,16 +98,13 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
|
|||||||
const { addAgent, updateAgent } = useAgents()
|
const { addAgent, updateAgent } = useAgents()
|
||||||
const isEditing = (agent?: AgentEntity) => agent !== undefined
|
const isEditing = (agent?: AgentEntity) => agent !== undefined
|
||||||
|
|
||||||
// default values. may change to undefined.
|
const [form, setForm] = useState<AgentForm>(() => buildAgentForm(agent))
|
||||||
const [form, setForm] = useState<AgentForm>(
|
|
||||||
isEditing(agent)
|
useEffect(() => {
|
||||||
? agent
|
if (isOpen) {
|
||||||
: {
|
setForm(buildAgentForm(agent))
|
||||||
type: 'claude-code',
|
}
|
||||||
name: 'Claude Code',
|
}, [agent, isOpen])
|
||||||
model: 'claude-4-sonnet'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const Option = useCallback(
|
const Option = useCallback(
|
||||||
({ option }: { option?: Option | null }) => {
|
({ option }: { option?: Option | null }) => {
|
||||||
@ -222,44 +229,51 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
|
|||||||
// Additional validation check besides native HTML validation to ensure security
|
// Additional validation check besides native HTML validation to ensure security
|
||||||
if (!isAgentType(form.type)) {
|
if (!isAgentType(form.type)) {
|
||||||
window.toast.error(t('agent.add.error.invalid_agent'))
|
window.toast.error(t('agent.add.error.invalid_agent'))
|
||||||
|
loadingRef.current = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (form.model === undefined) {
|
if (!form.model) {
|
||||||
window.toast.error(t('error.model.not_exists'))
|
window.toast.error(t('error.model.not_exists'))
|
||||||
|
loadingRef.current = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let _agent: AgentEntity
|
let resultAgent: AgentEntity
|
||||||
if (isEditing(agent)) {
|
if (isEditing(agent)) {
|
||||||
_agent = {
|
if (!agent) {
|
||||||
...agent,
|
throw new Error('Agent is required for editing mode')
|
||||||
// type: form.type,
|
}
|
||||||
|
|
||||||
|
const updatePayload: Partial<AgentEntity> & { id: string } = {
|
||||||
|
id: agent.id,
|
||||||
name: form.name,
|
name: form.name,
|
||||||
description: form.description,
|
description: form.description,
|
||||||
instructions: form.instructions,
|
instructions: form.instructions,
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
model: form.model
|
model: form.model
|
||||||
// avatar: getAvatar(form.type)
|
}
|
||||||
} satisfies AgentEntity
|
|
||||||
updateAgent(_agent)
|
updateAgent(updatePayload)
|
||||||
|
resultAgent = { ...agent, ...updatePayload }
|
||||||
window.toast.success(t('common.update_success'))
|
window.toast.success(t('common.update_success'))
|
||||||
} else {
|
} else {
|
||||||
_agent = {
|
const now = new Date().toISOString()
|
||||||
|
resultAgent = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
type: form.type,
|
type: form.type,
|
||||||
name: form.name,
|
name: form.name,
|
||||||
description: form.description,
|
description: form.description,
|
||||||
instructions: form.instructions,
|
instructions: form.instructions,
|
||||||
created_at: new Date().toISOString(),
|
created_at: now,
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: now,
|
||||||
model: form.model,
|
model: form.model,
|
||||||
avatar: getAvatar(form.type)
|
accessible_paths: [...form.accessible_paths]
|
||||||
} satisfies AgentEntity
|
}
|
||||||
addAgent(_agent)
|
addAgent(resultAgent)
|
||||||
window.toast.success(t('common.add_success'))
|
window.toast.success(t('common.add_success'))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('Agent', _agent)
|
logger.debug('Agent mutation payload', { agent: resultAgent })
|
||||||
loadingRef.current = false
|
loadingRef.current = false
|
||||||
|
|
||||||
setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
||||||
@ -271,6 +285,7 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
|
|||||||
form.name,
|
form.name,
|
||||||
form.description,
|
form.description,
|
||||||
form.instructions,
|
form.instructions,
|
||||||
|
form.accessible_paths,
|
||||||
agent,
|
agent,
|
||||||
setTimeoutTimer,
|
setTimeoutTimer,
|
||||||
onClose,
|
onClose,
|
||||||
@ -339,8 +354,16 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
<Textarea label={t('common.description')} value={form.description} onValueChange={onDescChange} />
|
<Textarea
|
||||||
<Textarea label={t('common.prompt')} value={form.instructions} onValueChange={onInstChange} />
|
label={t('common.description')}
|
||||||
|
value={form.description ?? ''}
|
||||||
|
onValueChange={onDescChange}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label={t('common.prompt')}
|
||||||
|
value={form.instructions ?? ''}
|
||||||
|
onValueChange={onInstChange}
|
||||||
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter className="w-full">
|
<ModalFooter className="w-full">
|
||||||
<Button onPress={onClose}>{t('common.close')}</Button>
|
<Button onPress={onClose}>{t('common.close')}</Button>
|
||||||
@ -356,11 +379,3 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
|
|||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAvatar = (type: AgentType) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'claude-code':
|
|
||||||
return ClaudeIcon
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { AgentBase } from '@renderer/types'
|
import { AgentBase } from '@renderer/types'
|
||||||
|
|
||||||
// base agent config. no default config for now.
|
// base agent config. no default config for now.
|
||||||
const DEFAULT_AGENT_CONFIG: Omit<AgentBase, 'model'> = {} as const
|
const DEFAULT_AGENT_CONFIG: Omit<AgentBase, 'model'> = {
|
||||||
|
accessible_paths: []
|
||||||
|
} as const
|
||||||
|
|
||||||
// no default config for now.
|
// no default config for now.
|
||||||
export const DEFAULT_CLAUDE_CODE_CONFIG: Omit<AgentBase, 'model'> = {
|
export const DEFAULT_CLAUDE_CODE_CONFIG: Omit<AgentBase, 'model'> = {
|
||||||
|
|||||||
@ -6,8 +6,9 @@ export const useUpdateAgent = () => {
|
|||||||
|
|
||||||
// TODO: use api
|
// TODO: use api
|
||||||
return useMutation({
|
return useMutation({
|
||||||
// @ts-expect-error not-implemented
|
mutationFn: async (agentUpdate: Partial<AgentEntity> & { id: string }) => {
|
||||||
mutationFn: async ({}: Partial<AgentEntity> & { id: string }) => {},
|
throw new Error(`useUpdateAgent mutationFn not implemented for agent ${agentUpdate.id}`)
|
||||||
|
},
|
||||||
onSuccess: (updated: AgentEntity) => {
|
onSuccess: (updated: AgentEntity) => {
|
||||||
qc.setQueryData<AgentEntity[]>(['todos'], (old) =>
|
qc.setQueryData<AgentEntity[]>(['todos'], (old) =>
|
||||||
old ? old.map((t) => (t.id === updated.id ? updated : t)) : []
|
old ? old.map((t) => (t.id === updated.id ? updated : t)) : []
|
||||||
|
|||||||
@ -22,13 +22,14 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete }) => {
|
|||||||
// const { agents } = useAgents()
|
// const { agents } = useAgents()
|
||||||
|
|
||||||
const AgentLabel = useCallback(() => {
|
const AgentLabel = useCallback(() => {
|
||||||
|
const displayName = agent.name ?? agent.id
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{agent.avatar && <Avatar className="h-6 w-6" src={agent.avatar} />}
|
<Avatar className="h-6 w-6" name={displayName} />
|
||||||
<span className="text-sm">{agent.name}</span>
|
<span className="text-sm">{displayName}</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}, [agent.avatar, agent.name])
|
}, [agent.id, agent.name])
|
||||||
|
|
||||||
const handleClick = () => logger.debug('not implemented')
|
const handleClick = () => logger.debug('not implemented')
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete }) => {
|
|||||||
<ContextMenu modal={false}>
|
<ContextMenu modal={false}>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<Container onClick={handleClick} className={isActive ? 'active' : ''}>
|
<Container onClick={handleClick} className={isActive ? 'active' : ''}>
|
||||||
<AssistantNameRow className="name" title={agent.name}>
|
<AssistantNameRow className="name" title={agent.name ?? agent.id}>
|
||||||
<AgentLabel />
|
<AgentLabel />
|
||||||
</AssistantNameRow>
|
</AssistantNameRow>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Database entity types for Agent, Session, and SessionMessage
|
* Database entity types for Agent, Session, and SessionMessage
|
||||||
* Shared between main and renderer processes
|
* Shared between main and renderer processes
|
||||||
*/
|
*/
|
||||||
import { TextStreamPart, UIMessageChunk, ModelMessage } from 'ai'
|
import { ModelMessage, TextStreamPart, UIMessageChunk } from 'ai'
|
||||||
export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'
|
export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'
|
||||||
export type SessionMessageRole = ModelMessage['role']
|
export type SessionMessageRole = ModelMessage['role']
|
||||||
export type AgentType = 'claude-code'
|
export type AgentType = 'claude-code'
|
||||||
@ -75,15 +75,14 @@ export interface ListOptions {
|
|||||||
export interface AgentSessionEntity extends AgentBase {
|
export interface AgentSessionEntity extends AgentBase {
|
||||||
id: string
|
id: string
|
||||||
agent_id: string // Primary agent ID for the session
|
agent_id: string // Primary agent ID for the session
|
||||||
|
agent_type: AgentType
|
||||||
// sub_agent_ids?: string[] // Array of sub-agent IDs involved in the session
|
// sub_agent_ids?: string[] // Array of sub-agent IDs involved in the session
|
||||||
|
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateSessionRequest extends AgentBase {
|
export type CreateSessionRequest = AgentBase
|
||||||
agent_id: string // Primary agent ID for the session
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateSessionRequest extends Partial<AgentBase> {}
|
export interface UpdateSessionRequest extends Partial<AgentBase> {}
|
||||||
|
|
||||||
|
|||||||
@ -16,13 +16,14 @@ Content-Type: application/json
|
|||||||
"type": "claude-code",
|
"type": "claude-code",
|
||||||
"model": "anthropic:claude-sonnet-4",
|
"model": "anthropic:claude-sonnet-4",
|
||||||
"description": "An AI assistant specialized in code review and debugging",
|
"description": "An AI assistant specialized in code review and debugging",
|
||||||
"avatar": "https://example.com/avatar.png",
|
|
||||||
"instructions": "You are a helpful coding assistant. Focus on writing clean, maintainable code and providing constructive feedback.",
|
"instructions": "You are a helpful coding assistant. Focus on writing clean, maintainable code and providing constructive feedback.",
|
||||||
"accessible_paths": [
|
"accessible_paths": [
|
||||||
"/tmp/workspace"
|
"/tmp/workspace"
|
||||||
],
|
],
|
||||||
"permission_mode": "acceptEdits",
|
"configuration": {
|
||||||
"max_steps": 10
|
"permission_mode": "acceptEdits",
|
||||||
|
"max_turns": 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
### Get Agent Details
|
### Get Agent Details
|
||||||
@ -42,14 +43,16 @@ Content-Type: application/json
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "Claude Code",
|
"name": "Claude Code",
|
||||||
|
"type": "claude-code",
|
||||||
"model": "anthropic:claude-sonnet-4",
|
"model": "anthropic:claude-sonnet-4",
|
||||||
"description": "An AI assistant specialized in code review and debugging",
|
"description": "An AI assistant specialized in code review and debugging",
|
||||||
"avatar": "https://example.com/avatar.png",
|
|
||||||
"instructions": "You are a helpful coding assistant. Focus on writing clean, maintainable code and providing constructive feedback.",
|
"instructions": "You are a helpful coding assistant. Focus on writing clean, maintainable code and providing constructive feedback.",
|
||||||
"accessible_paths": [
|
"accessible_paths": [
|
||||||
"/tmp/workspace"
|
"/tmp/workspace"
|
||||||
],
|
],
|
||||||
"permission_mode": "acceptEdits",
|
"configuration": {
|
||||||
"max_steps": 10
|
"permission_mode": "acceptEdits",
|
||||||
|
"max_turns": 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
@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_1757947603408_t1y2mbnq4
|
@agent_id=agent_1758084953648_my4oxnbm3
|
||||||
@session_id=session_1757947684264_z2wcwn8t7
|
@session_id=session_1758087299124_2etavjo2x
|
||||||
|
|
||||||
### List Sessions
|
### List Sessions
|
||||||
GET {{host}}/v1/agents/{{agent_id}}/sessions
|
GET {{host}}/v1/agents/{{agent_id}}/sessions
|
||||||
@ -15,29 +15,26 @@ POST {{host}}/v1/agents/{{agent_id}}/sessions
|
|||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{}
|
||||||
"name": "Joke telling Session",
|
|
||||||
"user_goal": "Tell me a funny joke"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get Session Details
|
### Get Session Details
|
||||||
GET {{host}}/v1/agents/{{agent_id}}/sessions/session_1757815260195_eldvompnv
|
GET {{host}}/v1/agents/{{agent_id}}/sessions/{{session_id}}
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
### Delete Session
|
### Delete Session
|
||||||
DELETE {{host}}/v1/agents/{{agent_id}}/sessions/session_1757815245456_tfs6oogl0
|
DELETE {{host}}/v1/agents/{{agent_id}}/sessions/{{session_id}}
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
### Update Session
|
### Update Session
|
||||||
PUT {{host}}/v1/agents/{{agent_id}}/sessions/session_1757815281790_q4yxgdk74
|
PUT {{host}}/v1/agents/{{agent_id}}/sessions/{{session_id}}
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Code Review Session 1",
|
"name": "Code Review Session 1",
|
||||||
"user_goal": "Review the newly implemented feature for bugs and improvements"
|
"instructions": "Review the newly implemented feature for bugs and improvements"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -47,17 +44,5 @@ Authorization: Bearer {{token}}
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"role": "user",
|
|
||||||
"content": "a joke about programmers"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
### Create Session Message Stream
|
|
||||||
POST {{host}}/v1/agents/{{agent_id}}/sessions/{{session_id}}/messages/stream
|
|
||||||
Authorization: Bearer {{token}}
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "a joke about programmers"
|
"content": "a joke about programmers"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user