diff --git a/_mocks/opencsg.com/csghub-server/builder/rpc/mock_AgentHubSvcClient.go b/_mocks/opencsg.com/csghub-server/builder/rpc/mock_AgentHubSvcClient.go new file mode 100644 index 000000000..e4f0ac97f --- /dev/null +++ b/_mocks/opencsg.com/csghub-server/builder/rpc/mock_AgentHubSvcClient.go @@ -0,0 +1,377 @@ +// Code generated by mockery v2.53.0. DO NOT EDIT. + +package rpc + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + rpc "opencsg.com/csghub-server/builder/rpc" + + types "opencsg.com/csghub-server/common/types" +) + +// MockAgentHubSvcClient is an autogenerated mock type for the AgentHubSvcClient type +type MockAgentHubSvcClient struct { + mock.Mock +} + +type MockAgentHubSvcClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockAgentHubSvcClient) EXPECT() *MockAgentHubSvcClient_Expecter { + return &MockAgentHubSvcClient_Expecter{mock: &_m.Mock} +} + +// CreateAgentInstance provides a mock function with given fields: ctx, userUUID, req +func (_m *MockAgentHubSvcClient) CreateAgentInstance(ctx context.Context, userUUID string, req *rpc.CreateAgentInstanceRequest) (*rpc.CreateAgentInstanceResponse, error) { + ret := _m.Called(ctx, userUUID, req) + + if len(ret) == 0 { + panic("no return value specified for CreateAgentInstance") + } + + var r0 *rpc.CreateAgentInstanceResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *rpc.CreateAgentInstanceRequest) (*rpc.CreateAgentInstanceResponse, error)); ok { + return rf(ctx, userUUID, req) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *rpc.CreateAgentInstanceRequest) *rpc.CreateAgentInstanceResponse); ok { + r0 = rf(ctx, userUUID, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.CreateAgentInstanceResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *rpc.CreateAgentInstanceRequest) error); ok { + r1 = rf(ctx, userUUID, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentHubSvcClient_CreateAgentInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAgentInstance' +type MockAgentHubSvcClient_CreateAgentInstance_Call struct { + *mock.Call +} + +// CreateAgentInstance is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - req *rpc.CreateAgentInstanceRequest +func (_e *MockAgentHubSvcClient_Expecter) CreateAgentInstance(ctx interface{}, userUUID interface{}, req interface{}) *MockAgentHubSvcClient_CreateAgentInstance_Call { + return &MockAgentHubSvcClient_CreateAgentInstance_Call{Call: _e.mock.On("CreateAgentInstance", ctx, userUUID, req)} +} + +func (_c *MockAgentHubSvcClient_CreateAgentInstance_Call) Run(run func(ctx context.Context, userUUID string, req *rpc.CreateAgentInstanceRequest)) *MockAgentHubSvcClient_CreateAgentInstance_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*rpc.CreateAgentInstanceRequest)) + }) + return _c +} + +func (_c *MockAgentHubSvcClient_CreateAgentInstance_Call) Return(_a0 *rpc.CreateAgentInstanceResponse, _a1 error) *MockAgentHubSvcClient_CreateAgentInstance_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentHubSvcClient_CreateAgentInstance_Call) RunAndReturn(run func(context.Context, string, *rpc.CreateAgentInstanceRequest) (*rpc.CreateAgentInstanceResponse, error)) *MockAgentHubSvcClient_CreateAgentInstance_Call { + _c.Call.Return(run) + return _c +} + +// DeleteAgentInstance provides a mock function with given fields: ctx, userUUID, contentID +func (_m *MockAgentHubSvcClient) DeleteAgentInstance(ctx context.Context, userUUID string, contentID string) error { + ret := _m.Called(ctx, userUUID, contentID) + + if len(ret) == 0 { + panic("no return value specified for DeleteAgentInstance") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, userUUID, contentID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAgentHubSvcClient_DeleteAgentInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAgentInstance' +type MockAgentHubSvcClient_DeleteAgentInstance_Call struct { + *mock.Call +} + +// DeleteAgentInstance is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - contentID string +func (_e *MockAgentHubSvcClient_Expecter) DeleteAgentInstance(ctx interface{}, userUUID interface{}, contentID interface{}) *MockAgentHubSvcClient_DeleteAgentInstance_Call { + return &MockAgentHubSvcClient_DeleteAgentInstance_Call{Call: _e.mock.On("DeleteAgentInstance", ctx, userUUID, contentID)} +} + +func (_c *MockAgentHubSvcClient_DeleteAgentInstance_Call) Run(run func(ctx context.Context, userUUID string, contentID string)) *MockAgentHubSvcClient_DeleteAgentInstance_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockAgentHubSvcClient_DeleteAgentInstance_Call) Return(_a0 error) *MockAgentHubSvcClient_DeleteAgentInstance_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAgentHubSvcClient_DeleteAgentInstance_Call) RunAndReturn(run func(context.Context, string, string) error) *MockAgentHubSvcClient_DeleteAgentInstance_Call { + _c.Call.Return(run) + return _c +} + +// GetAgentInstances provides a mock function with given fields: ctx, req +func (_m *MockAgentHubSvcClient) GetAgentInstances(ctx context.Context, req *rpc.GetAgentInstancesRequest) (rpc.GetAgentInstancesResponse, error) { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for GetAgentInstances") + } + + var r0 rpc.GetAgentInstancesResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *rpc.GetAgentInstancesRequest) (rpc.GetAgentInstancesResponse, error)); ok { + return rf(ctx, req) + } + if rf, ok := ret.Get(0).(func(context.Context, *rpc.GetAgentInstancesRequest) rpc.GetAgentInstancesResponse); ok { + r0 = rf(ctx, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(rpc.GetAgentInstancesResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *rpc.GetAgentInstancesRequest) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentHubSvcClient_GetAgentInstances_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAgentInstances' +type MockAgentHubSvcClient_GetAgentInstances_Call struct { + *mock.Call +} + +// GetAgentInstances is a helper method to define mock.On call +// - ctx context.Context +// - req *rpc.GetAgentInstancesRequest +func (_e *MockAgentHubSvcClient_Expecter) GetAgentInstances(ctx interface{}, req interface{}) *MockAgentHubSvcClient_GetAgentInstances_Call { + return &MockAgentHubSvcClient_GetAgentInstances_Call{Call: _e.mock.On("GetAgentInstances", ctx, req)} +} + +func (_c *MockAgentHubSvcClient_GetAgentInstances_Call) Run(run func(ctx context.Context, req *rpc.GetAgentInstancesRequest)) *MockAgentHubSvcClient_GetAgentInstances_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*rpc.GetAgentInstancesRequest)) + }) + return _c +} + +func (_c *MockAgentHubSvcClient_GetAgentInstances_Call) Return(_a0 rpc.GetAgentInstancesResponse, _a1 error) *MockAgentHubSvcClient_GetAgentInstances_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentHubSvcClient_GetAgentInstances_Call) RunAndReturn(run func(context.Context, *rpc.GetAgentInstancesRequest) (rpc.GetAgentInstancesResponse, error)) *MockAgentHubSvcClient_GetAgentInstances_Call { + _c.Call.Return(run) + return _c +} + +// RunAgentInstance provides a mock function with given fields: ctx, userUUID, contentID, req +func (_m *MockAgentHubSvcClient) RunAgentInstance(ctx context.Context, userUUID string, contentID string, req *rpc.RunAgentInstanceRequest) (*rpc.RunAgentInstanceResponse, error) { + ret := _m.Called(ctx, userUUID, contentID, req) + + if len(ret) == 0 { + panic("no return value specified for RunAgentInstance") + } + + var r0 *rpc.RunAgentInstanceResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *rpc.RunAgentInstanceRequest) (*rpc.RunAgentInstanceResponse, error)); ok { + return rf(ctx, userUUID, contentID, req) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, *rpc.RunAgentInstanceRequest) *rpc.RunAgentInstanceResponse); ok { + r0 = rf(ctx, userUUID, contentID, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.RunAgentInstanceResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, *rpc.RunAgentInstanceRequest) error); ok { + r1 = rf(ctx, userUUID, contentID, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentHubSvcClient_RunAgentInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunAgentInstance' +type MockAgentHubSvcClient_RunAgentInstance_Call struct { + *mock.Call +} + +// RunAgentInstance is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - contentID string +// - req *rpc.RunAgentInstanceRequest +func (_e *MockAgentHubSvcClient_Expecter) RunAgentInstance(ctx interface{}, userUUID interface{}, contentID interface{}, req interface{}) *MockAgentHubSvcClient_RunAgentInstance_Call { + return &MockAgentHubSvcClient_RunAgentInstance_Call{Call: _e.mock.On("RunAgentInstance", ctx, userUUID, contentID, req)} +} + +func (_c *MockAgentHubSvcClient_RunAgentInstance_Call) Run(run func(ctx context.Context, userUUID string, contentID string, req *rpc.RunAgentInstanceRequest)) *MockAgentHubSvcClient_RunAgentInstance_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*rpc.RunAgentInstanceRequest)) + }) + return _c +} + +func (_c *MockAgentHubSvcClient_RunAgentInstance_Call) Return(_a0 *rpc.RunAgentInstanceResponse, _a1 error) *MockAgentHubSvcClient_RunAgentInstance_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentHubSvcClient_RunAgentInstance_Call) RunAndReturn(run func(context.Context, string, string, *rpc.RunAgentInstanceRequest) (*rpc.RunAgentInstanceResponse, error)) *MockAgentHubSvcClient_RunAgentInstance_Call { + _c.Call.Return(run) + return _c +} + +// RunAgentInstanceStream provides a mock function with given fields: ctx, userUUID, contentID, req +func (_m *MockAgentHubSvcClient) RunAgentInstanceStream(ctx context.Context, userUUID string, contentID string, req *rpc.RunAgentInstanceRequest) (<-chan types.AgentStreamEvent, error) { + ret := _m.Called(ctx, userUUID, contentID, req) + + if len(ret) == 0 { + panic("no return value specified for RunAgentInstanceStream") + } + + var r0 <-chan types.AgentStreamEvent + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *rpc.RunAgentInstanceRequest) (<-chan types.AgentStreamEvent, error)); ok { + return rf(ctx, userUUID, contentID, req) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, *rpc.RunAgentInstanceRequest) <-chan types.AgentStreamEvent); ok { + r0 = rf(ctx, userUUID, contentID, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan types.AgentStreamEvent) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, *rpc.RunAgentInstanceRequest) error); ok { + r1 = rf(ctx, userUUID, contentID, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentHubSvcClient_RunAgentInstanceStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunAgentInstanceStream' +type MockAgentHubSvcClient_RunAgentInstanceStream_Call struct { + *mock.Call +} + +// RunAgentInstanceStream is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - contentID string +// - req *rpc.RunAgentInstanceRequest +func (_e *MockAgentHubSvcClient_Expecter) RunAgentInstanceStream(ctx interface{}, userUUID interface{}, contentID interface{}, req interface{}) *MockAgentHubSvcClient_RunAgentInstanceStream_Call { + return &MockAgentHubSvcClient_RunAgentInstanceStream_Call{Call: _e.mock.On("RunAgentInstanceStream", ctx, userUUID, contentID, req)} +} + +func (_c *MockAgentHubSvcClient_RunAgentInstanceStream_Call) Run(run func(ctx context.Context, userUUID string, contentID string, req *rpc.RunAgentInstanceRequest)) *MockAgentHubSvcClient_RunAgentInstanceStream_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*rpc.RunAgentInstanceRequest)) + }) + return _c +} + +func (_c *MockAgentHubSvcClient_RunAgentInstanceStream_Call) Return(_a0 <-chan types.AgentStreamEvent, _a1 error) *MockAgentHubSvcClient_RunAgentInstanceStream_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentHubSvcClient_RunAgentInstanceStream_Call) RunAndReturn(run func(context.Context, string, string, *rpc.RunAgentInstanceRequest) (<-chan types.AgentStreamEvent, error)) *MockAgentHubSvcClient_RunAgentInstanceStream_Call { + _c.Call.Return(run) + return _c +} + +// UpdateAgentInstance provides a mock function with given fields: ctx, userUUID, contentID, req +func (_m *MockAgentHubSvcClient) UpdateAgentInstance(ctx context.Context, userUUID string, contentID string, req *rpc.UpdateAgentInstanceRequest) error { + ret := _m.Called(ctx, userUUID, contentID, req) + + if len(ret) == 0 { + panic("no return value specified for UpdateAgentInstance") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *rpc.UpdateAgentInstanceRequest) error); ok { + r0 = rf(ctx, userUUID, contentID, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAgentHubSvcClient_UpdateAgentInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAgentInstance' +type MockAgentHubSvcClient_UpdateAgentInstance_Call struct { + *mock.Call +} + +// UpdateAgentInstance is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - contentID string +// - req *rpc.UpdateAgentInstanceRequest +func (_e *MockAgentHubSvcClient_Expecter) UpdateAgentInstance(ctx interface{}, userUUID interface{}, contentID interface{}, req interface{}) *MockAgentHubSvcClient_UpdateAgentInstance_Call { + return &MockAgentHubSvcClient_UpdateAgentInstance_Call{Call: _e.mock.On("UpdateAgentInstance", ctx, userUUID, contentID, req)} +} + +func (_c *MockAgentHubSvcClient_UpdateAgentInstance_Call) Run(run func(ctx context.Context, userUUID string, contentID string, req *rpc.UpdateAgentInstanceRequest)) *MockAgentHubSvcClient_UpdateAgentInstance_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*rpc.UpdateAgentInstanceRequest)) + }) + return _c +} + +func (_c *MockAgentHubSvcClient_UpdateAgentInstance_Call) Return(_a0 error) *MockAgentHubSvcClient_UpdateAgentInstance_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAgentHubSvcClient_UpdateAgentInstance_Call) RunAndReturn(run func(context.Context, string, string, *rpc.UpdateAgentInstanceRequest) error) *MockAgentHubSvcClient_UpdateAgentInstance_Call { + _c.Call.Return(run) + return _c +} + +// NewMockAgentHubSvcClient creates a new instance of MockAgentHubSvcClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockAgentHubSvcClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockAgentHubSvcClient { + mock := &MockAgentHubSvcClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/_mocks/opencsg.com/csghub-server/builder/store/database/mock_AgentInstanceStore.go b/_mocks/opencsg.com/csghub-server/builder/store/database/mock_AgentInstanceStore.go new file mode 100644 index 000000000..603e27e8d --- /dev/null +++ b/_mocks/opencsg.com/csghub-server/builder/store/database/mock_AgentInstanceStore.go @@ -0,0 +1,613 @@ +// Code generated by mockery v2.53.0. DO NOT EDIT. + +package database + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + database "opencsg.com/csghub-server/builder/store/database" + + types "opencsg.com/csghub-server/common/types" +) + +// MockAgentInstanceStore is an autogenerated mock type for the AgentInstanceStore type +type MockAgentInstanceStore struct { + mock.Mock +} + +type MockAgentInstanceStore_Expecter struct { + mock *mock.Mock +} + +func (_m *MockAgentInstanceStore) EXPECT() *MockAgentInstanceStore_Expecter { + return &MockAgentInstanceStore_Expecter{mock: &_m.Mock} +} + +// CountByUserAndType provides a mock function with given fields: ctx, userUUID, instanceType +func (_m *MockAgentInstanceStore) CountByUserAndType(ctx context.Context, userUUID string, instanceType string) (int, error) { + ret := _m.Called(ctx, userUUID, instanceType) + + if len(ret) == 0 { + panic("no return value specified for CountByUserAndType") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (int, error)); ok { + return rf(ctx, userUUID, instanceType) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) int); ok { + r0 = rf(ctx, userUUID, instanceType) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, userUUID, instanceType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_CountByUserAndType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountByUserAndType' +type MockAgentInstanceStore_CountByUserAndType_Call struct { + *mock.Call +} + +// CountByUserAndType is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - instanceType string +func (_e *MockAgentInstanceStore_Expecter) CountByUserAndType(ctx interface{}, userUUID interface{}, instanceType interface{}) *MockAgentInstanceStore_CountByUserAndType_Call { + return &MockAgentInstanceStore_CountByUserAndType_Call{Call: _e.mock.On("CountByUserAndType", ctx, userUUID, instanceType)} +} + +func (_c *MockAgentInstanceStore_CountByUserAndType_Call) Run(run func(ctx context.Context, userUUID string, instanceType string)) *MockAgentInstanceStore_CountByUserAndType_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_CountByUserAndType_Call) Return(_a0 int, _a1 error) *MockAgentInstanceStore_CountByUserAndType_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_CountByUserAndType_Call) RunAndReturn(run func(context.Context, string, string) (int, error)) *MockAgentInstanceStore_CountByUserAndType_Call { + _c.Call.Return(run) + return _c +} + +// Create provides a mock function with given fields: ctx, instance +func (_m *MockAgentInstanceStore) Create(ctx context.Context, instance *database.AgentInstance) (*database.AgentInstance, error) { + ret := _m.Called(ctx, instance) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 *database.AgentInstance + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *database.AgentInstance) (*database.AgentInstance, error)); ok { + return rf(ctx, instance) + } + if rf, ok := ret.Get(0).(func(context.Context, *database.AgentInstance) *database.AgentInstance); ok { + r0 = rf(ctx, instance) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*database.AgentInstance) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *database.AgentInstance) error); ok { + r1 = rf(ctx, instance) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type MockAgentInstanceStore_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - instance *database.AgentInstance +func (_e *MockAgentInstanceStore_Expecter) Create(ctx interface{}, instance interface{}) *MockAgentInstanceStore_Create_Call { + return &MockAgentInstanceStore_Create_Call{Call: _e.mock.On("Create", ctx, instance)} +} + +func (_c *MockAgentInstanceStore_Create_Call) Run(run func(ctx context.Context, instance *database.AgentInstance)) *MockAgentInstanceStore_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*database.AgentInstance)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_Create_Call) Return(_a0 *database.AgentInstance, _a1 error) *MockAgentInstanceStore_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_Create_Call) RunAndReturn(run func(context.Context, *database.AgentInstance) (*database.AgentInstance, error)) *MockAgentInstanceStore_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *MockAgentInstanceStore) Delete(ctx context.Context, id int64) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAgentInstanceStore_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type MockAgentInstanceStore_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id int64 +func (_e *MockAgentInstanceStore_Expecter) Delete(ctx interface{}, id interface{}) *MockAgentInstanceStore_Delete_Call { + return &MockAgentInstanceStore_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *MockAgentInstanceStore_Delete_Call) Run(run func(ctx context.Context, id int64)) *MockAgentInstanceStore_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_Delete_Call) Return(_a0 error) *MockAgentInstanceStore_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAgentInstanceStore_Delete_Call) RunAndReturn(run func(context.Context, int64) error) *MockAgentInstanceStore_Delete_Call { + _c.Call.Return(run) + return _c +} + +// FindByContentID provides a mock function with given fields: ctx, instanceType, contentID +func (_m *MockAgentInstanceStore) FindByContentID(ctx context.Context, instanceType string, contentID string) (*database.AgentInstance, error) { + ret := _m.Called(ctx, instanceType, contentID) + + if len(ret) == 0 { + panic("no return value specified for FindByContentID") + } + + var r0 *database.AgentInstance + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*database.AgentInstance, error)); ok { + return rf(ctx, instanceType, contentID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *database.AgentInstance); ok { + r0 = rf(ctx, instanceType, contentID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*database.AgentInstance) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, instanceType, contentID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_FindByContentID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByContentID' +type MockAgentInstanceStore_FindByContentID_Call struct { + *mock.Call +} + +// FindByContentID is a helper method to define mock.On call +// - ctx context.Context +// - instanceType string +// - contentID string +func (_e *MockAgentInstanceStore_Expecter) FindByContentID(ctx interface{}, instanceType interface{}, contentID interface{}) *MockAgentInstanceStore_FindByContentID_Call { + return &MockAgentInstanceStore_FindByContentID_Call{Call: _e.mock.On("FindByContentID", ctx, instanceType, contentID)} +} + +func (_c *MockAgentInstanceStore_FindByContentID_Call) Run(run func(ctx context.Context, instanceType string, contentID string)) *MockAgentInstanceStore_FindByContentID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_FindByContentID_Call) Return(_a0 *database.AgentInstance, _a1 error) *MockAgentInstanceStore_FindByContentID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_FindByContentID_Call) RunAndReturn(run func(context.Context, string, string) (*database.AgentInstance, error)) *MockAgentInstanceStore_FindByContentID_Call { + _c.Call.Return(run) + return _c +} + +// FindByID provides a mock function with given fields: ctx, id +func (_m *MockAgentInstanceStore) FindByID(ctx context.Context, id int64) (*database.AgentInstance, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for FindByID") + } + + var r0 *database.AgentInstance + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (*database.AgentInstance, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) *database.AgentInstance); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*database.AgentInstance) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_FindByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByID' +type MockAgentInstanceStore_FindByID_Call struct { + *mock.Call +} + +// FindByID is a helper method to define mock.On call +// - ctx context.Context +// - id int64 +func (_e *MockAgentInstanceStore_Expecter) FindByID(ctx interface{}, id interface{}) *MockAgentInstanceStore_FindByID_Call { + return &MockAgentInstanceStore_FindByID_Call{Call: _e.mock.On("FindByID", ctx, id)} +} + +func (_c *MockAgentInstanceStore_FindByID_Call) Run(run func(ctx context.Context, id int64)) *MockAgentInstanceStore_FindByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_FindByID_Call) Return(_a0 *database.AgentInstance, _a1 error) *MockAgentInstanceStore_FindByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_FindByID_Call) RunAndReturn(run func(context.Context, int64) (*database.AgentInstance, error)) *MockAgentInstanceStore_FindByID_Call { + _c.Call.Return(run) + return _c +} + +// FindByIDs provides a mock function with given fields: ctx, ids +func (_m *MockAgentInstanceStore) FindByIDs(ctx context.Context, ids []int64) ([]database.AgentInstance, error) { + ret := _m.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for FindByIDs") + } + + var r0 []database.AgentInstance + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []int64) ([]database.AgentInstance, error)); ok { + return rf(ctx, ids) + } + if rf, ok := ret.Get(0).(func(context.Context, []int64) []database.AgentInstance); ok { + r0 = rf(ctx, ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]database.AgentInstance) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []int64) error); ok { + r1 = rf(ctx, ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_FindByIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByIDs' +type MockAgentInstanceStore_FindByIDs_Call struct { + *mock.Call +} + +// FindByIDs is a helper method to define mock.On call +// - ctx context.Context +// - ids []int64 +func (_e *MockAgentInstanceStore_Expecter) FindByIDs(ctx interface{}, ids interface{}) *MockAgentInstanceStore_FindByIDs_Call { + return &MockAgentInstanceStore_FindByIDs_Call{Call: _e.mock.On("FindByIDs", ctx, ids)} +} + +func (_c *MockAgentInstanceStore_FindByIDs_Call) Run(run func(ctx context.Context, ids []int64)) *MockAgentInstanceStore_FindByIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]int64)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_FindByIDs_Call) Return(_a0 []database.AgentInstance, _a1 error) *MockAgentInstanceStore_FindByIDs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_FindByIDs_Call) RunAndReturn(run func(context.Context, []int64) ([]database.AgentInstance, error)) *MockAgentInstanceStore_FindByIDs_Call { + _c.Call.Return(run) + return _c +} + +// IsInstanceExistsByContentID provides a mock function with given fields: ctx, instanceType, contentID +func (_m *MockAgentInstanceStore) IsInstanceExistsByContentID(ctx context.Context, instanceType string, contentID string) (bool, error) { + ret := _m.Called(ctx, instanceType, contentID) + + if len(ret) == 0 { + panic("no return value specified for IsInstanceExistsByContentID") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { + return rf(ctx, instanceType, contentID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { + r0 = rf(ctx, instanceType, contentID) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, instanceType, contentID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_IsInstanceExistsByContentID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsInstanceExistsByContentID' +type MockAgentInstanceStore_IsInstanceExistsByContentID_Call struct { + *mock.Call +} + +// IsInstanceExistsByContentID is a helper method to define mock.On call +// - ctx context.Context +// - instanceType string +// - contentID string +func (_e *MockAgentInstanceStore_Expecter) IsInstanceExistsByContentID(ctx interface{}, instanceType interface{}, contentID interface{}) *MockAgentInstanceStore_IsInstanceExistsByContentID_Call { + return &MockAgentInstanceStore_IsInstanceExistsByContentID_Call{Call: _e.mock.On("IsInstanceExistsByContentID", ctx, instanceType, contentID)} +} + +func (_c *MockAgentInstanceStore_IsInstanceExistsByContentID_Call) Run(run func(ctx context.Context, instanceType string, contentID string)) *MockAgentInstanceStore_IsInstanceExistsByContentID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_IsInstanceExistsByContentID_Call) Return(_a0 bool, _a1 error) *MockAgentInstanceStore_IsInstanceExistsByContentID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_IsInstanceExistsByContentID_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockAgentInstanceStore_IsInstanceExistsByContentID_Call { + _c.Call.Return(run) + return _c +} + +// IsInstanceNameExists provides a mock function with given fields: ctx, userUUID, instanceName +func (_m *MockAgentInstanceStore) IsInstanceNameExists(ctx context.Context, userUUID string, instanceName string) (bool, error) { + ret := _m.Called(ctx, userUUID, instanceName) + + if len(ret) == 0 { + panic("no return value specified for IsInstanceNameExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { + return rf(ctx, userUUID, instanceName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { + r0 = rf(ctx, userUUID, instanceName) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, userUUID, instanceName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAgentInstanceStore_IsInstanceNameExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsInstanceNameExists' +type MockAgentInstanceStore_IsInstanceNameExists_Call struct { + *mock.Call +} + +// IsInstanceNameExists is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - instanceName string +func (_e *MockAgentInstanceStore_Expecter) IsInstanceNameExists(ctx interface{}, userUUID interface{}, instanceName interface{}) *MockAgentInstanceStore_IsInstanceNameExists_Call { + return &MockAgentInstanceStore_IsInstanceNameExists_Call{Call: _e.mock.On("IsInstanceNameExists", ctx, userUUID, instanceName)} +} + +func (_c *MockAgentInstanceStore_IsInstanceNameExists_Call) Run(run func(ctx context.Context, userUUID string, instanceName string)) *MockAgentInstanceStore_IsInstanceNameExists_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_IsInstanceNameExists_Call) Return(_a0 bool, _a1 error) *MockAgentInstanceStore_IsInstanceNameExists_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAgentInstanceStore_IsInstanceNameExists_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockAgentInstanceStore_IsInstanceNameExists_Call { + _c.Call.Return(run) + return _c +} + +// ListByUserUUID provides a mock function with given fields: ctx, userUUID, filter, per, page +func (_m *MockAgentInstanceStore) ListByUserUUID(ctx context.Context, userUUID string, filter types.AgentInstanceFilter, per int, page int) ([]database.AgentInstance, int, error) { + ret := _m.Called(ctx, userUUID, filter, per, page) + + if len(ret) == 0 { + panic("no return value specified for ListByUserUUID") + } + + var r0 []database.AgentInstance + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, types.AgentInstanceFilter, int, int) ([]database.AgentInstance, int, error)); ok { + return rf(ctx, userUUID, filter, per, page) + } + if rf, ok := ret.Get(0).(func(context.Context, string, types.AgentInstanceFilter, int, int) []database.AgentInstance); ok { + r0 = rf(ctx, userUUID, filter, per, page) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]database.AgentInstance) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, types.AgentInstanceFilter, int, int) int); ok { + r1 = rf(ctx, userUUID, filter, per, page) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(context.Context, string, types.AgentInstanceFilter, int, int) error); ok { + r2 = rf(ctx, userUUID, filter, per, page) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockAgentInstanceStore_ListByUserUUID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListByUserUUID' +type MockAgentInstanceStore_ListByUserUUID_Call struct { + *mock.Call +} + +// ListByUserUUID is a helper method to define mock.On call +// - ctx context.Context +// - userUUID string +// - filter types.AgentInstanceFilter +// - per int +// - page int +func (_e *MockAgentInstanceStore_Expecter) ListByUserUUID(ctx interface{}, userUUID interface{}, filter interface{}, per interface{}, page interface{}) *MockAgentInstanceStore_ListByUserUUID_Call { + return &MockAgentInstanceStore_ListByUserUUID_Call{Call: _e.mock.On("ListByUserUUID", ctx, userUUID, filter, per, page)} +} + +func (_c *MockAgentInstanceStore_ListByUserUUID_Call) Run(run func(ctx context.Context, userUUID string, filter types.AgentInstanceFilter, per int, page int)) *MockAgentInstanceStore_ListByUserUUID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(types.AgentInstanceFilter), args[3].(int), args[4].(int)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_ListByUserUUID_Call) Return(_a0 []database.AgentInstance, _a1 int, _a2 error) *MockAgentInstanceStore_ListByUserUUID_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockAgentInstanceStore_ListByUserUUID_Call) RunAndReturn(run func(context.Context, string, types.AgentInstanceFilter, int, int) ([]database.AgentInstance, int, error)) *MockAgentInstanceStore_ListByUserUUID_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function with given fields: ctx, instance +func (_m *MockAgentInstanceStore) Update(ctx context.Context, instance *database.AgentInstance) error { + ret := _m.Called(ctx, instance) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *database.AgentInstance) error); ok { + r0 = rf(ctx, instance) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAgentInstanceStore_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type MockAgentInstanceStore_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - ctx context.Context +// - instance *database.AgentInstance +func (_e *MockAgentInstanceStore_Expecter) Update(ctx interface{}, instance interface{}) *MockAgentInstanceStore_Update_Call { + return &MockAgentInstanceStore_Update_Call{Call: _e.mock.On("Update", ctx, instance)} +} + +func (_c *MockAgentInstanceStore_Update_Call) Run(run func(ctx context.Context, instance *database.AgentInstance)) *MockAgentInstanceStore_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*database.AgentInstance)) + }) + return _c +} + +func (_c *MockAgentInstanceStore_Update_Call) Return(_a0 error) *MockAgentInstanceStore_Update_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAgentInstanceStore_Update_Call) RunAndReturn(run func(context.Context, *database.AgentInstance) error) *MockAgentInstanceStore_Update_Call { + _c.Call.Return(run) + return _c +} + +// NewMockAgentInstanceStore creates a new instance of MockAgentInstanceStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockAgentInstanceStore(t interface { + mock.TestingT + Cleanup(func()) +}) *MockAgentInstanceStore { + mock := &MockAgentInstanceStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/builder/rpc/agenthub_svc_client.go b/builder/rpc/agenthub_svc_client.go new file mode 100644 index 000000000..21047d71c --- /dev/null +++ b/builder/rpc/agenthub_svc_client.go @@ -0,0 +1,489 @@ +package rpc + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "strconv" + "strings" + + "opencsg.com/csghub-server/common/errorx" + "opencsg.com/csghub-server/common/types" +) + +type AgentHubSvcClient interface { + CreateAgentInstance(ctx context.Context, userUUID string, req *CreateAgentInstanceRequest) (*CreateAgentInstanceResponse, error) + DeleteAgentInstance(ctx context.Context, userUUID string, contentID string) error + UpdateAgentInstance(ctx context.Context, userUUID string, contentID string, req *UpdateAgentInstanceRequest) error + GetAgentInstances(ctx context.Context, req *GetAgentInstancesRequest) (GetAgentInstancesResponse, error) + RunAgentInstance(ctx context.Context, userUUID string, contentID string, req *RunAgentInstanceRequest) (*RunAgentInstanceResponse, error) + RunAgentInstanceStream(ctx context.Context, userUUID string, contentID string, req *RunAgentInstanceRequest) (<-chan types.AgentStreamEvent, error) +} + +type CreateAgentInstanceRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Data json.RawMessage `json:"data"` +} + +type CreateAgentInstanceResponse AgentInstance + +type GetAgentInstancesRequest struct { + IDs []string `json:"ids"` + UserUUID string `json:"user_uuid"` +} + +type GetAgentInstancesResponse []*AgentInstance + +type DeleteAgentInstanceRequest struct { + IDs []string `json:"ids"` +} + +type DeleteAgentInstanceResponse struct { + IDs []string `json:"ids"` + Total int `json:"total"` +} + +type UpdateAgentInstanceRequest struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + FolderID string `json:"folder_id,omitempty"` + EndpointName string `json:"endpoint_name,omitempty"` + MCPEnabled *bool `json:"mcp_enabled,omitempty"` + Locked *bool `json:"locked,omitempty"` + ActionName string `json:"action_name,omitempty"` + ActionDescription string `json:"action_description,omitempty"` + AccessType string `json:"access_type,omitempty"` + FSPath string `json:"fs_path,omitempty"` +} + +type RunAgentInstanceRequest struct { + InputValue string `json:"input_value"` + InputType string `json:"input_type"` + OutputType string `json:"output_type"` + Tweaks json.RawMessage `json:"tweaks"` + SessionID string `json:"session_id"` + Stream bool `json:"stream"` +} + +type RunAgentInstanceResponse struct { + SessionID string `json:"session_id"` + Outputs []Outputs `json:"outputs"` +} + +type Outputs struct { + Outputs []InnerOutput `json:"outputs,omitempty"` // camada extra para stream=true + Results *Results `json:"results,omitempty"` // stream=false +} + +type InnerOutput struct { + Results *Results `json:"results,omitempty"` +} + +type Results struct { + Message Message `json:"message"` +} + +type Message struct { + Timestamp string `json:"timestamp"` + Sender string `json:"sender"` + SenderName string `json:"sender_name"` + TextKey string `json:"text_key"` + Text string `json:"text"` +} + +// TokenData holds the data for an event of type "token". +type TokenData struct { + Chunk string `json:"chunk"` +} + +// EndData holds the final result from an event of type "end". +// The 'Result' field conveniently matches our target RunAgentInstanceResponse struct. +type EndData struct { + Result RunAgentInstanceResponse `json:"result"` +} + +/* + { + "id": "new-flow-id", + "name": "New Flow Name", + "description": "Flow description", + "data": { + // Flow graph data + }, + "hasIO": true, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "user_id": "user-uuid", + "folder_id": "folder-uuid" + } +*/ +type AgentInstance struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Data json.RawMessage `json:"data"` + HasIO bool `json:"hasIO"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + UserUUID string `json:"user_id"` // user uuid + FolderID string `json:"folder_id"` +} + +type AgentHubSvcClientImpl struct { + hc *HttpClient + token string +} + +func NewAgentHubSvcClientImpl(endpoint string, token string, opts ...RequestOption) AgentHubSvcClient { + return &AgentHubSvcClientImpl{ + hc: NewHttpClient(strings.TrimSuffix(endpoint, "/"), opts...), + token: token, + } +} + +// POST /api/v1/opencsg/flows/ +func (c *AgentHubSvcClientImpl) CreateAgentInstance(ctx context.Context, userUUID string, req *CreateAgentInstanceRequest) (*CreateAgentInstanceResponse, error) { + if req == nil { + return nil, errorx.BadRequest(errors.New("create agent instance request is nil"), nil) + } + rpcErrorCtx := map[string]any{ + "user_uuid": userUUID, + "service": "agenthub", + "api": "/api/v1/opencsg/flows/", + } + var resp CreateAgentInstanceResponse + var buf io.Reader + + jsonData, err := json.Marshal(req) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + buf = bytes.NewBuffer(jsonData) + path := c.hc.endpoint + "/api/v1/opencsg/flows/?token=" + c.token + hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, path, buf) + if err != nil { + return nil, errorx.InternalServerError(err, nil) + } + hreq.Header.Set("Content-Type", "application/json") + hreq.Header.Set("user_uuid", userUUID) + + hresp, err := c.hc.Do(hreq) + if err != nil { + return nil, errorx.RemoteSvcFail(errors.New("failed to create agent instance in agenthub"), rpcErrorCtx) + } + defer hresp.Body.Close() + if hresp.StatusCode != http.StatusOK { + return nil, errorx.RemoteSvcFail(errors.New("failed to create agent instance in agenthub"), rpcErrorCtx) + } + + body, err := io.ReadAll(hresp.Body) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + err = json.Unmarshal(body, &resp) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + return &resp, nil +} + +// POST /api/v1/opencsg/flows/query +func (c *AgentHubSvcClientImpl) GetAgentInstances(ctx context.Context, req *GetAgentInstancesRequest) (GetAgentInstancesResponse, error) { + if req == nil { + return nil, errorx.BadRequest(errors.New("get agent instances request is nil"), nil) + } + rpcErrorCtx := map[string]any{ + "user_uuid": req.UserUUID, + "service": "agenthub", + "api": "/api/v1/opencsg/flows/query", + } + + var resp GetAgentInstancesResponse + var buf io.Reader + jsonData, err := json.Marshal(req) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + buf = bytes.NewBuffer(jsonData) + path := c.hc.endpoint + "/api/v1/opencsg/flows/query?token=" + c.token + hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, path, buf) + if err != nil { + return nil, errorx.InternalServerError(err, nil) + } + hreq.Header.Set("Content-Type", "application/json") + hreq.Header.Set("user_uuid", req.UserUUID) + + hresp, err := c.hc.Do(hreq) + if err != nil { + return nil, errorx.RemoteSvcFail(errors.New("failed to get agent instance from agenthub"), rpcErrorCtx) + } + defer hresp.Body.Close() + if hresp.StatusCode != http.StatusOK { + return nil, errorx.RemoteSvcFail(errors.New("failed to get agent instance from agenthub"), rpcErrorCtx) + } + body, err := io.ReadAll(hresp.Body) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + err = json.Unmarshal(body, &resp) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + return resp, nil +} + +// POST /api/v1/opencsg/flows/delete +func (c *AgentHubSvcClientImpl) DeleteAgentInstance(ctx context.Context, userUUID string, contentID string) error { + rpcErrorCtx := map[string]any{ + "user_uuid": userUUID, + "service": "agenthub", + "api": "/api/v1/opencsg/flows/delete", + } + var resp DeleteAgentInstanceResponse + + req := DeleteAgentInstanceRequest{ + IDs: []string{contentID}, + } + jsonData, err := json.Marshal(req) + if err != nil { + return errorx.InternalServerError(err, rpcErrorCtx) + } + buf := bytes.NewBuffer(jsonData) + path := c.hc.endpoint + "/api/v1/opencsg/flows/delete?token=" + c.token + hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, path, buf) + if err != nil { + return errorx.InternalServerError(err, rpcErrorCtx) + } + + hreq.Header.Set("Content-Type", "application/json") + hreq.Header.Set("user_uuid", userUUID) + + hresp, err := c.hc.Do(hreq) + if err != nil { + return errorx.RemoteSvcFail(errors.New("failed to delete agent instance in agenthub"), rpcErrorCtx) + } + defer hresp.Body.Close() + + if hresp.StatusCode != http.StatusOK { + return errorx.RemoteSvcFail(errors.New("failed to delete agent instance in agenthub, status code: "+strconv.Itoa(hresp.StatusCode)), rpcErrorCtx) + } + + body, err := io.ReadAll(hresp.Body) + if err != nil { + return errorx.InternalServerError(err, rpcErrorCtx) + } + if err := json.Unmarshal(body, &resp); err != nil { + return errorx.RemoteSvcFail(errors.New("failed to delete agent instance in agenthub, unmarshal response error: "+err.Error()), rpcErrorCtx) + } + + if resp.Total != 1 { + return errorx.RemoteSvcFail(errors.New("failed to delete agent instance in agenthub, total: "+strconv.Itoa(resp.Total)), rpcErrorCtx) + } + + if len(resp.IDs) == 0 { + return errorx.RemoteSvcFail(errors.New("failed to delete agent instance in agenthub, response IDs is empty"), rpcErrorCtx) + } + + if resp.IDs[0] != contentID { + return errorx.RemoteSvcFail(errors.New("failed to delete agent instance in agenthub, content ID mismatch: "+contentID+" != "+resp.IDs[0]), rpcErrorCtx) + } + return nil +} + +// PATCH /api/v1/opencsg/flows/{id} +func (c *AgentHubSvcClientImpl) UpdateAgentInstance(ctx context.Context, userUUID string, contentID string, req *UpdateAgentInstanceRequest) error { + if req == nil { + return errorx.BadRequest(errors.New("update agent instance request is nil"), nil) + } + rpcErrorCtx := map[string]any{ + "user_uuid": userUUID, + "service": "agenthub", + "api": "/api/v1/opencsg/flows/" + contentID, + } + var buf io.Reader + jsonData, err := json.Marshal(req) + if err != nil { + return errorx.InternalServerError(err, rpcErrorCtx) + } + buf = bytes.NewBuffer(jsonData) + path := c.hc.endpoint + "/api/v1/opencsg/flows/" + contentID + "?token=" + c.token + hreq, err := http.NewRequestWithContext(ctx, http.MethodPatch, path, buf) + if err != nil { + return errorx.InternalServerError(err, rpcErrorCtx) + } + hreq.Header.Set("Content-Type", "application/json") + hreq.Header.Set("user_uuid", userUUID) + + hresp, err := c.hc.Do(hreq) + if err != nil { + return errorx.RemoteSvcFail(fmt.Errorf("failed to update agent instance in agenthub: %w", err), rpcErrorCtx) + } + defer hresp.Body.Close() + if hresp.StatusCode != http.StatusOK { + return errorx.RemoteSvcFail(fmt.Errorf("failed to update agent instance in agenthub, status code: %d", hresp.StatusCode), rpcErrorCtx) + } + + return nil +} + +// POST /api/v1/opencsg/run/{id}?stream=false +func (c *AgentHubSvcClientImpl) RunAgentInstance(ctx context.Context, userUUID string, instanceID string, req *RunAgentInstanceRequest) (*RunAgentInstanceResponse, error) { + if req == nil { + return nil, errorx.BadRequest(errors.New("run agent instance request is nil"), nil) + } + rpcErrorCtx := map[string]any{ + "user_uuid": userUUID, + "service": "agenthub", + "api": "/api/v1/opencsg/run/" + instanceID + "?stream=false", + } + var buf io.Reader + jsonData, err := json.Marshal(req) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + buf = bytes.NewBuffer(jsonData) + path := c.hc.endpoint + "/api/v1/opencsg/run/" + instanceID + "?stream=false&token=" + c.token + hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, path, buf) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + hreq.Header.Set("Content-Type", "application/json") + hreq.Header.Set("user_uuid", userUUID) + + hresp, err := c.hc.Do(hreq) + if err != nil { + return nil, errorx.RemoteSvcFail(errors.New("failed to run agent instance in agenthub"), rpcErrorCtx) + } + defer hresp.Body.Close() + if hresp.StatusCode != http.StatusOK { + return nil, errorx.RemoteSvcFail(errors.New("failed to run agent instance in agenthub"), rpcErrorCtx) + } + + // handle non-stream response + body, err := io.ReadAll(hresp.Body) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + var resp RunAgentInstanceResponse + err = json.Unmarshal(body, &resp) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + return &resp, nil +} + +// RunAgentInstanceStream runs an agent instance and returns a streaming channel +// POST /api/v1/opencsg/run/{id}?stream=true +func (c *AgentHubSvcClientImpl) RunAgentInstanceStream(ctx context.Context, userUUID string, contentID string, req *RunAgentInstanceRequest) (<-chan types.AgentStreamEvent, error) { + if req == nil { + return nil, errorx.BadRequest(errors.New("run agent instance request is nil"), nil) + } + + rpcErrorCtx := map[string]any{ + "user_uuid": userUUID, + "service": "agenthub", + "api": "/api/v1/opencsg/run/" + contentID + "?stream=true", + } + + var buf io.Reader + jsonData, err := json.Marshal(req) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + buf = bytes.NewBuffer(jsonData) + path := c.hc.endpoint + "/api/v1/opencsg/run/" + contentID + "?stream=true&token=" + c.token + hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, path, buf) + if err != nil { + return nil, errorx.InternalServerError(err, rpcErrorCtx) + } + hreq.Header.Set("Content-Type", "application/json") + hreq.Header.Set("user_uuid", userUUID) + + hresp, err := c.hc.Do(hreq) + if err != nil { + return nil, errorx.RemoteSvcFail(errors.New("failed to run agent instance in agenthub"), rpcErrorCtx) + } + + if hresp.StatusCode != http.StatusOK { + defer hresp.Body.Close() + return nil, errorx.RemoteSvcFail(errors.New("failed to run agent instance in agenthub"), rpcErrorCtx) + } + + // Create a channel for streaming responses + streamChan := make(chan types.AgentStreamEvent, 100) + + // Start a goroutine to handle the streaming response + go func(body io.ReadCloser) { + defer close(streamChan) + defer body.Close() + + scanner := bufio.NewScanner(body) + scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) // allow up to 1MB buffer + for scanner.Scan() { + select { + case <-ctx.Done(): + slog.Debug("stream cancelled", slog.String("session_id", contentID)) + return + default: + } + + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + var streamEvent types.AgentStreamEvent + if err := json.Unmarshal([]byte(line), &streamEvent); err != nil { + slog.Warn("could not unmarshal stream line into event, skipping. session_id: %s, error: %v, line: %s", slog.String("session_id", contentID), slog.Any("error", err), slog.String("line", line)) + continue + } + + // Process the event based on its type + switch streamEvent.Event { + case "token": + sendEvent(ctx, streamChan, streamEvent) + case "add_message": + sendEvent(ctx, streamChan, streamEvent) + case "end": + var endData EndData + if err := json.Unmarshal(streamEvent.Data, &endData); err != nil { + slog.Error("Error: could not unmarshal 'end' event data: %v", slog.String("session_id", contentID), slog.Any("error", err)) + continue + } + sendEvent(ctx, streamChan, streamEvent) + + // extract the message text from the event data, and send it as a separate event "output-message" + if len(endData.Result.Outputs) > 0 && len(endData.Result.Outputs[0].Outputs) > 0 && endData.Result.Outputs[0].Outputs[0].Results != nil && endData.Result.Outputs[0].Outputs[0].Results.Message.Text != "" { + sendEvent(ctx, streamChan, types.AgentStreamEvent{ + Event: "output-message", + Data: []byte(endData.Result.Outputs[0].Outputs[0].Results.Message.Text), + }) + } + default: + slog.Warn("unknown event type", slog.String("session_id", contentID), slog.String("event", streamEvent.Event)) + } + } + + if err := scanner.Err(); err != nil { + slog.Error("scanner error", slog.String("session_id", contentID), slog.Any("error", err)) + } + }(hresp.Body) + + return streamChan, nil +} + +func sendEvent(ctx context.Context, ch chan<- types.AgentStreamEvent, msg types.AgentStreamEvent) { + select { + case ch <- msg: + case <-ctx.Done(): + slog.Debug("stream channel closed", slog.Any("error", ctx.Err())) + } +} diff --git a/builder/rpc/agenthub_svc_client_test.go b/builder/rpc/agenthub_svc_client_test.go new file mode 100644 index 000000000..6fce5134b --- /dev/null +++ b/builder/rpc/agenthub_svc_client_test.go @@ -0,0 +1,261 @@ +package rpc + +import ( + "context" + "encoding/json" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTestAgentHubClient(server *httptest.Server) *AgentHubSvcClientImpl { + return &AgentHubSvcClientImpl{ + hc: &HttpClient{ + endpoint: server.URL, + hc: server.Client(), + logger: slog.New(slog.NewJSONHandler(os.Stdout, nil)), + }, + token: "test-token", + } +} + +func TestAgentHubSvcClientImpl_DeleteAgentInstance_Success(t *testing.T) { + contentID := "test-content-id" + userUUID := "test-user-uuid" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/opencsg/flows/delete?token=test-token", r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + assert.Equal(t, userUUID, r.Header.Get("user_uuid")) + + var req DeleteAgentInstanceRequest + err := json.NewDecoder(r.Body).Decode(&req) + require.NoError(t, err) + assert.Equal(t, []string{contentID}, req.IDs) + + resp := DeleteAgentInstanceResponse{ + IDs: []string{contentID}, + Total: 1, + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + err := client.DeleteAgentInstance(context.Background(), userUUID, contentID) + require.NoError(t, err) +} + +func TestAgentHubSvcClientImpl_DeleteAgentInstance_Non200Status(t *testing.T) { + contentID := "test-content-id" + userUUID := "test-user-uuid" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + err := client.DeleteAgentInstance(context.Background(), userUUID, contentID) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to delete agent instance in agenthub, status code: 500") +} + +func TestAgentHubSvcClientImpl_DeleteAgentInstance_ReadBodyError(t *testing.T) { + contentID := "test-content-id" + userUUID := "test-user-uuid" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Set Content-Length to force an error when reading + w.Header().Set("Content-Length", "100") + w.WriteHeader(http.StatusOK) + // Close the connection immediately to cause a read error + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + err := client.DeleteAgentInstance(context.Background(), userUUID, contentID) + require.Error(t, err) +} + +func TestAgentHubSvcClientImpl_CreateAgentInstance_Success(t *testing.T) { + userUUID := "test-user-uuid" + req := &CreateAgentInstanceRequest{ + Name: "Test Instance", + Description: "Test Description", + Data: json.RawMessage(`{"nodes": [], "edges": []}`), + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/opencsg/flows/?token=test-token", r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + assert.Equal(t, userUUID, r.Header.Get("user_uuid")) + + var requestBody CreateAgentInstanceRequest + err := json.NewDecoder(r.Body).Decode(&requestBody) + require.NoError(t, err) + assert.Equal(t, req.Name, requestBody.Name) + assert.Equal(t, req.Description, requestBody.Description) + + resp := CreateAgentInstanceResponse{ + ID: "new-flow-id", + Name: "Test Instance", + Description: "Test Description", + Data: json.RawMessage(`{"nodes": [], "edges": []}`), + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + resp, err := client.CreateAgentInstance(context.Background(), userUUID, req) + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "new-flow-id", resp.ID) + assert.Equal(t, "Test Instance", resp.Name) + assert.Equal(t, "Test Description", resp.Description) +} + +func TestAgentHubSvcClientImpl_CreateAgentInstance_NilRequest(t *testing.T) { + userUUID := "test-user-uuid" + + client := setupTestAgentHubClient(httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))) + defer client.hc.hc.CloseIdleConnections() + + resp, err := client.CreateAgentInstance(context.Background(), userUUID, nil) + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "create agent instance request is nil") +} + +func TestAgentHubSvcClientImpl_CreateAgentInstance_Non200Status(t *testing.T) { + userUUID := "test-user-uuid" + req := &CreateAgentInstanceRequest{ + Name: "Test Instance", + Description: "Test Description", + Data: json.RawMessage(`{}`), + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + resp, err := client.CreateAgentInstance(context.Background(), userUUID, req) + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "failed to create agent instance in agenthub") +} + +func TestAgentHubSvcClientImpl_CreateAgentInstance_ReadBodyError(t *testing.T) { + userUUID := "test-user-uuid" + req := &CreateAgentInstanceRequest{ + Name: "Test Instance", + Description: "Test Description", + Data: json.RawMessage(`{}`), + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Set Content-Length to force an error when reading + w.Header().Set("Content-Length", "100") + w.WriteHeader(http.StatusOK) + // Close the connection immediately to cause a read error + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + resp, err := client.CreateAgentInstance(context.Background(), userUUID, req) + require.Error(t, err) + require.Nil(t, resp) +} + +func TestAgentHubSvcClientImpl_UpdateAgentInstance_Success(t *testing.T) { + contentID := "test-content-id" + userUUID := "test-user-uuid" + mcpEnabled := true + locked := false + req := &UpdateAgentInstanceRequest{ + Name: "Updated Instance", + Description: "Updated Description", + Data: json.RawMessage(`{"nodes": [{"id": "1"}], "edges": []}`), + FolderID: "folder-id", + MCPEnabled: &mcpEnabled, + Locked: &locked, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/opencsg/flows/"+contentID+"?token=test-token", r.URL.String()) + assert.Equal(t, http.MethodPatch, r.Method) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + assert.Equal(t, userUUID, r.Header.Get("user_uuid")) + + var requestBody UpdateAgentInstanceRequest + err := json.NewDecoder(r.Body).Decode(&requestBody) + require.NoError(t, err) + assert.Equal(t, req.Name, requestBody.Name) + assert.Equal(t, req.Description, requestBody.Description) + assert.Equal(t, req.FolderID, requestBody.FolderID) + assert.NotNil(t, requestBody.MCPEnabled) + assert.True(t, *requestBody.MCPEnabled) + assert.NotNil(t, requestBody.Locked) + assert.False(t, *requestBody.Locked) + + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + err := client.UpdateAgentInstance(context.Background(), userUUID, contentID, req) + require.NoError(t, err) +} + +func TestAgentHubSvcClientImpl_UpdateAgentInstance_NilRequest(t *testing.T) { + contentID := "test-content-id" + userUUID := "test-user-uuid" + + client := setupTestAgentHubClient(httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))) + defer client.hc.hc.CloseIdleConnections() + + err := client.UpdateAgentInstance(context.Background(), userUUID, contentID, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "update agent instance request is nil") +} + +func TestAgentHubSvcClientImpl_UpdateAgentInstance_Non200Status(t *testing.T) { + contentID := "test-content-id" + userUUID := "test-user-uuid" + req := &UpdateAgentInstanceRequest{ + Name: "Updated Instance", + Description: "Updated Description", + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + + client := setupTestAgentHubClient(server) + + err := client.UpdateAgentInstance(context.Background(), userUUID, contentID, req) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to update agent instance in agenthub") +} diff --git a/builder/store/database/agent_instance.go b/builder/store/database/agent_instance.go new file mode 100644 index 000000000..eb5c257cc --- /dev/null +++ b/builder/store/database/agent_instance.go @@ -0,0 +1,411 @@ +package database + +import ( + "context" + "strings" + "time" + + "github.com/uptrace/bun" + "opencsg.com/csghub-server/common/errorx" + "opencsg.com/csghub-server/common/types" +) + +// AgentTemplate represents the template for an agent +type AgentTemplate struct { + ID int64 `bun:",pk,autoincrement" json:"id"` + Type string `bun:",notnull" json:"type"` // Possible values: langflow, agno, code, etc. + UserUUID string `bun:",notnull" json:"user_uuid"` // Associated with the corresponding field in the User table + Name string `bun:",notnull" json:"name"` // Agent template name + Description string `bun:",nullzero" json:"description"` // Agent template description + Content string `bun:",type:text" json:"content"` // Used to store the complete content of the template + Public bool `bun:",notnull" json:"public"` // Whether the template is public + Metadata map[string]any `bun:",type:jsonb" json:"metadata"` // Template metadata + DeletedAt time.Time `bun:",soft_delete,nullzero" json:"deleted_at"` + times +} + +// AgentInstance represents an instance created from an agent template +type AgentInstance struct { + ID int64 `bun:",pk,autoincrement" json:"id"` + TemplateID int64 `bun:"" json:"template_id"` // Associated with the id in the template table + UserUUID string `bun:",notnull" json:"user_uuid"` // Associated with the corresponding field in the User table + Type string `bun:",notnull" json:"type"` // Possible values: langflow, agno, code, etc. + ContentID string `bun:",notnull" json:"content_id"` // Used to specify the unique id of the instance resource + Public bool `bun:",notnull" json:"public"` // Whether the instance is public + Name string `bun:",nullzero" json:"name"` // Instance name + Description string `bun:",nullzero" json:"description"` // Instance description + BuiltIn bool `bun:",notnull" json:"built_in"` // Whether the instance is built-in + Metadata map[string]any `bun:",type:jsonb" json:"metadata"` // Instance metadata + DeletedAt time.Time `bun:",soft_delete,nullzero" json:"deleted_at"` + times +} + +// AgentTemplateStore provides database operations for AgentTemplate +type AgentTemplateStore interface { + Create(ctx context.Context, template *AgentTemplate) (*AgentTemplate, error) + FindByID(ctx context.Context, id int64) (*AgentTemplate, error) + ListByUserUUID(ctx context.Context, userUUID string, filter types.AgentTemplateFilter, per int, page int) ([]AgentTemplate, int, error) + Update(ctx context.Context, template *AgentTemplate) error + Delete(ctx context.Context, id int64) error +} + +// AgentInstanceStore provides database operations for AgentInstance +type AgentInstanceStore interface { + Create(ctx context.Context, instance *AgentInstance) (*AgentInstance, error) + FindByID(ctx context.Context, id int64) (*AgentInstance, error) + FindByIDs(ctx context.Context, ids []int64) ([]AgentInstance, error) + FindByContentID(ctx context.Context, instanceType string, contentID string) (*AgentInstance, error) + IsInstanceExistsByContentID(ctx context.Context, instanceType string, contentID string) (bool, error) + ListByUserUUID(ctx context.Context, userUUID string, filter types.AgentInstanceFilter, per int, page int) ([]AgentInstance, int, error) + Update(ctx context.Context, instance *AgentInstance) error + Delete(ctx context.Context, id int64) error + CountByUserAndType(ctx context.Context, userUUID string, instanceType string) (int, error) + IsInstanceNameExists(ctx context.Context, userUUID string, instanceName string) (bool, error) +} + +// agentTemplateStoreImpl is the implementation of AgentTemplateStore +type agentTemplateStoreImpl struct { + db *DB +} + +// agentInstanceStoreImpl is the implementation of AgentInstanceStore +type agentInstanceStoreImpl struct { + db *DB +} + +// NewAgentTemplateStore creates a new AgentTemplateStore +func NewAgentTemplateStore() AgentTemplateStore { + return &agentTemplateStoreImpl{ + db: defaultDB, + } +} + +// NewAgentTemplateStoreWithDB creates a new AgentTemplateStore with a specific DB +func NewAgentTemplateStoreWithDB(db *DB) AgentTemplateStore { + return &agentTemplateStoreImpl{ + db: db, + } +} + +// NewAgentInstanceStore creates a new AgentInstanceStore +func NewAgentInstanceStore() AgentInstanceStore { + return &agentInstanceStoreImpl{ + db: defaultDB, + } +} + +// NewAgentInstanceStoreWithDB creates a new AgentInstanceStore with a specific DB +func NewAgentInstanceStoreWithDB(db *DB) AgentInstanceStore { + return &agentInstanceStoreImpl{ + db: db, + } +} + +// Create inserts a new AgentTemplate into the database +func (s *agentTemplateStoreImpl) Create(ctx context.Context, template *AgentTemplate) (*AgentTemplate, error) { + res, err := s.db.Core.NewInsert().Model(template).Exec(ctx, template) + if err = assertAffectedOneRow(res, err); err != nil { + return nil, errorx.HandleDBError(err, map[string]any{ + "template_type": template.Type, + "user_uuid": template.UserUUID, + }) + } + return template, nil +} + +// FindByID retrieves an AgentTemplate by its ID +func (s *agentTemplateStoreImpl) FindByID(ctx context.Context, id int64) (*AgentTemplate, error) { + template := &AgentTemplate{} + err := s.db.Core.NewSelect().Model(template).Where("id = ?", id).Scan(ctx, template) + if err != nil { + return nil, errorx.HandleDBError(err, map[string]any{ + "template_id": id, + }) + } + return template, nil +} + +func (s *agentTemplateStoreImpl) applyAgentTemplateFilters(query *bun.SelectQuery, filter types.AgentTemplateFilter) *bun.SelectQuery { + filter.Search = strings.TrimSpace(filter.Search) + if filter.Search != "" { + searchPattern := "%" + filter.Search + "%" + query = query.Where("LOWER(name) LIKE LOWER(?) OR LOWER(description) LIKE LOWER(?)", searchPattern, searchPattern) + } + + if filter.Type != "" { + query = query.Where("type = ?", filter.Type) + } + + return query +} + +// ListByUserUUID retrieves all AgentTemplates for a specific user +func (s *agentTemplateStoreImpl) ListByUserUUID(ctx context.Context, userUUID string, filter types.AgentTemplateFilter, per int, page int) ([]AgentTemplate, int, error) { + var templates []AgentTemplate + query := s.db.Core.NewSelect().Model(&templates).Where("user_uuid = ? OR public = ?", userUUID, true) + + query = s.applyAgentTemplateFilters(query, filter) + + total, err := query.Count(ctx) + if err != nil { + return nil, 0, errorx.HandleDBError(err, map[string]any{ + "user_uuid": userUUID, + }) + } + + err = query.Order("updated_at DESC").Limit(per).Offset((page-1)*per).Scan(ctx, &templates) + if err != nil { + return nil, 0, errorx.HandleDBError(err, map[string]any{ + "user_uuid": userUUID, + }) + } + return templates, total, nil +} + +// Update updates an existing AgentTemplate +func (s *agentTemplateStoreImpl) Update(ctx context.Context, template *AgentTemplate) error { + res, err := s.db.Core.NewUpdate().Model(template).Where("id = ?", template.ID).Exec(ctx) + if err = assertAffectedOneRow(res, err); err != nil { + return errorx.HandleDBError(err, map[string]any{ + "template_id": template.ID, + }) + } + return nil +} + +// Delete removes an AgentTemplate from the database +func (s *agentTemplateStoreImpl) Delete(ctx context.Context, id int64) error { + res, err := s.db.Core.NewDelete().Model((*AgentTemplate)(nil)).Where("id = ?", id).Exec(ctx) + if err = assertAffectedOneRow(res, err); err != nil { + return errorx.HandleDBError(err, map[string]any{ + "template_id": id, + }) + } + return nil +} + +// Create inserts a new AgentInstance into the database +func (s *agentInstanceStoreImpl) Create(ctx context.Context, instance *AgentInstance) (*AgentInstance, error) { + res, err := s.db.Core.NewInsert().Model(instance).Exec(ctx, instance) + if err = assertAffectedOneRow(res, err); err != nil { + return nil, errorx.HandleDBError(err, map[string]any{ + "template_id": instance.TemplateID, + "user_uuid": instance.UserUUID, + "content_id": instance.ContentID, + }) + } + return instance, nil +} + +// FindByID retrieves an AgentInstance by its ID +func (s *agentInstanceStoreImpl) FindByID(ctx context.Context, id int64) (*AgentInstance, error) { + instance := &AgentInstance{} + err := s.db.Core.NewSelect().Model(instance).Where("id = ?", id).Scan(ctx, instance) + if err != nil { + return nil, errorx.HandleDBError(err, map[string]any{ + "instance_id": id, + }) + } + return instance, nil +} + +// FindByIDs retrieves AgentInstances by their IDs +func (s *agentInstanceStoreImpl) FindByIDs(ctx context.Context, ids []int64) ([]AgentInstance, error) { + instances := make([]AgentInstance, len(ids)) + err := s.db.Core.NewSelect().Model(&instances).Where("id IN (?)", bun.In(ids)).Scan(ctx, &instances) + if err != nil { + return nil, errorx.HandleDBError(err, map[string]any{ + "instance_ids": ids, + }) + } + return instances, nil +} + +// FindByContentID retrieves an AgentInstance by its content ID +func (s *agentInstanceStoreImpl) FindByContentID(ctx context.Context, instanceType string, contentID string) (*AgentInstance, error) { + instance := &AgentInstance{} + err := s.db.Core.NewSelect().Model(instance).Where("type = ? AND content_id = ?", instanceType, contentID).Limit(1).Scan(ctx, instance) + if err != nil { + return nil, errorx.HandleDBError(err, map[string]any{ + "instance_type": instanceType, + "content_id": contentID, + }) + } + return instance, nil +} + +func (s *agentInstanceStoreImpl) IsInstanceExistsByContentID(ctx context.Context, instanceType string, contentID string) (bool, error) { + exists, err := s.db.Core.NewSelect(). + Model((*AgentInstance)(nil)). + Where("type = ? AND content_id = ?", instanceType, contentID). + Exists(ctx) + if err != nil { + return false, errorx.HandleDBError(err, map[string]any{ + "instance_type": instanceType, + "content_id": contentID, + }) + } + + return exists, nil +} + +func (s *agentInstanceStoreImpl) applyAgentInstanceFilters(query *bun.SelectQuery, filter types.AgentInstanceFilter) *bun.SelectQuery { + filter.Search = strings.TrimSpace(filter.Search) + if filter.Search != "" { + searchPattern := "%" + filter.Search + "%" + query = query.Where("LOWER(name) LIKE LOWER(?) OR LOWER(description) LIKE LOWER(?)", searchPattern, searchPattern) + } + + if filter.Type != "" { + query = query.Where("type = ?", filter.Type) + } + + // Apply template ID filter + if filter.TemplateID != nil { + query = query.Where("template_id = ?", *filter.TemplateID) + } + + if filter.BuiltIn != nil { + query = query.Where("built_in = ?", *filter.BuiltIn) + } + + if filter.Public != nil { + query = query.Where("public = ?", *filter.Public) + } + + return query +} + +func (s *agentInstanceStoreImpl) ListByUserUUID(ctx context.Context, userUUID string, filter types.AgentInstanceFilter, per int, page int) ([]AgentInstance, int, error) { + var instances []AgentInstance + query := s.db.Core.NewSelect().Model(&instances).Where("user_uuid = ? OR public = ?", userUUID, true) + + query = s.applyAgentInstanceFilters(query, filter) + + total, err := query.Count(ctx) + if err != nil { + return nil, 0, errorx.HandleDBError(err, map[string]any{ + "user_uuid": userUUID, + }) + } + + err = query.Order("updated_at DESC").Limit(per).Offset((page-1)*per).Scan(ctx, &instances) + if err != nil { + return nil, 0, errorx.HandleDBError(err, map[string]any{ + "user_uuid": userUUID, + }) + } + return instances, total, nil +} + +// Update updates an existing AgentInstance +func (s *agentInstanceStoreImpl) Update(ctx context.Context, instance *AgentInstance) error { + res, err := s.db.Core.NewUpdate().Model(instance).Where("id = ?", instance.ID).Exec(ctx) + if err = assertAffectedOneRow(res, err); err != nil { + return errorx.HandleDBError(err, map[string]any{ + "instance_id": instance.ID, + }) + } + return nil +} + +// Delete removes an AgentInstance from the database +// It also soft-deletes all related sessions, session histories, and tasks +func (s *agentInstanceStoreImpl) Delete(ctx context.Context, id int64) error { + return s.db.Core.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + // First, get all session IDs for this instance + var sessionIDs []int64 + err := tx.NewSelect(). + Model((*AgentInstanceSession)(nil)). + Column("id"). + Where("instance_id = ?", id). + Scan(ctx, &sessionIDs) + if err != nil { + return errorx.HandleDBError(err, map[string]any{ + "instance_id": id, + "operation": "get_session_ids", + }) + } + + // Soft-delete all session histories for these sessions + if len(sessionIDs) > 0 { + _, err = tx.NewDelete(). + Model((*AgentInstanceSessionHistory)(nil)). + Where("session_id IN (?)", bun.In(sessionIDs)). + Exec(ctx) + if err != nil { + return errorx.HandleDBError(err, map[string]any{ + "instance_id": id, + "operation": "delete_session_histories", + }) + } + } + + // Soft-delete all sessions for this instance + _, err = tx.NewDelete(). + Model((*AgentInstanceSession)(nil)). + Where("instance_id = ?", id). + Exec(ctx) + if err != nil { + return errorx.HandleDBError(err, map[string]any{ + "instance_id": id, + "operation": "delete_sessions", + }) + } + + // Soft-delete all tasks for this instance + _, err = tx.NewDelete(). + Model((*AgentInstanceTask)(nil)). + Where("instance_id = ?", id). + Exec(ctx) + if err != nil { + return errorx.HandleDBError(err, map[string]any{ + "instance_id": id, + "operation": "delete_tasks", + }) + } + + // Finally, soft-delete the instance itself + res, err := tx.NewDelete(). + Model((*AgentInstance)(nil)). + Where("id = ?", id). + Exec(ctx) + if err = assertAffectedOneRow(res, err); err != nil { + return errorx.HandleDBError(err, map[string]any{ + "instance_id": id, + "operation": "delete_instance", + }) + } + + return nil + }) +} + +// CountByUserAndType returns the count of agent instances for a specific user and type +func (s *agentInstanceStoreImpl) CountByUserAndType(ctx context.Context, userUUID string, instanceType string) (int, error) { + count, err := s.db.Core.NewSelect(). + Model((*AgentInstance)(nil)). + Where("user_uuid = ? AND type = ?", userUUID, instanceType). + Count(ctx) + if err != nil { + return 0, errorx.HandleDBError(err, map[string]any{ + "user_uuid": userUUID, + "instance_type": instanceType, + }) + } + return count, nil +} + +func (s *agentInstanceStoreImpl) IsInstanceNameExists(ctx context.Context, userUUID string, instanceName string) (bool, error) { + exists, err := s.db.Core.NewSelect(). + Model((*AgentInstance)(nil)). + Where("user_uuid = ? AND name = ?", userUUID, instanceName). + Exists(ctx) + if err != nil { + return false, errorx.HandleDBError(err, map[string]any{ + "user_uuid": userUUID, + "instance_name": instanceName, + }) + } + return exists, nil +} diff --git a/builder/store/database/agent_instance_test.go b/builder/store/database/agent_instance_test.go new file mode 100644 index 000000000..de0495772 --- /dev/null +++ b/builder/store/database/agent_instance_test.go @@ -0,0 +1,1120 @@ +package database_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "opencsg.com/csghub-server/builder/store/database" + "opencsg.com/csghub-server/common/tests" + "opencsg.com/csghub-server/common/types" +) + +func TestAgentInstanceStore_CRUD(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + // Test Create + userUUID := uuid.New().String() + instance := &database.AgentInstance{ + TemplateID: 123, + UserUUID: userUUID, + Type: "langflow", + ContentID: "instance-123", + Public: false, + } + + createdInstance, err := store.Create(ctx, instance) + require.NoError(t, err) + require.NotZero(t, createdInstance.ID) + // Update the original instance with the created one for further tests + *instance = *createdInstance + + // Test FindByID + foundInstance, err := store.FindByID(ctx, instance.ID) + require.NoError(t, err) + require.Equal(t, instance.ID, foundInstance.ID) + require.Equal(t, instance.TemplateID, foundInstance.TemplateID) + require.Equal(t, instance.UserUUID, foundInstance.UserUUID) + require.Equal(t, instance.Type, foundInstance.Type) + require.Equal(t, instance.ContentID, foundInstance.ContentID) + require.Equal(t, instance.Public, foundInstance.Public) + + // Test ListByUserUUID + instances, total, err := store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 5) // 1 user instance + 4 system instances (all public) + require.Equal(t, 5, total) + // Find our instance in the results + found := false + for _, inst := range instances { + if inst.ID == instance.ID { + found = true + break + } + } + require.True(t, found, "User instance should be found in results") + + // Test Update + instance.ContentID = "updated-instance-123" + instance.Public = true + err = store.Update(ctx, instance) + require.NoError(t, err) + + // Verify update + updatedInstance, err := store.FindByID(ctx, instance.ID) + require.NoError(t, err) + require.Equal(t, "updated-instance-123", updatedInstance.ContentID) + require.True(t, updatedInstance.Public) + + // Test Delete + err = store.Delete(ctx, instance.ID) + require.NoError(t, err) + + // Verify deletion + _, err = store.FindByID(ctx, instance.ID) + require.Error(t, err) +} + +func TestAgentInstanceStore_ListByUserUUID_WithPublicInstances(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID1 := uuid.New().String() + userUUID2 := uuid.New().String() + + // Create private instance for user1 + privateInstance := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID1, + Type: "langflow", + ContentID: "private-instance", + Public: false, + } + _, err := store.Create(ctx, privateInstance) + require.NoError(t, err) + + // Create public instance for user1 + publicInstance := &database.AgentInstance{ + TemplateID: 2, + UserUUID: userUUID1, + Type: "agno", + ContentID: "public-instance", + Public: true, + } + _, err = store.Create(ctx, publicInstance) + require.NoError(t, err) + + // Create private instance for user2 + user2Instance := &database.AgentInstance{ + TemplateID: 3, + UserUUID: userUUID2, + Type: "code", + ContentID: "user2-instance", + Public: false, + } + _, err = store.Create(ctx, user2Instance) + require.NoError(t, err) + + // Test ListByUserUUID for user1 - should return both private and public instances + instances, total, err := store.ListByUserUUID(ctx, userUUID1, types.AgentInstanceFilter{}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 6) // 2 user instances + 4 system instances (all public) + require.Equal(t, 6, total) + + // Test ListByUserUUID for user2 - should return only public instance from user1 and private instance from user2 + instances, total, err = store.ListByUserUUID(ctx, userUUID2, types.AgentInstanceFilter{}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 6) // public instance from user1 + private instance from user2 + 4 system instances + require.Equal(t, 6, total) +} + +func TestAgentInstanceStore_NotFound(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + // Test FindByID with non-existent ID + _, err := store.FindByID(ctx, 99999) + require.Error(t, err) + + // Test ListByUserUUID with non-existent user + instances, total, err := store.ListByUserUUID(ctx, "non-existent-user", types.AgentInstanceFilter{}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 4) // Only system instances (all public) + require.Equal(t, 4, total) + +} + +func TestAgentInstanceStore_Update_NonExistent(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + // Test Update with non-existent instance + nonExistentInstance := &database.AgentInstance{ + ID: 99999, + TemplateID: 123, + UserUUID: uuid.New().String(), + Type: "langflow", + ContentID: "test-content", + Public: false, + } + + err := store.Update(ctx, nonExistentInstance) + require.Error(t, err) +} + +func TestAgentInstanceStore_Delete_NonExistent(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + // Test Delete with non-existent ID + err := store.Delete(ctx, 99999) + require.Error(t, err) +} + +func TestAgentInstanceStore_MultipleInstancesFromSameTemplate(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + templateID := int64(200) + + // Create multiple instances from the same template + instance1 := &database.AgentInstance{ + TemplateID: templateID, + UserUUID: userUUID, + Type: "langflow", + ContentID: "instance-1", + Public: false, + } + _, err := store.Create(ctx, instance1) + require.NoError(t, err) + + instance2 := &database.AgentInstance{ + TemplateID: templateID, + UserUUID: userUUID, + Type: "langflow", + ContentID: "instance-2", + Public: true, + } + _, err = store.Create(ctx, instance2) + require.NoError(t, err) + + // Test ListByUserUUID - should return both instances + instances, total, err := store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 6) // 2 user instances + 4 system instances (all public) + require.Equal(t, 6, total) +} + +func TestAgentInstanceStore_ListByUserUUID_WithFilters(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + + // Create instances with different types and names + instance1 := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "langflow-instance", + Name: "Langflow Agent", + Description: "A langflow agent for automation", + Public: false, + } + _, err := store.Create(ctx, instance1) + require.NoError(t, err) + + instance2 := &database.AgentInstance{ + TemplateID: 2, + UserUUID: userUUID, + Type: "agno", + ContentID: "agno-instance", + Name: "Agno Assistant", + Description: "An agno assistant for help", + Public: false, + } + _, err = store.Create(ctx, instance2) + require.NoError(t, err) + + instance3 := &database.AgentInstance{ + TemplateID: 3, + UserUUID: userUUID, + Type: "langflow", + ContentID: "another-langflow", + Name: "Another Langflow", + Description: "Another langflow instance", + Public: false, + } + _, err = store.Create(ctx, instance3) + require.NoError(t, err) + + // Test search filter + instances, total, err := store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{Search: "langflow"}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 2) // Should find both langflow instances (system instances don't match "langflow") + require.Equal(t, 2, total) + + // Test type filter + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{Type: "agno"}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 1) // Should find only agno instance (system instances are type "code") + require.Equal(t, 1, total) + + // Test combined filters + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{Search: "another", Type: "langflow"}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 1) // Should find only the "Another Langflow" instance + require.Equal(t, 1, total) + + // Test pagination + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{}, 2, 1) + require.NoError(t, err) + require.Len(t, instances, 2) // Should return only 2 instances due to limit + require.Equal(t, 7, total) // But total should be 7 (3 user + 4 system) + + // Test second page + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{}, 2, 2) + require.NoError(t, err) + require.Len(t, instances, 2) // Should return 2 instances on second page + require.Equal(t, 7, total) // Total should still be 7 (3 user + 4 system) +} + +func TestAgentInstanceStore_ListByUserUUID_WithTemplateFilter(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + templateID1 := int64(1) + templateID2 := int64(2) + + // Create instances with different template IDs + instance1 := &database.AgentInstance{ + TemplateID: templateID1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "langflow-instance-1", + Name: "Langflow Agent 1", + Description: "A langflow agent for automation", + Public: false, + } + _, err := store.Create(ctx, instance1) + require.NoError(t, err) + + instance2 := &database.AgentInstance{ + TemplateID: templateID2, + UserUUID: userUUID, + Type: "agno", + ContentID: "agno-instance-1", + Name: "Agno Assistant 1", + Description: "An agno assistant for help", + Public: false, + } + _, err = store.Create(ctx, instance2) + require.NoError(t, err) + + instance3 := &database.AgentInstance{ + TemplateID: templateID1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "langflow-instance-2", + Name: "Langflow Agent 2", + Description: "Another langflow agent", + Public: false, + } + _, err = store.Create(ctx, instance3) + require.NoError(t, err) + + // Test template ID filter + instances, total, err := store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{TemplateID: &templateID1}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 2) // Should find both instances from template 1 + require.Equal(t, 2, total) + + // Test template ID filter with different template + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{TemplateID: &templateID2}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 1) // Should find only instance from template 2 + require.Equal(t, 1, total) + + // Test combined filters (template + type) + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{TemplateID: &templateID1, Type: "langflow"}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 2) // Should find both langflow instances from template 1 + require.Equal(t, 2, total) + + // Test combined filters (template + search) + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{TemplateID: &templateID1, Search: "Agent 1"}, 10, 1) + require.NoError(t, err) + require.Len(t, instances, 1) // Should find only "Langflow Agent 1" + require.Equal(t, 1, total) +} + +func TestAgentInstanceStore_ListByUserUUID_WithPublicFilter(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + + // Create public instance + publicInstance := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "public-instance", + Name: "Public Instance", + Description: "A public instance", + Public: true, + } + _, err := store.Create(ctx, publicInstance) + require.NoError(t, err) + + // Create private instance + privateInstance := &database.AgentInstance{ + TemplateID: 2, + UserUUID: userUUID, + Type: "agno", + ContentID: "private-instance", + Name: "Private Instance", + Description: "A private instance", + Public: false, + } + _, err = store.Create(ctx, privateInstance) + require.NoError(t, err) + + // Test public filter - true + publicTrue := true + instances, total, err := store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{Public: &publicTrue}, 10, 1) + require.NoError(t, err) + // Should find public instance + system instances (all public) + require.GreaterOrEqual(t, len(instances), 1, "Should find at least the public instance") + require.GreaterOrEqual(t, total, 1) + // Verify the public instance is in results + found := false + for _, inst := range instances { + if inst.ID == publicInstance.ID { + found = true + require.True(t, inst.Public, "Instance should be public") + break + } + } + require.True(t, found, "Public instance should be found in results") + + // Test public filter - false + publicFalse := false + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{Public: &publicFalse}, 10, 1) + require.NoError(t, err) + // Should find only private instance (user's own private instance) + require.GreaterOrEqual(t, len(instances), 1, "Should find at least the private instance") + require.GreaterOrEqual(t, total, 1) + // Verify the private instance is in results + found = false + for _, inst := range instances { + if inst.ID == privateInstance.ID { + found = true + require.False(t, inst.Public, "Instance should be private") + break + } + } + require.True(t, found, "Private instance should be found in results") + + // Test combined filters (public + type) + publicTrue = true + instances, total, err = store.ListByUserUUID(ctx, userUUID, types.AgentInstanceFilter{Public: &publicTrue, Type: "langflow"}, 10, 1) + require.NoError(t, err) + // Should find public langflow instance + require.GreaterOrEqual(t, len(instances), 1) + require.GreaterOrEqual(t, total, 1) + found = false + for _, inst := range instances { + if inst.ID == publicInstance.ID { + found = true + require.True(t, inst.Public) + require.Equal(t, "langflow", inst.Type) + break + } + } + require.True(t, found, "Public langflow instance should be found in results") +} + +func TestAgentInstanceStore_IsInstanceExistsByContentID(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + instanceType := "langflow" + contentID := "test-content-123" + + // Test case 1: Instance does not exist + exists, err := store.IsInstanceExistsByContentID(ctx, instanceType, contentID) + require.NoError(t, err) + require.False(t, exists) + + // Test case 2: Create an instance and verify it exists + instance := &database.AgentInstance{ + TemplateID: 123, + UserUUID: userUUID, + Type: instanceType, + ContentID: contentID, + Public: false, + } + + createdInstance, err := store.Create(ctx, instance) + require.NoError(t, err) + require.NotZero(t, createdInstance.ID) + + // Verify the instance exists + exists, err = store.IsInstanceExistsByContentID(ctx, instanceType, contentID) + require.NoError(t, err) + require.True(t, exists) + + // Test case 3: Different type, same content_id + exists, err = store.IsInstanceExistsByContentID(ctx, "agno", contentID) + require.NoError(t, err) + require.False(t, exists) + + // Test case 4: Same type, different content_id + exists, err = store.IsInstanceExistsByContentID(ctx, instanceType, "different-content-id") + require.NoError(t, err) + require.False(t, exists) + + // Test case 5: Both type and content_id different + exists, err = store.IsInstanceExistsByContentID(ctx, "agno", "different-content-id") + require.NoError(t, err) + require.False(t, exists) +} + +func TestAgentInstanceStore_IsInstanceExistsByContentID_MultipleInstances(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + + // Create multiple instances with different types and content IDs + instances := []*database.AgentInstance{ + { + TemplateID: 1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "content-1", + Public: false, + }, + { + TemplateID: 2, + UserUUID: userUUID, + Type: "agno", + ContentID: "content-2", + Public: false, + }, + { + TemplateID: 3, + UserUUID: userUUID, + Type: "langflow", + ContentID: "content-3", + Public: false, + }, + } + + // Create all instances + for _, instance := range instances { + _, err := store.Create(ctx, instance) + require.NoError(t, err) + } + + // Test each instance exists + for _, instance := range instances { + exists, err := store.IsInstanceExistsByContentID(ctx, instance.Type, instance.ContentID) + require.NoError(t, err) + require.True(t, exists, "Instance with type %s and content_id %s should exist", instance.Type, instance.ContentID) + } + + // Test non-existent combinations + testCases := []struct { + instanceType string + contentID string + description string + }{ + {"langflow", "non-existent", "langflow type with non-existent content_id"}, + {"agno", "content-1", "agno type with langflow content_id"}, + {"code", "content-2", "code type with agno content_id"}, + {"unknown", "content-3", "unknown type with langflow content_id"}, + } + + for _, tc := range testCases { + exists, err := store.IsInstanceExistsByContentID(ctx, tc.instanceType, tc.contentID) + require.NoError(t, err) + require.False(t, exists, "Should not exist: %s", tc.description) + } +} + +func TestAgentInstanceStore_IsInstanceExistsByContentID_EmptyParameters(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + // Test with empty type + exists, err := store.IsInstanceExistsByContentID(ctx, "", "some-content-id") + require.NoError(t, err) + require.False(t, exists) + + // Test with empty content_id + exists, err = store.IsInstanceExistsByContentID(ctx, "langflow", "") + require.NoError(t, err) + require.False(t, exists) + + // Test with both empty + exists, err = store.IsInstanceExistsByContentID(ctx, "", "") + require.NoError(t, err) + require.False(t, exists) +} + +func TestAgentInstanceStore_CountByUserAndType(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID1 := uuid.New().String() + userUUID2 := uuid.New().String() + instanceType1 := "langflow" + instanceType2 := "agno" + + // Test case 1: Count with no instances (should return 0) + count, err := store.CountByUserAndType(ctx, userUUID1, instanceType1) + require.NoError(t, err) + require.Equal(t, 0, count) + + // Test case 2: Create instances for user1 with type1 + instance1 := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID1, + Type: instanceType1, + ContentID: "content-1", + Public: false, + } + _, err = store.Create(ctx, instance1) + require.NoError(t, err) + + instance2 := &database.AgentInstance{ + TemplateID: 2, + UserUUID: userUUID1, + Type: instanceType1, + ContentID: "content-2", + Public: false, + } + _, err = store.Create(ctx, instance2) + require.NoError(t, err) + + instance3 := &database.AgentInstance{ + TemplateID: 3, + UserUUID: userUUID1, + Type: instanceType1, + ContentID: "content-3", + Public: true, + } + _, err = store.Create(ctx, instance3) + require.NoError(t, err) + + // Count should return 3 for user1 with type1 + count, err = store.CountByUserAndType(ctx, userUUID1, instanceType1) + require.NoError(t, err) + require.Equal(t, 3, count) + + // Test case 3: Create instances for user1 with type2 + instance4 := &database.AgentInstance{ + TemplateID: 4, + UserUUID: userUUID1, + Type: instanceType2, + ContentID: "content-4", + Public: false, + } + _, err = store.Create(ctx, instance4) + require.NoError(t, err) + + // Count for user1 with type1 should still be 3 + count, err = store.CountByUserAndType(ctx, userUUID1, instanceType1) + require.NoError(t, err) + require.Equal(t, 3, count) + + // Count for user1 with type2 should be 1 + count, err = store.CountByUserAndType(ctx, userUUID1, instanceType2) + require.NoError(t, err) + require.Equal(t, 1, count) + + // Test case 4: Create instances for user2 with type1 + instance5 := &database.AgentInstance{ + TemplateID: 5, + UserUUID: userUUID2, + Type: instanceType1, + ContentID: "content-5", + Public: false, + } + _, err = store.Create(ctx, instance5) + require.NoError(t, err) + + // Count for user1 with type1 should still be 3 (not affected by user2's instances) + count, err = store.CountByUserAndType(ctx, userUUID1, instanceType1) + require.NoError(t, err) + require.Equal(t, 3, count) + + // Count for user2 with type1 should be 1 + count, err = store.CountByUserAndType(ctx, userUUID2, instanceType1) + require.NoError(t, err) + require.Equal(t, 1, count) + + // Test case 5: Count with non-existent user (should return 0) + nonExistentUser := uuid.New().String() + count, err = store.CountByUserAndType(ctx, nonExistentUser, instanceType1) + require.NoError(t, err) + require.Equal(t, 0, count) + + // Test case 6: Count with non-existent type (should return 0) + count, err = store.CountByUserAndType(ctx, userUUID1, "non-existent-type") + require.NoError(t, err) + require.Equal(t, 0, count) +} + +func TestAgentInstanceStore_Delete_WithCascadingSoftDelete(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + instanceStore := database.NewAgentInstanceStoreWithDB(db) + sessionStore := database.NewAgentInstanceSessionStoreWithDB(db) + historyStore := database.NewAgentInstanceSessionHistoryStoreWithDB(db) + taskStore := database.NewAgentInstanceTaskStoreWithDB(db) + + // Create test data + userUUID := uuid.New().String() + instance := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "test-instance", + Public: false, + } + createdInstance, err := instanceStore.Create(ctx, instance) + require.NoError(t, err) + require.NotZero(t, createdInstance.ID) + + // Create sessions for this instance + session1 := &database.AgentInstanceSession{ + UUID: uuid.New().String(), + Name: "Session 1", + InstanceID: createdInstance.ID, + UserUUID: userUUID, + Type: "langflow", + } + createdSession1, err := sessionStore.Create(ctx, session1) + require.NoError(t, err) + + session2 := &database.AgentInstanceSession{ + UUID: uuid.New().String(), + Name: "Session 2", + InstanceID: createdInstance.ID, + UserUUID: userUUID, + Type: "langflow", + } + createdSession2, err := sessionStore.Create(ctx, session2) + require.NoError(t, err) + + // Create session histories for session1 + history1 := &database.AgentInstanceSessionHistory{ + UUID: uuid.New().String(), + SessionID: createdSession1.ID, + Request: true, + Turn: 1, + Content: "User message 1", + } + err = historyStore.Create(ctx, history1) + require.NoError(t, err) + + history2 := &database.AgentInstanceSessionHistory{ + UUID: uuid.New().String(), + SessionID: createdSession1.ID, + Request: false, + Turn: 1, + Content: "Assistant response 1", + } + err = historyStore.Create(ctx, history2) + require.NoError(t, err) + + // Create session history for session2 + history3 := &database.AgentInstanceSessionHistory{ + UUID: uuid.New().String(), + SessionID: createdSession2.ID, + Request: true, + Turn: 1, + Content: "User message 2", + } + err = historyStore.Create(ctx, history3) + require.NoError(t, err) + + // Create tasks for this instance + task1 := &database.AgentInstanceTask{ + InstanceID: createdInstance.ID, + TaskType: types.AgentTaskTypeFinetuneJob, + TaskID: "task-1", + SessionUUID: createdSession1.UUID, + UserUUID: userUUID, + } + _, err = taskStore.Create(ctx, task1) + require.NoError(t, err) + + task2 := &database.AgentInstanceTask{ + InstanceID: createdInstance.ID, + TaskType: types.AgentTaskTypeInference, + TaskID: "task-2", + SessionUUID: createdSession2.UUID, + UserUUID: userUUID, + } + _, err = taskStore.Create(ctx, task2) + require.NoError(t, err) + + // Verify all records exist before deletion + foundInstance, err := instanceStore.FindByID(ctx, createdInstance.ID) + require.NoError(t, err) + require.NotNil(t, foundInstance) + + sessions, _, err := sessionStore.ListByInstanceID(ctx, createdInstance.ID) + require.NoError(t, err) + require.Len(t, sessions, 2) + + histories1, err := historyStore.ListBySessionID(ctx, createdSession1.ID) + require.NoError(t, err) + require.Len(t, histories1, 2) + + histories2, err := historyStore.ListBySessionID(ctx, createdSession2.ID) + require.NoError(t, err) + require.Len(t, histories2, 1) + + // Delete the instance (should cascade soft-delete) + err = instanceStore.Delete(ctx, createdInstance.ID) + require.NoError(t, err) + + // Verify instance is deleted (not found in normal queries) + _, err = instanceStore.FindByID(ctx, createdInstance.ID) + require.Error(t, err, "Instance should not be found after delete") + + // Verify sessions are deleted (not found in normal queries) + sessions, _, err = sessionStore.ListByInstanceID(ctx, createdInstance.ID) + require.NoError(t, err) + require.Len(t, sessions, 0, "Sessions should not be found after delete") + + // Verify sessions are deleted + _, err = sessionStore.FindByID(ctx, createdSession1.ID) + require.Error(t, err, "Session 1 should not be found after delete") + + _, err = sessionStore.FindByID(ctx, createdSession2.ID) + require.Error(t, err, "Session 2 should not be found after delete") + + // Verify session histories are deleted (not found in normal queries) + histories1, err = historyStore.ListBySessionID(ctx, createdSession1.ID) + require.NoError(t, err) + require.Len(t, histories1, 0, "Session histories should not be found after delete") + + histories2, err = historyStore.ListBySessionID(ctx, createdSession2.ID) + require.NoError(t, err) + require.Len(t, histories2, 0, "Session histories should not be found after delete") + + // Verify tasks are deleted (not found in normal queries) + tasks, _, err := taskStore.ListTasks(ctx, userUUID, types.AgentTaskFilter{}, 10, 1) + require.NoError(t, err) + // Filter out tasks for our instance + taskCount := 0 + for _, task := range tasks { + if task.InstanceID == createdInstance.ID { + taskCount++ + } + } + require.Equal(t, 0, taskCount, "Tasks should not be found after delete") +} + +func TestAgentInstanceStore_Delete_WithNoRelatedRecords(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + // Create an instance with no related records + userUUID := uuid.New().String() + instance := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID, + Type: "langflow", + ContentID: "isolated-instance", + Public: false, + } + createdInstance, err := store.Create(ctx, instance) + require.NoError(t, err) + + // Delete the instance + err = store.Delete(ctx, createdInstance.ID) + require.NoError(t, err) + + // Verify instance is deleted (not found in normal queries) + _, err = store.FindByID(ctx, createdInstance.ID) + require.Error(t, err, "Instance should not be found after delete") +} + +func TestAgentInstanceStore_IsInstanceNameExists(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID1 := uuid.New().String() + userUUID2 := uuid.New().String() + instanceName := "test-instance-name" + + // Test case 1: Instance name does not exist + exists, err := store.IsInstanceNameExists(ctx, userUUID1, instanceName) + require.NoError(t, err) + require.False(t, exists) + + // Test case 2: Create an instance with the name and verify it exists + instance := &database.AgentInstance{ + TemplateID: 123, + UserUUID: userUUID1, + Type: "langflow", + ContentID: "content-123", + Name: instanceName, + Public: false, + } + + createdInstance, err := store.Create(ctx, instance) + require.NoError(t, err) + require.NotZero(t, createdInstance.ID) + + // Verify the instance name exists for user1 + exists, err = store.IsInstanceNameExists(ctx, userUUID1, instanceName) + require.NoError(t, err) + require.True(t, exists) + + // Test case 3: Different user, same instance name + exists, err = store.IsInstanceNameExists(ctx, userUUID2, instanceName) + require.NoError(t, err) + require.False(t, exists) + + // Test case 4: Same user, different instance name + exists, err = store.IsInstanceNameExists(ctx, userUUID1, "different-instance-name") + require.NoError(t, err) + require.False(t, exists) + + // Test case 5: Both user and instance name different + exists, err = store.IsInstanceNameExists(ctx, userUUID2, "different-instance-name") + require.NoError(t, err) + require.False(t, exists) +} + +func TestAgentInstanceStore_IsInstanceNameExists_MultipleInstances(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID1 := uuid.New().String() + userUUID2 := uuid.New().String() + + // Create multiple instances with different names for user1 + instances := []*database.AgentInstance{ + { + TemplateID: 1, + UserUUID: userUUID1, + Type: "langflow", + ContentID: "content-1", + Name: "instance-name-1", + Public: false, + }, + { + TemplateID: 2, + UserUUID: userUUID1, + Type: "agno", + ContentID: "content-2", + Name: "instance-name-2", + Public: false, + }, + { + TemplateID: 3, + UserUUID: userUUID1, + Type: "langflow", + ContentID: "content-3", + Name: "instance-name-3", + Public: false, + }, + } + + // Create all instances + for _, instance := range instances { + _, err := store.Create(ctx, instance) + require.NoError(t, err) + } + + // Test each instance name exists for user1 + for _, instance := range instances { + exists, err := store.IsInstanceNameExists(ctx, userUUID1, instance.Name) + require.NoError(t, err) + require.True(t, exists, "Instance name %s should exist for user %s", instance.Name, userUUID1) + } + + // Test instance names don't exist for user2 + for _, instance := range instances { + exists, err := store.IsInstanceNameExists(ctx, userUUID2, instance.Name) + require.NoError(t, err) + require.False(t, exists, "Instance name %s should not exist for user %s", instance.Name, userUUID2) + } + + // Create an instance for user2 with a different name + user2Instance := &database.AgentInstance{ + TemplateID: 4, + UserUUID: userUUID2, + Type: "code", + ContentID: "content-4", + Name: "user2-instance-name", + Public: false, + } + _, err := store.Create(ctx, user2Instance) + require.NoError(t, err) + + // Verify user2's instance name exists for user2 + exists, err := store.IsInstanceNameExists(ctx, userUUID2, user2Instance.Name) + require.NoError(t, err) + require.True(t, exists) + + // Verify user2's instance name doesn't exist for user1 + exists, err = store.IsInstanceNameExists(ctx, userUUID1, user2Instance.Name) + require.NoError(t, err) + require.False(t, exists) + + // Test non-existent combinations + testCases := []struct { + userUUID string + instanceName string + description string + }{ + {userUUID1, "non-existent-name", "user1 with non-existent name"}, + {userUUID2, "instance-name-1", "user2 with user1's instance name"}, + {uuid.New().String(), "instance-name-1", "non-existent user with existing name"}, + {uuid.New().String(), "non-existent-name", "non-existent user with non-existent name"}, + } + + for _, tc := range testCases { + exists, err := store.IsInstanceNameExists(ctx, tc.userUUID, tc.instanceName) + require.NoError(t, err) + require.False(t, exists, "Should not exist: %s", tc.description) + } +} + +func TestAgentInstanceStore_IsInstanceNameExists_EmptyParameters(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID := uuid.New().String() + + // Test with empty user UUID + exists, err := store.IsInstanceNameExists(ctx, "", "some-instance-name") + require.NoError(t, err) + require.False(t, exists) + + // Test with empty instance name + exists, err = store.IsInstanceNameExists(ctx, userUUID, "") + require.NoError(t, err) + require.False(t, exists) + + // Test with both empty + exists, err = store.IsInstanceNameExists(ctx, "", "") + require.NoError(t, err) + require.False(t, exists) +} + +func TestAgentInstanceStore_IsInstanceNameExists_SameNameDifferentUsers(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewAgentInstanceStoreWithDB(db) + + userUUID1 := uuid.New().String() + userUUID2 := uuid.New().String() + sharedName := "shared-instance-name" + + // Create instance for user1 with the name + instance1 := &database.AgentInstance{ + TemplateID: 1, + UserUUID: userUUID1, + Type: "langflow", + ContentID: "content-1", + Name: sharedName, + Public: false, + } + _, err := store.Create(ctx, instance1) + require.NoError(t, err) + + // Create instance for user2 with the same name + instance2 := &database.AgentInstance{ + TemplateID: 2, + UserUUID: userUUID2, + Type: "agno", + ContentID: "content-2", + Name: sharedName, + Public: false, + } + _, err = store.Create(ctx, instance2) + require.NoError(t, err) + + // Verify both users can have instances with the same name + exists, err := store.IsInstanceNameExists(ctx, userUUID1, sharedName) + require.NoError(t, err) + require.True(t, exists, "Instance name should exist for user1") + + exists, err = store.IsInstanceNameExists(ctx, userUUID2, sharedName) + require.NoError(t, err) + require.True(t, exists, "Instance name should exist for user2") + + // This confirms the query correctly filters by both user_uuid and name + // Both users can independently have instances with the same name +} diff --git a/common/errorx/error_agent.go b/common/errorx/error_agent.go index 95f0461f0..37fde0de4 100644 --- a/common/errorx/error_agent.go +++ b/common/errorx/error_agent.go @@ -4,6 +4,7 @@ const errAgentPrefix = "AGENT-ERR" const ( instanceQuotaExceeded = iota + instanceNameAlreadyExists ) var ( @@ -19,6 +20,19 @@ var ( // // zh-HK: 實例配額超出,智能體類型: {{.agent_type}}, 實例數量: {{.instance_count}},配額: {{.quota}} ErrInstanceQuotaExceeded error = CustomError{prefix: errAgentPrefix, code: instanceQuotaExceeded} + + // you have a instance with the same name + // + // Description: You have an instance with the same name. + // + // Description_ZH: 您已存在相同名称的实例。 + // + // en-US: You have a instance with the same name: {{.instance_name}} + // + // zh-CN: 您已存在相同名称的实例: {{.instance_name}} + // + // zh-HK: 您已存在相同名稱的實例: {{.instance_name}} + ErrInstanceNameAlreadyExists error = CustomError{prefix: errAgentPrefix, code: instanceNameAlreadyExists} ) func InstanceQuotaExceeded(err error, ctx context) error { @@ -30,3 +44,13 @@ func InstanceQuotaExceeded(err error, ctx context) error { } return customErr } + +func InstanceNameAlreadyExists(err error, ctx context) error { + customErr := CustomError{ + prefix: errAgentPrefix, + context: ctx, + err: err, + code: int(instanceNameAlreadyExists), + } + return customErr +} diff --git a/common/i18n/en-US/err_agent.json b/common/i18n/en-US/err_agent.json index 8a067d0fc..cfeadad77 100644 --- a/common/i18n/en-US/err_agent.json +++ b/common/i18n/en-US/err_agent.json @@ -1,5 +1,8 @@ { "error.AGENT-ERR-0": { "other": "Instance quota exceeded, agent type: {{.agent_type}}, instance count: {{.instance_count}}, quota: {{.quota}}" + }, + "error.AGENT-ERR-1": { + "other": "You have a instance with the same name: {{.instance_name}}" } } \ No newline at end of file diff --git a/common/i18n/zh-CN/err_agent.json b/common/i18n/zh-CN/err_agent.json index 287faafb9..31ed87815 100644 --- a/common/i18n/zh-CN/err_agent.json +++ b/common/i18n/zh-CN/err_agent.json @@ -1,5 +1,8 @@ { "error.AGENT-ERR-0": { "other": "实例配额超出,智能体类型: {{.agent_type}}, 实例数量: {{.instance_count}},配额: {{.quota}}" + }, + "error.AGENT-ERR-1": { + "other": "您已存在相同名称的实例: {{.instance_name}}" } } \ No newline at end of file diff --git a/common/i18n/zh-HK/err_agent.json b/common/i18n/zh-HK/err_agent.json index 1266aa92f..9d3b47528 100644 --- a/common/i18n/zh-HK/err_agent.json +++ b/common/i18n/zh-HK/err_agent.json @@ -1,5 +1,8 @@ { "error.AGENT-ERR-0": { "other": "實例配額超出,智能體類型: {{.agent_type}}, 實例數量: {{.instance_count}},配額: {{.quota}}" + }, + "error.AGENT-ERR-1": { + "other": "您已存在相同名稱的實例: {{.instance_name}}" } } \ No newline at end of file diff --git a/docs/error_codes_en.md b/docs/error_codes_en.md index 61f61810c..6049bc518 100644 --- a/docs/error_codes_en.md +++ b/docs/error_codes_en.md @@ -42,6 +42,14 @@ This document lists all the custom error codes defined in the project, categoriz - **Error Name:** `instanceQuotaExceeded` - **Description:** The instance quota exceeded. Includes agent type, instance count, and quota in the error message. +--- + +### `AGENT-ERR-1` + +- **Error Code:** `AGENT-ERR-1` +- **Error Name:** `instanceNameAlreadyExists` +- **Description:** You have an instance with the same name. + ## Auth Errors ### `AUTH-ERR-0` diff --git a/docs/error_codes_zh.md b/docs/error_codes_zh.md index 94458f3ea..d3271b408 100644 --- a/docs/error_codes_zh.md +++ b/docs/error_codes_zh.md @@ -42,6 +42,14 @@ - **错误名:** `instanceQuotaExceeded` - **描述:** 实例配额超出。错误消息中包含智能体类型、实例数量和配额。 +--- + +### `AGENT-ERR-1` + +- **错误代码:** `AGENT-ERR-1` +- **错误名:** `instanceNameAlreadyExists` +- **描述:** 您已存在相同名称的实例。 + ## Auth 错误 ### `AUTH-ERR-0`