Compare commits

...

7 Commits

54 changed files with 3873 additions and 244 deletions

1
.gitignore vendored
View File

@ -60,3 +60,4 @@ values-dev.yaml
*.tsbuildinfo
.coda/

View File

@ -51,7 +51,6 @@ import (
"github.com/coze-dev/coze-studio/backend/infra/contract/messages2query"
"github.com/coze-dev/coze-studio/backend/infra/contract/modelmgr"
"github.com/coze-dev/coze-studio/backend/infra/impl/cache/redis"
"github.com/coze-dev/coze-studio/backend/infra/impl/coderunner/direct"
"github.com/coze-dev/coze-studio/backend/infra/impl/coderunner/sandbox"
builtinNL2SQL "github.com/coze-dev/coze-studio/backend/infra/impl/document/nl2sql/builtin"
"github.com/coze-dev/coze-studio/backend/infra/impl/document/ocr/ppocr"
@ -346,40 +345,43 @@ func initKnowledgeEventBusProducer() (eventbus.Producer, error) {
}
func initCodeRunner() coderunner.Runner {
switch typ := os.Getenv(consts.CodeRunnerType); typ {
case "sandbox":
getAndSplit := func(key string) []string {
v := os.Getenv(key)
if v == "" {
return nil
}
return strings.Split(v, ",")
// 为了安全考虑移除不安全的direct runner强制使用sandbox
getAndSplit := func(key string) []string {
v := os.Getenv(key)
if v == "" {
return nil
}
config := &sandbox.Config{
AllowEnv: getAndSplit(consts.CodeRunnerAllowEnv),
AllowRead: getAndSplit(consts.CodeRunnerAllowRead),
AllowWrite: getAndSplit(consts.CodeRunnerAllowWrite),
AllowNet: getAndSplit(consts.CodeRunnerAllowNet),
AllowRun: getAndSplit(consts.CodeRunnerAllowRun),
AllowFFI: getAndSplit(consts.CodeRunnerAllowFFI),
NodeModulesDir: os.Getenv(consts.CodeRunnerNodeModulesDir),
TimeoutSeconds: 0,
MemoryLimitMB: 0,
}
if f, err := strconv.ParseFloat(os.Getenv(consts.CodeRunnerTimeoutSeconds), 64); err == nil {
config.TimeoutSeconds = f
} else {
config.TimeoutSeconds = 60.0
}
if mem, err := strconv.ParseInt(os.Getenv(consts.CodeRunnerMemoryLimitMB), 10, 64); err == nil {
config.MemoryLimitMB = mem
} else {
config.MemoryLimitMB = 100
}
return sandbox.NewRunner(config)
default:
return direct.NewRunner()
return strings.Split(v, ",")
}
// 使用安全的默认配置
config := &sandbox.Config{
AllowEnv: getAndSplit(consts.CodeRunnerAllowEnv), // 默认为空,禁止环境变量访问
AllowRead: getAndSplit(consts.CodeRunnerAllowRead), // 默认为空,禁止文件读取
AllowWrite: getAndSplit(consts.CodeRunnerAllowWrite), // 默认为空,禁止文件写入
AllowNet: getAndSplit(consts.CodeRunnerAllowNet), // 默认为空,禁止网络访问
AllowRun: getAndSplit(consts.CodeRunnerAllowRun), // 默认为空,禁止运行外部程序
AllowFFI: getAndSplit(consts.CodeRunnerAllowFFI), // 默认为空禁止FFI调用
NodeModulesDir: os.Getenv(consts.CodeRunnerNodeModulesDir),
TimeoutSeconds: 0,
MemoryLimitMB: 0,
}
// 设置安全的超时时间最大30秒
if f, err := strconv.ParseFloat(os.Getenv(consts.CodeRunnerTimeoutSeconds), 64); err == nil && f > 0 && f <= 30 {
config.TimeoutSeconds = f
} else {
config.TimeoutSeconds = 30.0 // 默认30秒超时
}
// 设置安全的内存限制最大100MB
if mem, err := strconv.ParseInt(os.Getenv(consts.CodeRunnerMemoryLimitMB), 10, 64); err == nil && mem > 0 && mem <= 100 {
config.MemoryLimitMB = mem
} else {
config.MemoryLimitMB = 100 // 默认100MB内存限制
}
return sandbox.NewRunner(config)
}
func initOCR() ocr.OCR {
@ -798,4 +800,4 @@ func getEmbedding(ctx context.Context) (embedding.Embedder, error) {
}
return emb, nil
}
}

View File

@ -21,6 +21,7 @@ import (
"encoding/json"
"errors"
"io"
"slices"
"strconv"
"github.com/cloudwego/eino/schema"
@ -102,7 +103,7 @@ func (a *OpenapiAgentRunApplication) checkConversation(ctx context.Context, ar *
return nil, err
}
if conData == nil {
return nil, errors.New("conversation data is nil")
return nil, errorx.New(errno.ErrConversationNotFound)
}
conversationData = conData
@ -110,7 +111,7 @@ func (a *OpenapiAgentRunApplication) checkConversation(ctx context.Context, ar *
}
if conversationData.CreatorID != userID {
return nil, errors.New("conversation data not match")
return nil, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg","user not match"))
}
return conversationData, nil
@ -138,26 +139,31 @@ func (a *OpenapiAgentRunApplication) buildAgentRunRequest(ctx context.Context, a
if err != nil {
return nil, err
}
multiContent, contentType, err := a.buildMultiContent(ctx, ar)
multiAdditionalMessages, err := a.parseAdditionalMessages(ctx, ar)
if err != nil {
return nil, err
}
filterMultiAdditionalMessages, multiContent, contentType, err := a.parseQueryContent(ctx, multiAdditionalMessages)
if err != nil {
return nil, err
}
displayContent := a.buildDisplayContent(ctx, ar)
arm := &entity.AgentRunMeta{
ConversationID: ptr.From(ar.ConversationID),
AgentID: ar.BotID,
Content: multiContent,
DisplayContent: displayContent,
SpaceID: spaceID,
UserID: ar.User,
SectionID: conversationData.SectionID,
PreRetrieveTools: shortcutCMDData,
IsDraft: false,
ConnectorID: connectorID,
ContentType: contentType,
Ext: ar.ExtraParams,
CustomVariables: ar.CustomVariables,
CozeUID: conversationData.CreatorID,
ConversationID: ptr.From(ar.ConversationID),
AgentID: ar.BotID,
Content: multiContent,
DisplayContent: displayContent,
SpaceID: spaceID,
UserID: ar.User,
SectionID: conversationData.SectionID,
PreRetrieveTools: shortcutCMDData,
IsDraft: false,
ConnectorID: connectorID,
ContentType: contentType,
Ext: ar.ExtraParams,
CustomVariables: ar.CustomVariables,
CozeUID: conversationData.CreatorID,
AdditionalMessages: filterMultiAdditionalMessages,
}
return arm, nil
}
@ -200,29 +206,68 @@ func (a *OpenapiAgentRunApplication) buildDisplayContent(_ context.Context, ar *
return ""
}
func (a *OpenapiAgentRunApplication) buildMultiContent(ctx context.Context, ar *run.ChatV3Request) ([]*message.InputMetaData, message.ContentType, error) {
var multiContents []*message.InputMetaData
contentType := message.ContentTypeText
func (a *OpenapiAgentRunApplication) parseQueryContent(ctx context.Context, multiAdditionalMessages []*entity.AdditionalMessage) ([]*entity.AdditionalMessage, []*message.InputMetaData, message.ContentType, error) {
var multiContent []*message.InputMetaData
var contentType message.ContentType
var filterMultiAdditionalMessages []*entity.AdditionalMessage
filterMultiAdditionalMessages = multiAdditionalMessages
if len(multiAdditionalMessages) > 0 {
lastMessage := multiAdditionalMessages[len(multiAdditionalMessages)-1]
if lastMessage != nil && lastMessage.Role == schema.User {
multiContent = lastMessage.Content
contentType = lastMessage.ContentType
filterMultiAdditionalMessages = multiAdditionalMessages[:len(multiAdditionalMessages)-1]
}
}
return filterMultiAdditionalMessages, multiContent, contentType, nil
}
func (a *OpenapiAgentRunApplication) parseAdditionalMessages(ctx context.Context, ar *run.ChatV3Request) ([]*entity.AdditionalMessage, error) {
additionalMessages := make([]*entity.AdditionalMessage, 0, len(ar.AdditionalMessages))
for _, item := range ar.AdditionalMessages {
if item == nil {
continue
}
if item.Role != string(schema.User) {
return nil, contentType, errors.New("role not match")
if item.Role != string(schema.User) && item.Role != string(schema.Assistant) {
return nil, errors.New("additional message role only support user and assistant")
}
if item.Type != nil && !slices.Contains([]message.MessageType{message.MessageTypeQuestion, message.MessageTypeAnswer}, message.MessageType(*item.Type)) {
return nil, errors.New("additional message type only support question and answer now")
}
addOne := entity.AdditionalMessage{
Role: schema.RoleType(item.Role),
}
if item.Type != nil {
addOne.Type = message.MessageType(*item.Type)
} else {
addOne.Type = message.MessageTypeQuestion
}
if item.ContentType == run.ContentTypeText {
if item.Content == "" {
continue
}
multiContents = append(multiContents, &message.InputMetaData{
addOne.ContentType = message.ContentTypeText
addOne.Content = []*message.InputMetaData{{
Type: message.InputTypeText,
Text: item.Content,
})
}}
}
if item.ContentType == run.ContentTypeMixApi {
contentType = message.ContentTypeMix
if ptr.From(item.Type) == string(message.MessageTypeAnswer) {
return nil, errors.New(" answer messages only support text content")
}
addOne.ContentType = message.ContentTypeMix
var inputs []*run.AdditionalContent
err := json.Unmarshal([]byte(item.Content), &inputs)
@ -236,7 +281,8 @@ func (a *OpenapiAgentRunApplication) buildMultiContent(ctx context.Context, ar *
}
switch message.InputType(one.Type) {
case message.InputTypeText:
multiContents = append(multiContents, &message.InputMetaData{
addOne.Content = append(addOne.Content, &message.InputMetaData{
Type: message.InputTypeText,
Text: ptr.From(one.Text),
})
@ -250,12 +296,12 @@ func (a *OpenapiAgentRunApplication) buildMultiContent(ctx context.Context, ar *
ID: one.GetFileID(),
})
if err != nil {
return nil, contentType, err
return nil, err
}
fileUrl = fileInfo.File.Url
fileURI = fileInfo.File.TosURI
}
multiContents = append(multiContents, &message.InputMetaData{
addOne.Content = append(addOne.Content, &message.InputMetaData{
Type: message.InputType(one.Type),
FileData: []*message.FileData{
{
@ -269,10 +315,10 @@ func (a *OpenapiAgentRunApplication) buildMultiContent(ctx context.Context, ar *
}
}
}
additionalMessages = append(additionalMessages, &addOne)
}
return multiContents, contentType, nil
return additionalMessages, nil
}
func (a *OpenapiAgentRunApplication) pullStream(ctx context.Context, sseSender *sseImpl.SSenderImpl, streamer *schema.StreamReader[*entity.AgentRunResponse]) {

View File

@ -0,0 +1,903 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package conversation
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"github.com/coze-dev/coze-studio/backend/api/model/conversation/common"
"github.com/coze-dev/coze-studio/backend/api/model/conversation/run"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
saEntity "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
convEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
openapiEntity "github.com/coze-dev/coze-studio/backend/domain/openauth/openapiauth/entity"
cmdEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
uploadEntity "github.com/coze-dev/coze-studio/backend/domain/upload/entity"
uploadService "github.com/coze-dev/coze-studio/backend/domain/upload/service"
sseImpl "github.com/coze-dev/coze-studio/backend/infra/impl/sse"
mockSingleAgent "github.com/coze-dev/coze-studio/backend/internal/mock/domain/agent/singleagent"
mockAgentRun "github.com/coze-dev/coze-studio/backend/internal/mock/domain/conversation/agentrun"
mockConversation "github.com/coze-dev/coze-studio/backend/internal/mock/domain/conversation/conversation"
mockShortcut "github.com/coze-dev/coze-studio/backend/internal/mock/domain/shortcutcmd"
mockUpload "github.com/coze-dev/coze-studio/backend/internal/mock/domain/upload"
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
func setupMocks(t *testing.T) (*OpenapiAgentRunApplication, *mockShortcut.MockShortcutCmd, *mockUpload.MockUploadService, *mockAgentRun.MockRun, *mockConversation.MockConversation, *mockSingleAgent.MockSingleAgent, *gomock.Controller) {
ctrl := gomock.NewController(t)
mockShortcutSvc := mockShortcut.NewMockShortcutCmd(ctrl)
mockUploadSvc := mockUpload.NewMockUploadService(ctrl)
mockAgentRunSvc := mockAgentRun.NewMockRun(ctrl)
mockConversationSvc := mockConversation.NewMockConversation(ctrl)
mockSingleAgentSvc := mockSingleAgent.NewMockSingleAgent(ctrl)
app := &OpenapiAgentRunApplication{
ShortcutDomainSVC: mockShortcutSvc,
UploaodDomainSVC: mockUploadSvc,
}
// Setup ConversationSVC mocks
originalConversationSVC := ConversationSVC
ConversationSVC = &ConversationApplicationService{
AgentRunDomainSVC: mockAgentRunSvc,
ConversationDomainSVC: mockConversationSvc,
appContext: &ServiceComponents{
SingleAgentDomainSVC: mockSingleAgentSvc,
},
}
t.Cleanup(func() {
ConversationSVC = originalConversationSVC
ctrl.Finish()
})
return app, mockShortcutSvc, mockUploadSvc, mockAgentRunSvc, mockConversationSvc, mockSingleAgentSvc, ctrl
}
func createTestContext() context.Context {
ctx := context.Background()
ctx = ctxcache.Init(ctx)
apiKey := &openapiEntity.ApiKey{
UserID: 12345,
ConnectorID: consts.CozeConnectorID,
}
ctxcache.Store(ctx, consts.OpenapiAuthKeyInCtx, apiKey)
return ctx
}
func createTestRequest() *run.ChatV3Request {
return &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: "Hello, world!",
ContentType: run.ContentTypeText,
},
},
}
}
func createTestRequestWithMultipleMessages() *run.ChatV3Request {
return &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: "Hello, I need help with something.",
ContentType: run.ContentTypeText,
},
{
Role: "assistant",
Content: "Sure, I'd be happy to help! What do you need assistance with?",
ContentType: run.ContentTypeText,
},
{
Role: "user",
Content: `{"type": "image", "url": "https://example.com/image.jpg"}`,
ContentType: run.ContentTypeImage,
},
{
Role: "user",
Content: `{"type": "file", "name": "document.pdf", "url": "https://example.com/doc.pdf"}`,
ContentType: run.ContentTypeFile,
},
},
}
}
func createTestRequestWithAssistantOnly() *run.ChatV3Request {
return &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "assistant",
Content: "I'm here to help you with any questions you might have.",
ContentType: run.ContentTypeText, // assistant role only supports text content type
},
},
}
}
func TestOpenapiAgentRun_Success(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
// Mock agent check
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
}
func TestOpenapiAgentRun_CheckAgentError(t *testing.T) {
app, _, _, _, _, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
// Mock agent check failure
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(nil, errors.New("agent not found"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "agent not found")
}
func TestOpenapiAgentRun_AgentNotExists(t *testing.T) {
app, _, _, _, _, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
// Mock agent check returns nil (agent not exists)
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(nil, nil)
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
}
func TestOpenapiAgentRun_CheckConversationError(t *testing.T) {
app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check failure
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(nil, errors.New("conversation not found"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "conversation not found")
}
func TestOpenapiAgentRun_ConversationPermissionError(t *testing.T) {
app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation with different creator
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 99999, // Different from user ID (12345)
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
}
func TestOpenapiAgentRun_CreateNewConversation(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
req.ConversationID = ptr.Of(int64(0)) // No conversation ID
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock create new conversation
mockConv := &convEntity.Conversation{
ID: 22222,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().Create(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, meta *convEntity.CreateMeta) (*convEntity.Conversation, error) {
assert.Equal(t, int64(67890), meta.AgentID)
assert.Equal(t, int64(12345), meta.UserID)
assert.Equal(t, common.Scene_SceneOpenApi, meta.Scene)
return mockConv, nil
})
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Equal(t, int64(22222), *req.ConversationID) // Should be updated
}
func TestOpenapiAgentRun_AgentRunError(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("agent run failed"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "agent run failed")
}
func TestOpenapiAgentRun_WithShortcutCommand(t *testing.T) {
app, mockShortcut, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequest()
req.ShortcutCommand = &run.ShortcutCommandDetail{
CommandID: 123,
Parameters: map[string]string{"param1": "value1"},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock shortcut command
mockCmd := &cmdEntity.ShortcutCmd{
ID: 123,
PluginID: 456,
PluginToolName: "test-tool",
PluginToolID: 789,
ToolType: 1,
}
mockShortcut.EXPECT().GetByCmdID(ctx, int64(123), int32(0)).Return(mockCmd, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
}
func TestOpenapiAgentRun_WithMultipleMessages(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequestWithMultipleMessages()
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
// Verify that the request contains multiple messages with different roles and content types
assert.Len(t, req.AdditionalMessages, 4)
assert.Equal(t, "user", req.AdditionalMessages[0].Role)
assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[0].ContentType)
assert.Equal(t, "assistant", req.AdditionalMessages[1].Role)
assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[1].ContentType)
assert.Equal(t, "user", req.AdditionalMessages[2].Role)
assert.Equal(t, run.ContentTypeImage, req.AdditionalMessages[2].ContentType)
assert.Equal(t, "user", req.AdditionalMessages[3].Role)
assert.Equal(t, run.ContentTypeFile, req.AdditionalMessages[3].ContentType)
}
func TestOpenapiAgentRun_WithAssistantMessage(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
req := createTestRequestWithAssistantOnly()
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
// Verify that the assistant message only supports text content type
assert.Len(t, req.AdditionalMessages, 1)
assert.Equal(t, "assistant", req.AdditionalMessages[0].Role)
assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[0].ContentType)
}
func TestOpenapiAgentRun_WithMixedContentTypes(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with various content types for user role
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: "Here's a text message",
ContentType: run.ContentTypeText,
},
{
Role: "user",
Content: `{"type": "audio", "url": "https://example.com/audio.mp3"}`,
ContentType: run.ContentTypeAudio,
},
{
Role: "user",
Content: `{"type": "video", "url": "https://example.com/video.mp4"}`,
ContentType: run.ContentTypeVideo,
},
{
Role: "assistant",
Content: "I can only respond with text content.",
ContentType: run.ContentTypeText, // assistant must use text
},
{
Role: "user",
Content: `{"type": "link", "url": "https://example.com"}`,
ContentType: run.ContentTypeLink,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
// Verify various content types are preserved
assert.Len(t, req.AdditionalMessages, 5)
// Check user messages with different content types
assert.Equal(t, "user", req.AdditionalMessages[0].Role)
assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[0].ContentType)
assert.Equal(t, "user", req.AdditionalMessages[1].Role)
assert.Equal(t, run.ContentTypeAudio, req.AdditionalMessages[1].ContentType)
assert.Equal(t, "user", req.AdditionalMessages[2].Role)
assert.Equal(t, run.ContentTypeVideo, req.AdditionalMessages[2].ContentType)
// Check assistant message (must be text)
assert.Equal(t, "assistant", req.AdditionalMessages[3].Role)
assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[3].ContentType)
assert.Equal(t, "user", req.AdditionalMessages[4].Role)
assert.Equal(t, run.ContentTypeLink, req.AdditionalMessages[4].ContentType)
}
func TestOpenapiAgentRun_ParseAdditionalMessages_InvalidRole(t *testing.T) {
app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with invalid role
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "system", // Invalid role
Content: "System message",
ContentType: run.ContentTypeText,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success to reach parseAdditionalMessages
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "additional message role only support user and assistant")
}
func TestOpenapiAgentRun_ParseAdditionalMessages_InvalidType(t *testing.T) {
app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with invalid message type
invalidType := "invalid_type"
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: "Test message",
ContentType: run.ContentTypeText,
Type: &invalidType, // Invalid type
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success to reach parseAdditionalMessages
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "additional message type only support question and answer now")
}
func TestOpenapiAgentRun_ParseAdditionalMessages_AnswerWithNonTextContent(t *testing.T) {
app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with answer type but non-text content
answerType := "answer"
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "assistant",
Content: `[{"type": "image", "file_url": "https://example.com/image.jpg"}]`,
ContentType: run.ContentTypeMixApi, // object_string
Type: &answerType,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success to reach parseAdditionalMessages
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "answer messages only support text content")
}
func TestOpenapiAgentRun_ParseAdditionalMessages_MixApiWithFileURL(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with object_string content type and file URL
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: `[{"type": "text", "text": "Here's an image:"}, {"type": "image", "file_url": "https://example.com/image.jpg"}]`,
ContentType: run.ContentTypeMixApi,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
}
func TestOpenapiAgentRun_ParseAdditionalMessages_MixApiWithFileID(t *testing.T) {
app, _, mockUpload, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with object_string content type and file ID
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: `[{"type": "file", "file_id": "12345"}]`,
ContentType: run.ContentTypeMixApi,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock upload service to return file info
mockUpload.EXPECT().GetFile(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, req *uploadService.GetFileRequest) (*uploadService.GetFileResponse, error) {
assert.Equal(t, int64(12345), req.ID)
return &uploadService.GetFileResponse{
File: &uploadEntity.File{
Url: "https://example.com/file.pdf",
TosURI: "tos://bucket/file.pdf",
},
}, nil
})
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
}
func TestOpenapiAgentRun_ParseAdditionalMessages_FileIDError(t *testing.T) {
app, _, mockUpload, _, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with object_string content type and file ID that will fail
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: `[{"type": "file", "file_id": "99999"}]`,
ContentType: run.ContentTypeMixApi,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock upload service to return error
mockUpload.EXPECT().GetFile(ctx, gomock.Any()).Return(nil, errors.New("file not found"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "file not found")
}
func TestOpenapiAgentRun_ParseAdditionalMessages_EmptyContent(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with empty text content (should be skipped)
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: "", // Empty content
ContentType: run.ContentTypeText,
},
{
Role: "user",
Content: "Valid content",
ContentType: run.ContentTypeText,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
// Verify that only the non-empty message is included
assert.Len(t, req.AdditionalMessages, 2) // Original request still has 2 messages
}
func TestOpenapiAgentRun_ParseAdditionalMessages_NilMessage(t *testing.T) {
app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t)
ctx := createTestContext()
// Create request with empty content message (should be skipped)
req := &run.ChatV3Request{
BotID: 67890,
ConversationID: ptr.Of(int64(11111)),
User: "test-user",
AdditionalMessages: []*run.EnterMessage{
{
Role: "user",
Content: "", // Empty content message
ContentType: run.ContentTypeText,
},
{
Role: "user",
Content: "Valid content",
ContentType: run.ContentTypeText,
},
},
}
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
// Mock conversation check success
mockConv := &convEntity.Conversation{
ID: 11111,
CreatorID: 12345,
SectionID: 98765,
}
mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil)
// Mock agent run failure to avoid pullStream complexity
mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error"))
err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock stream error")
}

View File

@ -30,6 +30,7 @@ type Message interface {
GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*message.Message, error)
PreCreate(ctx context.Context, msg *message.Message) (*message.Message, error)
Create(ctx context.Context, msg *message.Message) (*message.Message, error)
BatchCreate(ctx context.Context, msg []*message.Message) ([]*message.Message, error)
List(ctx context.Context, meta *entity.ListMeta) (*entity.ListResult, error)
ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error)
Edit(ctx context.Context, msg *message.Message) (*message.Message, error)

View File

@ -59,6 +59,21 @@ func (m *MockMessage) EXPECT() *MockMessageMockRecorder {
return m.recorder
}
// BatchCreate mocks base method.
func (m *MockMessage) BatchCreate(ctx context.Context, msg []*message.Message) ([]*message.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BatchCreate", ctx, msg)
ret0, _ := ret[0].([]*message.Message)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BatchCreate indicates an expected call of BatchCreate.
func (mr *MockMessageMockRecorder) BatchCreate(ctx, msg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCreate", reflect.TypeOf((*MockMessage)(nil).BatchCreate), ctx, msg)
}
// Create mocks base method.
func (m *MockMessage) Create(ctx context.Context, msg *message.Message) (*message.Message, error) {
m.ctrl.T.Helper()

View File

@ -170,6 +170,9 @@ func (c *impl) GetMessageByID(ctx context.Context, id int64) (*entity.Message, e
func (c *impl) ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) {
return c.DomainSVC.ListWithoutPair(ctx, req)
}
func (c *impl) BatchCreate(ctx context.Context, msgs []*entity.Message) ([]*entity.Message, error) {
return c.DomainSVC.BatchCreate(ctx, msgs)
}
func convertToConvAndSchemaMessage(ctx context.Context, msgs []*entity.Message) ([]*crossmessage.WfMessage, []*schema.Message, error) {
messages := make([]*schema.Message, 0)

View File

@ -25,6 +25,7 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
)
//go:generate mockgen -destination ../../../../internal/mock/domain/agent/singleagent/single_agent_mock.go --package singleagent -source single_agent.go
type SingleAgent interface {
// draft agent
CreateSingleAgentDraft(ctx context.Context, creatorID int64, draft *entity.SingleAgent) (agentID int64, err error)

View File

@ -106,24 +106,34 @@ type MetaInfo struct {
}
type AgentRunMeta struct {
ConversationID int64 `json:"conversation_id"`
ConnectorID int64 `json:"connector_id"`
SpaceID int64 `json:"space_id"`
Scene common.Scene `json:"scene"`
SectionID int64 `json:"section_id"`
Name string `json:"name"`
UserID string `json:"user_id"`
CozeUID int64 `json:"coze_uid"`
AgentID int64 `json:"agent_id"`
ContentType message.ContentType `json:"content_type"`
Content []*message.InputMetaData `json:"content"`
PreRetrieveTools []*Tool `json:"tools"`
IsDraft bool `json:"is_draft"`
CustomerConfig *CustomerConfig `json:"customer_config"`
DisplayContent string `json:"display_content"`
CustomVariables map[string]string `json:"custom_variables"`
Version string `json:"version"`
Ext map[string]string `json:"ext"`
ConversationID int64 `json:"conversation_id"`
ConnectorID int64 `json:"connector_id"`
SpaceID int64 `json:"space_id"`
Scene common.Scene `json:"scene"`
SectionID int64 `json:"section_id"`
Name string `json:"name"`
UserID string `json:"user_id"`
CozeUID int64 `json:"coze_uid"`
AgentID int64 `json:"agent_id"`
ContentType message.ContentType `json:"content_type"`
Content []*message.InputMetaData `json:"content"`
PreRetrieveTools []*Tool `json:"tools"`
IsDraft bool `json:"is_draft"`
CustomerConfig *CustomerConfig `json:"customer_config"`
DisplayContent string `json:"display_content"`
CustomVariables map[string]string `json:"custom_variables"`
Version string `json:"version"`
Ext map[string]string `json:"ext"`
AdditionalMessages []*AdditionalMessage `json:"additional_messages"`
}
type AdditionalMessage struct {
Role schema.RoleType `json:"role"`
Type message.MessageType `json:"type"`
Content []*message.InputMetaData `json:"content"`
ContentType message.ContentType `json:"content_type"`
Name *string `json:"name"`
Meta map[string]string `json:"meta"`
}
type UpdateMeta struct {

View File

@ -41,7 +41,7 @@ import (
func (art *AgentRuntime) ChatflowRun(ctx context.Context, imagex imagex.ImageX) (err error) {
mh := &MesssageEventHanlder{
mh := &MessageEventHandler{
sw: art.SW,
messageEvent: art.MessageEvent,
}
@ -110,7 +110,7 @@ func concatWfInput(rtDependence *AgentRuntime) string {
return strings.Trim(input, ",")
}
func (art *AgentRuntime) pullWfStream(ctx context.Context, events *schema.StreamReader[*crossworkflow.WorkflowMessage], mh *MesssageEventHanlder) {
func (art *AgentRuntime) pullWfStream(ctx context.Context, events *schema.StreamReader[*crossworkflow.WorkflowMessage], mh *MessageEventHandler) {
fullAnswerContent := bytes.NewBuffer([]byte{})
var usage *msgEntity.UsageExt

View File

@ -221,6 +221,51 @@ func preCreateAnswer(ctx context.Context, rtDependence *AgentRuntime) (*msgEntit
return crossmessage.DefaultSVC().PreCreate(ctx, msgMeta)
}
func buildAdditionalMessage2Create(ctx context.Context, runRecord *entity.RunRecordMeta, additionalMessage *entity.AdditionalMessage, userID string) *message.Message {
msg := &msgEntity.Message{
ConversationID: runRecord.ConversationID,
RunID: runRecord.ID,
AgentID: runRecord.AgentID,
SectionID: runRecord.SectionID,
UserID: userID,
MessageType: additionalMessage.Type,
}
switch additionalMessage.Type {
case message.MessageTypeQuestion:
msg.Role = schema.User
msg.ContentType = additionalMessage.ContentType
for _, content := range additionalMessage.Content {
if content.Type == message.InputTypeText {
msg.Content = content.Text
break
}
}
msg.MultiContent = additionalMessage.Content
case message.MessageTypeAnswer:
msg.Role = schema.Assistant
msg.ContentType = message.ContentTypeText
for _, content := range additionalMessage.Content {
if content.Type == message.InputTypeText {
msg.Content = content.Text
break
}
}
modelContent := &schema.Message{
Role: schema.Assistant,
Content: msg.Content,
}
jsonContent, err := json.Marshal(modelContent)
if err == nil {
msg.ModelContent = string(jsonContent)
}
}
return msg
}
func buildAgentMessage2Create(ctx context.Context, chunk *entity.AgentRespEvent, messageType message.MessageType, rtDependence *AgentRuntime) *message.Message {
arm := rtDependence.GetRunMeta()
msg := &msgEntity.Message{

View File

@ -98,12 +98,12 @@ func (e *Event) SendStreamDoneEvent(sw *schema.StreamWriter[*entity.AgentRunResp
sw.Send(resp, nil)
}
type MesssageEventHanlder struct {
type MessageEventHandler struct {
messageEvent *Event
sw *schema.StreamWriter[*entity.AgentRunResponse]
}
func (mh *MesssageEventHanlder) handlerErr(_ context.Context, err error) {
func (mh *MessageEventHandler) handlerErr(_ context.Context, err error) {
var errMsg string
var statusErr errorx.StatusError
@ -123,7 +123,7 @@ func (mh *MesssageEventHanlder) handlerErr(_ context.Context, err error) {
})
}
func (mh *MesssageEventHanlder) handlerAckMessage(_ context.Context, input *msgEntity.Message) error {
func (mh *MessageEventHandler) handlerAckMessage(_ context.Context, input *msgEntity.Message) error {
sendMsg := &entity.ChunkMessageItem{
ID: input.ID,
ConversationID: input.ConversationID,
@ -142,7 +142,7 @@ func (mh *MesssageEventHanlder) handlerAckMessage(_ context.Context, input *msgE
return nil
}
func (mh *MesssageEventHanlder) handlerFunctionCall(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
func (mh *MessageEventHandler) handlerFunctionCall(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeFunctionCall, rtDependence)
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
@ -156,7 +156,7 @@ func (mh *MesssageEventHanlder) handlerFunctionCall(ctx context.Context, chunk *
return nil
}
func (mh *MesssageEventHanlder) handlerTooResponse(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, preToolResponseMsg *msgEntity.Message, toolResponseMsgContent string) error {
func (mh *MessageEventHandler) handlerTooResponse(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, preToolResponseMsg *msgEntity.Message, toolResponseMsgContent string) error {
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeToolResponse, rtDependence)
@ -184,7 +184,7 @@ func (mh *MesssageEventHanlder) handlerTooResponse(ctx context.Context, chunk *e
return nil
}
func (mh *MesssageEventHanlder) handlerSuggest(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
func (mh *MessageEventHandler) handlerSuggest(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeFlowUp, rtDependence)
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
@ -199,7 +199,7 @@ func (mh *MesssageEventHanlder) handlerSuggest(ctx context.Context, chunk *entit
return nil
}
func (mh *MesssageEventHanlder) handlerKnowledge(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
func (mh *MessageEventHandler) handlerKnowledge(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeKnowledge, rtDependence)
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
if err != nil {
@ -212,7 +212,7 @@ func (mh *MesssageEventHanlder) handlerKnowledge(ctx context.Context, chunk *ent
return nil
}
func (mh *MesssageEventHanlder) handlerAnswer(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt, rtDependence *AgentRuntime, preAnswerMsg *msgEntity.Message) error {
func (mh *MessageEventHandler) handlerAnswer(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt, rtDependence *AgentRuntime, preAnswerMsg *msgEntity.Message) error {
if len(msg.Content) == 0 && len(ptr.From(msg.ReasoningContent)) == 0 {
return nil
@ -265,7 +265,7 @@ func (mh *MesssageEventHanlder) handlerAnswer(ctx context.Context, msg *entity.C
return nil
}
func (mh *MesssageEventHanlder) handlerFinalAnswerFinish(ctx context.Context, rtDependence *AgentRuntime) error {
func (mh *MessageEventHandler) handlerFinalAnswerFinish(ctx context.Context, rtDependence *AgentRuntime) error {
cm := buildAgentMessage2Create(ctx, nil, message.MessageTypeVerbose, rtDependence)
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
if err != nil {
@ -278,7 +278,7 @@ func (mh *MesssageEventHanlder) handlerFinalAnswerFinish(ctx context.Context, rt
return nil
}
func (mh *MesssageEventHanlder) handlerInterruptVerbose(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
func (mh *MessageEventHandler) handlerInterruptVerbose(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeInterrupt, rtDependence)
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
if err != nil {
@ -291,7 +291,7 @@ func (mh *MesssageEventHanlder) handlerInterruptVerbose(ctx context.Context, chu
return nil
}
func (mh *MesssageEventHanlder) handlerWfUsage(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt) error {
func (mh *MessageEventHandler) handlerWfUsage(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt) error {
if msg.Ext == nil {
msg.Ext = map[string]string{}
@ -314,7 +314,7 @@ func (mh *MesssageEventHanlder) handlerWfUsage(ctx context.Context, msg *entity.
return nil
}
func (mh *MesssageEventHanlder) handlerInterrupt(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, firstAnswerMsg *msgEntity.Message, reasoningContent string) error {
func (mh *MessageEventHandler) handlerInterrupt(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, firstAnswerMsg *msgEntity.Message, reasoningContent string) error {
interruptData, cType, err := parseInterruptData(ctx, chunk.Interrupt)
if err != nil {
return err
@ -366,7 +366,7 @@ func (mh *MesssageEventHanlder) handlerInterrupt(ctx context.Context, chunk *ent
return nil
}
func (mh *MesssageEventHanlder) handlerWfInterruptMsg(ctx context.Context, stateMsg *crossworkflow.StateMessage, rtDependence *AgentRuntime) {
func (mh *MessageEventHandler) handlerWfInterruptMsg(ctx context.Context, stateMsg *crossworkflow.StateMessage, rtDependence *AgentRuntime) {
interruptData, cType, err := handlerWfInterruptEvent(ctx, stateMsg.InterruptEvent)
if err != nil {
return
@ -412,7 +412,7 @@ func (mh *MesssageEventHanlder) handlerWfInterruptMsg(ctx context.Context, state
}
}
func (mh *MesssageEventHanlder) HandlerInput(ctx context.Context, rtDependence *AgentRuntime) (*msgEntity.Message, error) {
func (mh *MessageEventHandler) HandlerInput(ctx context.Context, rtDependence *AgentRuntime) (*msgEntity.Message, error) {
msgMeta := buildAgentMessage2Create(ctx, nil, message.MessageTypeQuestion, rtDependence)
cm, err := crossmessage.DefaultSVC().Create(ctx, msgMeta)
@ -426,3 +426,21 @@ func (mh *MesssageEventHanlder) HandlerInput(ctx context.Context, rtDependence *
}
return cm, nil
}
func (mh *MessageEventHandler) ParseAdditionalMessages(ctx context.Context, rtDependence *AgentRuntime, runRecord *entity.RunRecordMeta) error {
if len(rtDependence.GetRunMeta().AdditionalMessages) == 0 {
return nil
}
additionalMessages := make([]*message.Message, 0, len(rtDependence.GetRunMeta().AdditionalMessages))
for _, msg := range rtDependence.GetRunMeta().AdditionalMessages {
cm := buildAdditionalMessage2Create(ctx, runRecord, msg, rtDependence.GetRunMeta().UserID)
additionalMessages = append(additionalMessages, cm)
}
_, err := crossmessage.DefaultSVC().BatchCreate(ctx, additionalMessages)
return err
}

View File

@ -107,6 +107,11 @@ func (rd *AgentRuntime) GetHistory() []*msgEntity.Message {
func (art *AgentRuntime) Run(ctx context.Context) (err error) {
mh := &MessageEventHandler{
messageEvent: art.MessageEvent,
sw: art.SW,
}
agentInfo, err := getAgentInfo(ctx, art.GetRunMeta().AgentID, art.GetRunMeta().IsDraft, art.GetRunMeta().ConnectorID)
if err != nil {
return
@ -114,6 +119,18 @@ func (art *AgentRuntime) Run(ctx context.Context) (err error) {
art.SetAgentInfo(agentInfo)
if len(art.GetRunMeta().AdditionalMessages) > 0 {
var additionalRunRecord *entity.RunRecordMeta
additionalRunRecord, err = art.RunRecordRepo.Create(ctx, art.GetRunMeta())
if err != nil {
return
}
err = mh.ParseAdditionalMessages(ctx, art, additionalRunRecord)
if err != nil {
return
}
}
history, err := art.getHistory(ctx)
if err != nil {
return
@ -140,10 +157,7 @@ func (art *AgentRuntime) Run(ctx context.Context) (err error) {
}
art.RunProcess.StepToComplete(ctx, srRecord, art.SW, art.GetUsage())
}()
mh := &MesssageEventHanlder{
messageEvent: art.MessageEvent,
sw: art.SW,
}
input, err := mh.HandlerInput(ctx, art)
if err != nil {
return

View File

@ -80,7 +80,7 @@ func (art *AgentRuntime) AgentStreamExecute(ctx context.Context, imagex imagex.I
func (art *AgentRuntime) push(ctx context.Context, mainChan chan *entity.AgentRespEvent) {
mh := &MesssageEventHanlder{
mh := &MessageEventHandler{
sw: art.SW,
messageEvent: art.MessageEvent,
}

View File

@ -24,6 +24,7 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
)
//go:generate mockgen -destination ../../../../internal/mock/domain/conversation/agentrun/agent_run_mock.go --package agentrun -source agent_run.go
type Run interface {
AgentRun(ctx context.Context, req *entity.AgentRunMeta) (*schema.StreamReader[*entity.AgentRunResponse], error)
Delete(ctx context.Context, runID []int64) error

View File

@ -22,6 +22,7 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
)
//go:generate mockgen -destination ../../../../internal/mock/domain/conversation/conversation/conversation_mock.go --package conversation -source conversation.go
type Conversation interface {
Create(ctx context.Context, req *entity.CreateMeta) (*entity.Conversation, error)
GetByID(ctx context.Context, id int64) (*entity.Conversation, error)

View File

@ -72,6 +72,25 @@ func (dao *MessageDAO) Create(ctx context.Context, msg *entity.Message) (*entity
return dao.messagePO2DO(poData), nil
}
func (dao *MessageDAO) BatchCreate(ctx context.Context, msg []*entity.Message) ([]*entity.Message, error) {
poList := make([]*model.Message, 0, len(msg))
for _, m := range msg {
po, err := dao.messageDO2PO(ctx, m)
if err != nil {
return nil, err
}
poList = append(poList, po)
}
do := dao.query.Message.WithContext(ctx).Debug()
cErr := do.CreateInBatches(poList, len(poList))
if cErr != nil {
return nil, cErr
}
return dao.batchMessagePO2DO(poList), nil
}
func (dao *MessageDAO) List(ctx context.Context, listMeta *entity.ListMeta) ([]*entity.Message, bool, error) {
m := dao.query.Message
do := m.WithContext(ctx).Debug().Where(m.ConversationID.Eq(listMeta.ConversationID)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable)))

View File

@ -34,6 +34,7 @@ func NewMessageRepo(db *gorm.DB, idGen idgen.IDGenerator) MessageRepo {
type MessageRepo interface {
PreCreate(ctx context.Context, msg *entity.Message) (*entity.Message, error)
Create(ctx context.Context, msg *entity.Message) (*entity.Message, error)
BatchCreate(ctx context.Context, msg []*entity.Message) ([]*entity.Message, error)
List(ctx context.Context, listMeta *entity.ListMeta) ([]*entity.Message, bool, error)
GetByRunIDs(ctx context.Context, runIDs []int64, orderBy string) ([]*entity.Message, error)
Edit(ctx context.Context, msgID int64, message *message.Message) (int64, error)

View File

@ -27,6 +27,7 @@ type Message interface {
ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error)
PreCreate(ctx context.Context, req *entity.Message) (*entity.Message, error)
Create(ctx context.Context, req *entity.Message) (*entity.Message, error)
BatchCreate(ctx context.Context, req []*entity.Message) ([]*entity.Message, error)
GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*entity.Message, error)
GetByID(ctx context.Context, id int64) (*entity.Message, error)
Edit(ctx context.Context, req *entity.Message) (*entity.Message, error)

View File

@ -124,6 +124,10 @@ func (m *messageImpl) GetByID(ctx context.Context, id int64) (*entity.Message, e
return m.MessageRepo.GetByID(ctx, id)
}
func (m *messageImpl) BatchCreate(ctx context.Context, req []*entity.Message) ([]*entity.Message, error) {
return m.MessageRepo.BatchCreate(ctx, req)
}
func (m *messageImpl) Broken(ctx context.Context, req *entity.BrokenMeta) error {
_, err := m.MessageRepo.Edit(ctx, req.ID, &message.Message{

View File

@ -494,3 +494,55 @@ func TestListWithoutPair(t *testing.T) {
assert.Equal(t, "Answer message", resp.Messages[0].Content)
})
}
func TestBatchCreate(t *testing.T) {
ctx := context.Background()
mockDBGen := orm.NewMockDB()
mockDBGen.AddTable(&model.Message{})
mockDB, err := mockDBGen.DB()
assert.NoError(t, err)
components := &Components{
MessageRepo: repository.NewMessageRepo(mockDB, nil),
}
t.Run("success_single_message", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 准备测试数据
inputMsgs := []*entity.Message{
{
ID: 1,
ConversationID: 100,
RunID: 200,
AgentID: 300,
UserID: "user123",
Content: "Hello World",
Role: schema.User,
ContentType: message.ContentTypeText,
MessageType: message.MessageTypeQuestion,
Status: message.MessageStatusAvailable,
},
{
ID: 2,
ConversationID: 100,
RunID: 200,
AgentID: 300,
UserID: "user123",
Content: "Hello World",
Role: schema.Assistant,
ContentType: message.ContentTypeText,
MessageType: message.MessageTypeQuestion,
Status: message.MessageStatusAvailable,
},
}
result, err := NewService(components).BatchCreate(ctx, inputMsgs)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.Equal(t, inputMsgs[1].ID, result[1].ID)
})
}

View File

@ -22,6 +22,7 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
)
//go:generate mockgen -destination ../../../internal/mock/domain/shortcutcmd/shortcut_cmd_mock.go --package shortcutcmd -source shortcut_cmd.go
type ShortcutCmd interface {
ListCMD(ctx context.Context, lm *entity.ListMeta) ([]*entity.ShortcutCmd, error)
CreateCMD(ctx context.Context, shortcut *entity.ShortcutCmd) (*entity.ShortcutCmd, error)

View File

@ -22,6 +22,7 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/upload/entity"
)
//go:generate mockgen -destination ../../../internal/mock/domain/upload/upload_service_mock.go --package upload -source interface.go
type UploadService interface {
UploadFile(ctx context.Context, req *UploadFileRequest) (resp *UploadFileResponse, err error)
UploadFiles(ctx context.Context, req *UploadFilesRequest) (resp *UploadFilesResponse, err error)

View File

@ -609,7 +609,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
Category: "", // Not found in cate_list
Desc: "comment_desc", // Placeholder from JSON
Color: "",
SupportBatch: false, // supportBatch: 1
SupportBatch: false, // supportBatch: 1
EnUSName: "Comment",
},
NodeTypeVariableAggregator: {

View File

@ -19,6 +19,7 @@ package vo
import (
"errors"
"fmt"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"

View File

@ -446,7 +446,7 @@ func PruneIsolatedNodes(nodes []*vo.Node, edges []*vo.Edge, parentNode *vo.Node)
func parseBatchMode(n *vo.Node) (
batchN *vo.Node, // the new batch node
enabled bool, // whether the node has enabled batch mode
enabled bool, // whether the node has enabled batch mode
err error) {
if n.Data == nil || n.Data.Inputs == nil {
return nil, false, nil

View File

@ -27,17 +27,17 @@ import (
func TestAddFieldMappingsWithDeduplication(t *testing.T) {
tests := []struct {
name string
name string
initialCarryOvers map[vo.NodeKey][]*compose.FieldMapping
fromNodeKey vo.NodeKey
fieldMappings []*compose.FieldMapping
expectedCount int
description string
fromNodeKey vo.NodeKey
fieldMappings []*compose.FieldMapping
expectedCount int
description string
}{
{
name: "empty_carry_overs",
name: "empty_carry_overs",
initialCarryOvers: make(map[vo.NodeKey][]*compose.FieldMapping),
fromNodeKey: "node1",
fromNodeKey: "node1",
fieldMappings: []*compose.FieldMapping{
compose.MapFieldPaths(compose.FieldPath{"input1"}, compose.FieldPath{"output1"}),
compose.MapFieldPaths(compose.FieldPath{"input2"}, compose.FieldPath{"output2"}),
@ -94,7 +94,7 @@ func TestAddFieldMappingsWithDeduplication(t *testing.T) {
description: "should not add any mappings when all are duplicates",
},
{
name: "new_node_key",
name: "new_node_key",
initialCarryOvers: map[vo.NodeKey][]*compose.FieldMapping{
"node1": {
compose.MapFieldPaths(compose.FieldPath{"input1"}, compose.FieldPath{"output1"}),
@ -109,7 +109,7 @@ func TestAddFieldMappingsWithDeduplication(t *testing.T) {
description: "should add all mappings for new node key",
},
{
name: "empty_field_mappings",
name: "empty_field_mappings",
initialCarryOvers: map[vo.NodeKey][]*compose.FieldMapping{
"node1": {
compose.MapFieldPaths(compose.FieldPath{"input1"}, compose.FieldPath{"output1"}),

View File

@ -25,6 +25,8 @@ import (
"github.com/cloudwego/eino/components/tool"
einoCompose "github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
wf "github.com/coze-dev/coze-studio/backend/domain/workflow"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
@ -91,6 +93,9 @@ func (wt *workflowTool) prepare(ctx context.Context, rInfo *entity.ResumeRequest
lastEventChan <-chan *execute.Event, callOpts []einoCompose.Option, err error) {
cfg := execute.GetExecuteConfig(opts...)
cfg.InputFileFields = slices.ToMap(wt.sc.GetAllNodesInputFileFields(ctx), func(e *workflowModel.FileInfo) (string, *workflowModel.FileInfo) {
return e.FileURL, e
})
var runOpts []WorkflowRunnerOption
if rInfo != nil && !rInfo.Resumed {
runOpts = append(runOpts, WithResumeReq(rInfo))
@ -121,12 +126,19 @@ func (wt *workflowTool) prepare(ctx context.Context, rInfo *entity.ResumeRequest
if entryNode == nil {
panic("entry node not found in tool workflow")
}
input, ws, err = nodes.ConvertInputs(ctx, input, entryNode.OutputTypes)
collectFileFields := make(map[string]*workflowModel.FileInfo, 0)
input, ws, err = nodes.ConvertInputs(ctx, input, entryNode.OutputTypes, nodes.WithCollectFileFields(collectFileFields))
if err != nil {
return
} else if ws != nil {
logs.CtxWarnf(ctx, "convert inputs warnings: %v", *ws)
}
if len(collectFileFields) > 0 {
for k, v := range collectFileFields {
cfg.InputFileFields[k] = v
}
}
}
cancelCtx, executeID, callOpts, lastEventChan, err = NewWorkflowRunner(wt.wfEntity.GetBasic(), wt.sc, cfg, runOpts...).Prepare(ctx)

View File

@ -20,12 +20,12 @@ import (
"context"
"errors"
"fmt"
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
"net/url"
"path/filepath"
"strconv"
"strings"
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"

View File

@ -163,7 +163,7 @@ func parseToFileNameAndFileExtension(ctx context.Context, fileURL string) (strin
if err != nil {
return "", "", err
}
fileExt := filepath.Ext(strings.ToLower(strings.TrimPrefix(u.Path, ".")))
fileExt := strings.TrimPrefix(strings.ToLower(filepath.Ext(u.Path)), ".")
ext, support := parser.ValidateFileExtension(fileExt)
if !support {
return "", "", fmt.Errorf("unsupported file type: %s", fileExt)

View File

@ -204,11 +204,11 @@ func TestImpl_prefetchChatHistory(t *testing.T) {
crossmessage.SetDefaultSVC(mockMessage)
tests := []struct {
name string
setupMock func(msgSvc *messagemock.MockMessage)
config workflowModel.ExecuteConfig
name string
setupMock func(msgSvc *messagemock.MockMessage)
config workflowModel.ExecuteConfig
historyRounds int64
expectErr bool
expectErr bool
}{
{
name: "SectionID is nil",

View File

@ -11,8 +11,8 @@ require (
github.com/apache/rocketmq-client-go/v2 v2.1.3-0.20250427084711-67ec50b93040
github.com/apache/thrift v0.21.0
github.com/aws/aws-sdk-go-v2 v1.36.6
github.com/aws/aws-sdk-go-v2/config v1.29.1
github.com/aws/aws-sdk-go-v2/credentials v1.17.54
github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/credentials v1.17.62
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1
github.com/bytedance/gopkg v0.1.3
github.com/bytedance/mockey v1.2.14
@ -51,7 +51,7 @@ require (
github.com/nikolalohinski/gonja v1.5.3 // indirect
github.com/nsqio/go-nsq v1.1.0
github.com/ollama/ollama v0.9.6
github.com/onsi/gomega v1.27.3
github.com/onsi/gomega v1.35.1
github.com/pingcap/tidb/pkg/parser v0.0.0-20250417044355-c5882b1f6c58
github.com/pkg/errors v0.9.1
github.com/rbretecher/go-postman-collection v0.9.0
@ -71,7 +71,7 @@ require (
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
golang.org/x/image v0.22.0
golang.org/x/mod v0.25.0
golang.org/x/oauth2 v0.25.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sync v0.16.0
google.golang.org/genai v1.18.0
gopkg.in/yaml.v2 v2.4.0
@ -84,26 +84,26 @@ require (
)
require (
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/auth v0.9.3 // indirect
cloud.google.com/go v0.118.3 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/anthropics/anthropic-sdk-go v1.4.0 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
@ -146,15 +146,14 @@ require (
github.com/goccy/go-json v0.10.5 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
github.com/goph/emperror v0.17.2 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
@ -187,7 +186,6 @@ require (
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250620092828-0d508a1dcdde // indirect
github.com/milvus-io/milvus/pkg/v2 v2.0.0-20250319085209-5a6b4e56d59e // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
@ -207,10 +205,10 @@ require (
github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
@ -219,7 +217,7 @@ require (
github.com/rs/xid v1.6.0 // indirect
github.com/samber/lo v1.27.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/shirou/gopsutil/v3 v3.22.9 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f
github.com/smarty/assertions v1.16.0 // indirect
@ -249,9 +247,8 @@ require (
go.etcd.io/etcd/pkg/v3 v3.5.5 // indirect
go.etcd.io/etcd/raft/v3 v3.5.5 // indirect
go.etcd.io/etcd/server/v3 v3.5.5 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect
@ -270,8 +267,8 @@ require (
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect
google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
@ -280,14 +277,46 @@ require (
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
gorm.io/driver/postgres v1.5.11 // indirect
gorm.io/hints v1.1.0 // indirect
k8s.io/apimachinery v0.28.6 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/apimachinery v0.32.3 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
stathat.com/c/consistent v1.0.0 // indirect
)
require github.com/apache/pulsar-client-go v0.16.0
require (
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/AthenZ/athenz v1.12.13 // indirect
github.com/DataDog/zstd v1.5.0 // indirect
github.com/ardielle/ardielle-go v1.5.2 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bits-and-blooms/bitset v1.4.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/eino-contrib/jsonschema v1.0.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hamba/avro/v2 v2.26.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
golang.org/x/term v0.32.0 // indirect
k8s.io/client-go v0.32.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
)

View File

@ -13,10 +13,10 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -37,10 +37,20 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AthenZ/athenz v1.12.13 h1:OhZNqZsoBXNrKBJobeUUEirPDnwt0HRo4kQMIO1UwwQ=
github.com/AthenZ/athenz v1.12.13/go.mod h1:XXDXXgaQzXaBXnJX6x/bH4yF6eon2lkyzQZ0z/dxprE=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -49,12 +59,16 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo=
github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/IBM/sarama v1.45.1 h1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=
github.com/IBM/sarama v1.45.1/go.mod h1:qifDhA3VWSrQ1TjSMyxDl3nYL3oX2C83u+G6L79sq4w=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
@ -78,10 +92,14 @@ github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqR
github.com/anthropics/anthropic-sdk-go v1.4.0 h1:fU1jKxYbQdQDiEXCxeW5XZRIOwKevn/PMg8Ay1nnUx0=
github.com/anthropics/anthropic-sdk-go v1.4.0/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/pulsar-client-go v0.16.0 h1:SnmGzqcTu6WpK4D6I2Jdwe/VCFkMUk516OiIF3DHqI8=
github.com/apache/pulsar-client-go v0.16.0/go.mod h1:ow9PhLoGUY6ncrKOtjnWeJycFnTKOwrIV39j3kNV54M=
github.com/apache/rocketmq-client-go/v2 v2.1.3-0.20250427084711-67ec50b93040 h1:c2o4/foDm9LXc3jSmm3SUxVZb5I5KNtztw/bstf836s=
github.com/apache/rocketmq-client-go/v2 v2.1.3-0.20250427084711-67ec50b93040/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE=
github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNGM3Zj4=
github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@ -96,18 +114,18 @@ github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQ
github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/config v1.29.1 h1:JZhGawAyZ/EuJeBtbQYnaoftczcb2drR2Iq36Wgz4sQ=
github.com/aws/aws-sdk-go-v2/config v1.29.1/go.mod h1:7bR2YD5euaxBhzt2y/oDkt3uNRb6tjFp98GlTFueRwk=
github.com/aws/aws-sdk-go-v2/credentials v1.17.54 h1:4UmqeOqJPvdvASZWrKlhzpRahAulBfyTJQUaYy4+hEI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.54/go.mod h1:RTdfo0P0hbbTxIhmQrOsC/PquBZGabEPnCaxxKRPSnI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis=
github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0=
github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 h1:XTZZ0I3SZUHAtBLBU6395ad+VOblE0DwQP6MuaNeics=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37/go.mod h1:Pi6ksbniAWVwu2S8pEzcYPyhUkAcLaufxN7PfAUQjBk=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
@ -121,12 +139,12 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 h1:OS2e0SKqsU2Li
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18/go.mod h1:+Yrk+MDGzlNGxCXieljNeWpoZTCQUQVL+Jk9hGGJ8qM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 h1:RkHXU9jP0DptGy7qKI8CBGsUJruWz0v5IgwBa2DwWcU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1/go.mod h1:3xAOf7tdKF+qbb+XpU+EPhNXAdun3Lu1RcDrj8KC24I=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 h1:kuIyu4fTT38Kj7YCC7ouNbVZSSpqkZ+LzIfhCr6Dg+I=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 h1:l+dgv/64iVlQ3WsBbnn+JSbkj01jIi+SM0wYsj3y/hY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 h1:BRVDbewN6VZcwr+FBOszDKvYeXY1kJ+GGMCcpghlw0U=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
@ -140,6 +158,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bits-and-blooms/bitset v1.4.0 h1:+YZ8ePm+He2pU3dZlIZiOeAKfrBkXi1lSrXJ/Xzgbu8=
github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
@ -249,6 +269,12 @@ github.com/cohesion-org/deepseek-go v1.3.2 h1:WTZ/2346KFYca+n+DL5p+Ar1RQxF2w/wGk
github.com/cohesion-org/deepseek-go v1.3.2/go.mod h1:bOVyKj38r90UEYZFrmJOzJKPxuAh8sIzHOCnLOpiXeI=
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -260,11 +286,15 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -277,11 +307,21 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4wg/adLLz5xh5CmpiCA=
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
@ -324,6 +364,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@ -339,6 +381,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
@ -359,6 +403,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
@ -389,9 +435,10 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
@ -400,6 +447,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -414,6 +463,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -426,7 +477,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -481,6 +531,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -501,16 +552,20 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g=
github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -535,6 +590,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hamba/avro/v2 v2.26.0 h1:IaT5l6W3zh7K67sMrT2+RreJyDTllBGVJm4+Hedk9qE=
github.com/hamba/avro/v2 v2.26.0/go.mod h1:I8glyswHnpED3Nlx2ZdUe+4LJnCOOyiCzLMno9i/Uu0=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@ -692,12 +751,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -730,8 +793,6 @@ github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOj
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250620092828-0d508a1dcdde h1:pq2I0uxUR4lfr4OmqvE8QdHj9UML9b1jZu8L3dI2eu8=
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250620092828-0d508a1dcdde/go.mod h1:CqSFsV6AkkL2fixd25WYjRAolns+gQrY1x/Cz9c30v8=
@ -767,6 +828,20 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -776,7 +851,13 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
@ -795,6 +876,7 @@ github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTf
github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
@ -811,8 +893,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@ -820,9 +902,13 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.27.3 h1:5VwIwnBY3vbBDOJrNtA4rVdiTZCsq9B5F12pvy1Drmk=
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -881,14 +967,14 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@ -896,8 +982,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -905,8 +991,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=
github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
@ -945,8 +1031,12 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
github.com/shirou/gopsutil/v3 v3.22.9 h1:yibtJhIVEMcdw+tCTbOPiF1VcsuDeTE4utJ8Dm4c5eA=
github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@ -1011,6 +1101,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tealeg/xlsx/v3 v3.3.13 h1:Zk1Stj11MGRnOYI1st6av/Z2lIXp/jFZomrSWSeJLmY=
github.com/tealeg/xlsx/v3 v3.3.13/go.mod h1:KV4FTFtvGy0TBlOivJLZu/YNZk6e0Qtk7eOSglWksuA=
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -1025,10 +1117,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -1099,7 +1189,7 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -1129,13 +1219,13 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
@ -1323,8 +1413,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1406,13 +1496,13 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1422,6 +1512,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
@ -1587,10 +1678,10 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@ -1638,6 +1729,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@ -1654,6 +1746,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@ -1703,15 +1796,25 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/apimachinery v0.28.6 h1:RsTeR4z6S07srPg6XYrwXpTJVMXsjPXn0ODakMytSW0=
k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=

View File

@ -74,20 +74,23 @@ func (r *runner) Run(ctx context.Context, request *coderunner.RunRequest) (*code
}
func (r *runner) pythonCmdRun(_ context.Context, code string, params map[string]any) (map[string]any, error) {
bs, _ := sonic.Marshal(params)
bs, err := sonic.Marshal(params)
if err != nil {
return nil, fmt.Errorf("failed to marshal params to json, err: %w", err)
}
cmd := exec.Command(goutil.GetPython3Path(), "-c", fmt.Sprintf(pythonCode, code), string(bs)) // ignore_security_alert RCE
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd.Stdout = stdout
cmd.Stderr = stderr
err := cmd.Run()
err = cmd.Run()
if err != nil {
return nil, fmt.Errorf("failed to run python script err: %s, std err: %s", err.Error(), stderr.String())
}
if stderr.String() != "" {
return nil, fmt.Errorf("failed to run python script err: %s", stderr.String())
}
ret := make(map[string]any)
err = sonic.Unmarshal(stdout.Bytes(), &ret)
if err != nil {

View File

@ -21,6 +21,7 @@ import (
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
@ -47,6 +48,7 @@ func ParseMarkdown(config *contract.Config, storage storage.Storage, ocr ocr.OCR
return nil, err
}
node := mdParser.Parse(text.NewReader(b))
cs := config.ChunkingStrategy
ps := config.ParsingStrategy
@ -101,13 +103,118 @@ func ParseMarkdown(config *contract.Config, storage storage.Storage, ocr ocr.OCR
return text
}
// validateImageURL 验证图片URL的安全性
validateImageURL := func(urlString string) error {
parsedURL, err := url.Parse(urlString)
if err != nil {
return err
}
// 只允许HTTP/HTTPS
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return fmt.Errorf("unsupported scheme: %s", parsedURL.Scheme)
}
// 检查域名白名单
allowedDomains := []string{
"images.unsplash.com",
"cdn.example.com",
"github.com",
"githubusercontent.com",
// 可以根据需要添加其他受信任的域名
}
hostname := parsedURL.Hostname()
for _, domain := range allowedDomains {
if hostname == domain || strings.HasSuffix(hostname, "."+domain) {
return nil
}
}
return fmt.Errorf("domain not allowed: %s", hostname)
}
// isPrivateIPAddress 检查IP地址是否为私有地址
isPrivateIPAddress := func(ip net.IP) bool {
// 检查私有IP范围
privateRanges := []struct {
cidr string
}{
{"10.0.0.0/8"},
{"172.16.0.0/12"},
{"192.168.0.0/16"},
{"127.0.0.0/8"},
{"169.254.0.0/16"}, // 链路本地地址
{"::1/128"}, // IPv6 loopback
{"fc00::/7"}, // IPv6 私有地址
}
for _, r := range privateRanges {
_, cidr, _ := net.ParseCIDR(r.cidr)
if cidr.Contains(ip) {
return true
}
}
return false
}
// isPrivateIP 检查是否为私有IP地址
isPrivateIP := func(host string) bool {
ip := net.ParseIP(host)
if ip == nil {
// 可能是域名,需要解析
ips, err := net.LookupIP(host)
if err != nil {
return true // 解析失败,拒绝访问
}
// 检查所有解析的IP
for _, resolvedIP := range ips {
if isPrivateIPAddress(resolvedIP) {
return true
}
}
return false
}
return isPrivateIPAddress(ip)
}
downloadImage := func(ctx context.Context, url string) ([]byte, error) {
client := &http.Client{Timeout: 5 * time.Second}
// URL验证
if err := validateImageURL(url); err != nil {
return nil, fmt.Errorf("invalid URL: %w", err)
}
// 使用安全的HTTP客户端
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 禁止访问私有IP
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
if isPrivateIP(host) {
return nil, fmt.Errorf("access to private IP denied: %s", host)
}
return (&net.Dialer{}).DialContext(ctx, network, addr)
},
},
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
// 添加安全头
req.Header.Set("User-Agent", "CozeStudio/1.0")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to download image: %w", err)
@ -118,7 +225,11 @@ func ParseMarkdown(config *contract.Config, storage storage.Storage, ocr ocr.OCR
return nil, fmt.Errorf("failed to download image, status code: %d", resp.StatusCode)
}
data, err := io.ReadAll(resp.Body)
// 限制响应大小
const maxImageSize = 10 * 1024 * 1024 // 10MB
limitedReader := io.LimitReader(resp.Body, maxImageSize)
data, err := io.ReadAll(limitedReader)
if err != nil {
return nil, fmt.Errorf("failed to read image content: %w", err)
}

View File

@ -21,12 +21,12 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/coze-dev/coze-studio/backend/pkg/parsex"
"io"
"os"
"github.com/elastic/go-elasticsearch/v7"
"github.com/elastic/go-elasticsearch/v7/esapi"
"github.com/elastic/go-elasticsearch/v7/esutil"
"io"
"os"
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
@ -39,7 +39,7 @@ type es7Client struct {
}
func newES7() (Client, error) {
addresses, err := parsex.ParseClusterEndpoints(os.Getenv("ES_ADDR"))
addresses, err := parseClusterEndpoints(os.Getenv("ES_ADDR"))
if err != nil {
return nil, err
}
@ -123,8 +123,8 @@ func (c *es7Client) CreateIndex(ctx context.Context, index string, properties ma
"properties": properties,
},
"settings": map[string]any{
"number_of_shards": parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
"number_of_replicas": parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
"number_of_shards": getEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
"number_of_replicas": getEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
},
}

View File

@ -19,7 +19,8 @@ package es
import (
"context"
"fmt"
"github.com/coze-dev/coze-studio/backend/pkg/parsex"
"os"
"github.com/elastic/go-elasticsearch/v8"
"github.com/elastic/go-elasticsearch/v8/esutil"
"github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
@ -30,7 +31,6 @@ import (
"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/operator"
"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/sortorder"
"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/textquerytype"
"os"
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
@ -51,7 +51,7 @@ type es8BulkIndexer struct {
type es8Types struct{}
func newES8() (Client, error) {
addresses, err := parsex.ParseClusterEndpoints(os.Getenv("ES_ADDR"))
addresses, err := parseClusterEndpoints(os.Getenv("ES_ADDR"))
if err != nil {
return nil, err
}
@ -243,8 +243,8 @@ func (c *es8Client) CreateIndex(ctx context.Context, index string, properties ma
Properties: propertiesMap,
},
Settings: &types.IndexSettings{
NumberOfShards: parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
NumberOfReplicas: parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
NumberOfShards: getEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
NumberOfReplicas: getEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
},
}).Do(ctx); err != nil {
return err

View File

@ -18,8 +18,9 @@ package es
import (
"fmt"
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
"os"
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
)
type (

View File

@ -14,18 +14,18 @@
* limitations under the License.
*/
package parsex
package es
import (
"fmt"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"os"
"strconv"
"strings"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
)
// ParseClusterEndpoints 解析 ES /kafka 地址,多个地址用逗号分隔
func ParseClusterEndpoints(address string) ([]string, error) {
func parseClusterEndpoints(address string) ([]string, error) {
if strings.TrimSpace(address) == "" {
return nil, fmt.Errorf("endpoints environment variable is required")
}
@ -52,8 +52,7 @@ func ParseClusterEndpoints(address string) ([]string, error) {
return validEndpoints, nil
}
// GetEnvDefaultIntSetting 获取环境变量的值,如果不存在或无效则返回默认值
func GetEnvDefaultIntSetting(envVar, defaultValue string) string {
func getEnvDefaultIntSetting(envVar, defaultValue string) string {
value := os.Getenv(envVar)
if value == "" {
return defaultValue

View File

@ -23,6 +23,7 @@ import (
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/kafka"
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/nsq"
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/pulsar"
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/rmq"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
@ -54,9 +55,11 @@ func (consumerServiceImpl) RegisterConsumer(nameServer, topic, group string, con
return kafka.RegisterConsumer(nameServer, topic, group, consumerHandler, opts...)
case "rmq":
return rmq.RegisterConsumer(nameServer, topic, group, consumerHandler, opts...)
case "pulsar":
return pulsar.RegisterConsumer(nameServer, topic, group, consumerHandler, opts...)
}
return fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq", tp)
return fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq, pulsar", tp)
}
func NewProducer(nameServer, topic, group string, retries int) (eventbus.Producer, error) {
@ -68,7 +71,9 @@ func NewProducer(nameServer, topic, group string, retries int) (eventbus.Produce
return kafka.NewProducer(nameServer, topic)
case "rmq":
return rmq.NewProducer(nameServer, topic, group, retries)
case "pulsar":
return pulsar.NewProducer(nameServer, topic, group)
}
return nil, fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq", tp)
return nil, fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq, pulsar", tp)
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pulsar
import (
"context"
"fmt"
"os"
"github.com/apache/pulsar-client-go/pulsar"
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
"github.com/coze-dev/coze-studio/backend/pkg/lang/signal"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/pkg/safego"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
func RegisterConsumer(serviceURL, topic, group string, consumerHandler eventbus.ConsumerHandler, opts ...eventbus.ConsumerOpt) error {
if serviceURL == "" {
return fmt.Errorf("service URL is empty")
}
if topic == "" {
return fmt.Errorf("topic is empty")
}
if group == "" {
return fmt.Errorf("group is empty")
}
if consumerHandler == nil {
return fmt.Errorf("consumer handler is nil")
}
// Parse consumer options
option := &eventbus.ConsumerOption{}
for _, opt := range opts {
opt(option)
}
// Prepare client options
clientOptions := pulsar.ClientOptions{
URL: serviceURL,
}
// Add JWT authentication if token is provided
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
}
// Create Pulsar client
client, err := pulsar.NewClient(clientOptions)
if err != nil {
return fmt.Errorf("create pulsar client failed: %w", err)
}
// Configure consumer options
consumerOptions := pulsar.ConsumerOptions{
Topic: topic,
SubscriptionName: group,
Type: pulsar.Exclusive, // Exclusive mode ensures single consumer for message ordering
}
// Create consumer
consumer, err := client.Subscribe(consumerOptions)
if err != nil {
client.Close()
return fmt.Errorf("create pulsar consumer failed: %w", err)
}
// Create cancellable context for better resource management
ctx, cancel := context.WithCancel(context.Background())
// Start consuming messages in a goroutine
safego.Go(ctx, func() {
defer func() {
consumer.Close()
client.Close()
}()
for {
select {
case <-ctx.Done():
logs.Infof("pulsar consumer stopped for topic: %s, group: %s", topic, group)
return
default:
// Receive message with context timeout
msg, err := consumer.Receive(ctx)
if err != nil {
// Check if context was cancelled
if ctx.Err() != nil {
return
}
logs.Errorf("receive pulsar message error: %v", err)
continue
}
// Convert to eventbus message
eventMsg := &eventbus.Message{
Topic: topic,
Group: group,
Body: msg.Payload(),
}
// Handle message with context
if err := consumerHandler.HandleMessage(ctx, eventMsg); err != nil {
logs.Errorf("handle pulsar message failed, topic: %s, group: %s, err: %v", topic, group, err)
// Negative acknowledge on error
consumer.Nack(msg)
continue
}
// Acknowledge message on success
consumer.Ack(msg)
}
}
})
// Handle graceful shutdown
safego.Go(context.Background(), func() {
signal.WaitExit()
logs.Infof("shutting down pulsar consumer for topic: %s, group: %s", topic, group)
cancel() // Cancel the context to stop consumer loop
consumer.Close()
client.Close()
})
return nil
}

View File

@ -0,0 +1,155 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pulsar
import (
"context"
"fmt"
"os"
"github.com/apache/pulsar-client-go/pulsar"
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
"github.com/coze-dev/coze-studio/backend/pkg/lang/signal"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/pkg/safego"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
type producerImpl struct {
topic string
client pulsar.Client
producer pulsar.Producer
}
func NewProducer(serviceURL, topic, group string) (eventbus.Producer, error) {
if serviceURL == "" {
return nil, fmt.Errorf("pulsar service URL is required")
}
if topic == "" {
return nil, fmt.Errorf("topic is required")
}
// Prepare client options
clientOptions := pulsar.ClientOptions{
URL: serviceURL,
}
// Add JWT authentication if token is provided
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
}
// Create Pulsar client
logs.Debugf("Creating Pulsar client with URL: %s", serviceURL)
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
logs.Debugf("Using JWT authentication, token length: %d", len(jwtToken))
} else {
logs.Debugf("No JWT token provided")
}
client, err := pulsar.NewClient(clientOptions)
if err != nil {
return nil, fmt.Errorf("failed to create pulsar client with URL %s: %w", serviceURL, err)
}
logs.Debugf("Pulsar client created successfully")
// Create producer
logs.Debugf("Creating producer for topic: %s, group: %s", topic, group)
producer, err := client.CreateProducer(pulsar.ProducerOptions{
Topic: topic,
Name: fmt.Sprintf("%s-producer", group),
})
if err != nil {
logs.Errorf("Failed to create producer: %v", err)
client.Close()
return nil, fmt.Errorf("create pulsar producer failed: %w", err)
}
logs.Debugf("Producer created successfully")
impl := &producerImpl{
topic: topic,
client: client,
producer: producer,
}
// Handle graceful shutdown
safego.Go(context.Background(), func() {
signal.WaitExit()
impl.close()
})
return impl, nil
}
func (p *producerImpl) Send(ctx context.Context, body []byte, opts ...eventbus.SendOpt) error {
return p.BatchSend(ctx, [][]byte{body}, opts...)
}
func (p *producerImpl) BatchSend(ctx context.Context, bodyArr [][]byte, opts ...eventbus.SendOpt) error {
option := eventbus.SendOption{}
for _, opt := range opts {
opt(&option)
}
// Use Pulsar's async send with batch collection for better performance
type sendResult struct {
err error
}
resultChan := make(chan sendResult, len(bodyArr))
pendingCount := len(bodyArr)
for _, body := range bodyArr {
msg := &pulsar.ProducerMessage{
Payload: body,
}
// Set partition key if sharding key is provided
if option.ShardingKey != nil {
msg.Key = *option.ShardingKey
}
// Send message asynchronously for better batching performance
p.producer.SendAsync(ctx, msg, func(messageID pulsar.MessageID, producerMessage *pulsar.ProducerMessage, err error) {
resultChan <- sendResult{err: err}
})
}
// Wait for all messages to be sent
for i := 0; i < pendingCount; i++ {
select {
case result := <-resultChan:
if result.err != nil {
return fmt.Errorf("[pulsarProducer] batch send message failed: %w", result.err)
}
case <-ctx.Done():
return fmt.Errorf("[pulsarProducer] batch send cancelled: %w", ctx.Err())
}
}
return nil
}
func (p *producerImpl) close() {
if p.producer != nil {
p.producer.Close()
}
if p.client != nil {
p.client.Close()
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pulsar
import (
"context"
"os"
"sync"
"testing"
"time"
"github.com/apache/pulsar-client-go/pulsar"
"github.com/stretchr/testify/assert"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
var serviceURL = "pulsar://localhost:6650"
func TestProducer(t *testing.T) {
if os.Getenv("PULSAR_LOCAL_TEST") != "true" {
return
}
// JWT token should be set via environment variable PULSAR_JWT_TOKEN if needed
// For local testing, you can set: export PULSAR_JWT_TOKEN="your-jwt-token"
clientOptions := pulsar.ClientOptions{
URL: serviceURL,
}
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
}
client, err := pulsar.NewClient(clientOptions)
assert.NoError(t, err)
defer client.Close()
producer, err := client.CreateProducer(pulsar.ProducerOptions{
Topic: "test_topic",
Name: "test_group_producer",
})
assert.NoError(t, err)
defer producer.Close()
msgID, err := producer.Send(context.Background(), &pulsar.ProducerMessage{
Payload: []byte("hello"),
})
assert.NoError(t, err)
t.Logf("Message sent with ID: %v", msgID)
}
func TestConsumer(t *testing.T) {
if os.Getenv("PULSAR_LOCAL_TEST") != "true" {
return
}
// JWT token should be set via environment variable PULSAR_JWT_TOKEN if needed
// For local testing, you can set: export PULSAR_JWT_TOKEN="your-jwt-token"
clientOptions := pulsar.ClientOptions{
URL: serviceURL,
}
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
}
client, err := pulsar.NewClient(clientOptions)
assert.NoError(t, err)
defer client.Close()
// First create consumer
consumer, err := client.Subscribe(pulsar.ConsumerOptions{
Topic: "test_topic",
SubscriptionName: "test_group_consumer",
Type: pulsar.Shared,
})
assert.NoError(t, err)
defer consumer.Close()
// Then create producer and send a message
producer, err := client.CreateProducer(pulsar.ProducerOptions{
Topic: "test_topic",
Name: "test_consumer_producer",
})
assert.NoError(t, err)
defer producer.Close()
// Send a test message
_, err = producer.Send(context.Background(), &pulsar.ProducerMessage{
Payload: []byte("consumer test message"),
})
assert.NoError(t, err)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
msg, err := consumer.Receive(context.Background())
if err != nil {
t.Errorf("Failed to receive message: %v", err)
return
}
t.Logf("Received message: %s", string(msg.Payload()))
consumer.Ack(msg)
}()
wg.Wait()
time.Sleep(time.Second)
}

View File

@ -646,15 +646,15 @@ func (m *mysqlService) ExecuteSQL(ctx context.Context, req *rdb.ExecuteSQLReques
var processedParams []interface{}
var err error
// Handle SQLType: if raw, do not process params
// 禁用原始SQL执行以防止SQL注入攻击
if req.SQLType == entity2.SQLType_Raw {
processedSQL = req.SQL
processedParams = nil
} else {
processedSQL, processedParams, err = m.processSliceParams(req.SQL, req.Params)
if err != nil {
return nil, fmt.Errorf("failed to process parameters: %v", err)
}
return nil, fmt.Errorf("raw SQL execution is not allowed for security reasons")
}
// 强制使用参数化查询
processedSQL, processedParams, err = m.processSliceParams(req.SQL, req.Params)
if err != nil {
return nil, fmt.Errorf("failed to process parameters: %v", err)
}
operation, err := sqlparser.NewSQLParser().GetSQLOperation(processedSQL)
@ -1011,4 +1011,4 @@ func (m *mysqlService) buildNestedConditions(condition *rdb.ComplexCondition) (s
return whereClause.String(), values, nil
}
return "", values, nil
}
}

View File

@ -0,0 +1,355 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Code generated by MockGen. DO NOT EDIT.
// Source: domain/agent/singleagent/service/single_agent.go
//
// Generated by this command:
//
// mockgen -destination internal/mock/domain/agent/singleagent/single_agent_mock.go --package mock -source domain/agent/singleagent/service/single_agent.go
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
schema "github.com/cloudwego/eino/schema"
playground "github.com/coze-dev/coze-studio/backend/api/model/playground"
entity "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
gomock "go.uber.org/mock/gomock"
)
// MockSingleAgent is a mock of SingleAgent interface.
type MockSingleAgent struct {
ctrl *gomock.Controller
recorder *MockSingleAgentMockRecorder
isgomock struct{}
}
// MockSingleAgentMockRecorder is the mock recorder for MockSingleAgent.
type MockSingleAgentMockRecorder struct {
mock *MockSingleAgent
}
// NewMockSingleAgent creates a new mock instance.
func NewMockSingleAgent(ctrl *gomock.Controller) *MockSingleAgent {
mock := &MockSingleAgent{ctrl: ctrl}
mock.recorder = &MockSingleAgentMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSingleAgent) EXPECT() *MockSingleAgentMockRecorder {
return m.recorder
}
// CreateSingleAgent mocks base method.
func (m *MockSingleAgent) CreateSingleAgent(ctx context.Context, connectorID int64, version string, e *entity.SingleAgent) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSingleAgent", ctx, connectorID, version, e)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSingleAgent indicates an expected call of CreateSingleAgent.
func (mr *MockSingleAgentMockRecorder) CreateSingleAgent(ctx, connectorID, version, e any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSingleAgent", reflect.TypeOf((*MockSingleAgent)(nil).CreateSingleAgent), ctx, connectorID, version, e)
}
// CreateSingleAgentDraft mocks base method.
func (m *MockSingleAgent) CreateSingleAgentDraft(ctx context.Context, creatorID int64, draft *entity.SingleAgent) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSingleAgentDraft", ctx, creatorID, draft)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSingleAgentDraft indicates an expected call of CreateSingleAgentDraft.
func (mr *MockSingleAgentMockRecorder) CreateSingleAgentDraft(ctx, creatorID, draft any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSingleAgentDraft", reflect.TypeOf((*MockSingleAgent)(nil).CreateSingleAgentDraft), ctx, creatorID, draft)
}
// CreateSingleAgentDraftWithID mocks base method.
func (m *MockSingleAgent) CreateSingleAgentDraftWithID(ctx context.Context, creatorID, agentID int64, draft *entity.SingleAgent) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSingleAgentDraftWithID", ctx, creatorID, agentID, draft)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSingleAgentDraftWithID indicates an expected call of CreateSingleAgentDraftWithID.
func (mr *MockSingleAgentMockRecorder) CreateSingleAgentDraftWithID(ctx, creatorID, agentID, draft any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSingleAgentDraftWithID", reflect.TypeOf((*MockSingleAgent)(nil).CreateSingleAgentDraftWithID), ctx, creatorID, agentID, draft)
}
// DeleteAgentDraft mocks base method.
func (m *MockSingleAgent) DeleteAgentDraft(ctx context.Context, spaceID, agentID int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAgentDraft", ctx, spaceID, agentID)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAgentDraft indicates an expected call of DeleteAgentDraft.
func (mr *MockSingleAgentMockRecorder) DeleteAgentDraft(ctx, spaceID, agentID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAgentDraft", reflect.TypeOf((*MockSingleAgent)(nil).DeleteAgentDraft), ctx, spaceID, agentID)
}
// DuplicateInMemory mocks base method.
func (m *MockSingleAgent) DuplicateInMemory(ctx context.Context, req *entity.DuplicateInfo) (*entity.SingleAgent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DuplicateInMemory", ctx, req)
ret0, _ := ret[0].(*entity.SingleAgent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DuplicateInMemory indicates an expected call of DuplicateInMemory.
func (mr *MockSingleAgentMockRecorder) DuplicateInMemory(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DuplicateInMemory", reflect.TypeOf((*MockSingleAgent)(nil).DuplicateInMemory), ctx, req)
}
// GetAgentDraftDisplayInfo mocks base method.
func (m *MockSingleAgent) GetAgentDraftDisplayInfo(ctx context.Context, userID, agentID int64) (*entity.AgentDraftDisplayInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAgentDraftDisplayInfo", ctx, userID, agentID)
ret0, _ := ret[0].(*entity.AgentDraftDisplayInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAgentDraftDisplayInfo indicates an expected call of GetAgentDraftDisplayInfo.
func (mr *MockSingleAgentMockRecorder) GetAgentDraftDisplayInfo(ctx, userID, agentID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAgentDraftDisplayInfo", reflect.TypeOf((*MockSingleAgent)(nil).GetAgentDraftDisplayInfo), ctx, userID, agentID)
}
// GetAgentPopupCount mocks base method.
func (m *MockSingleAgent) GetAgentPopupCount(ctx context.Context, uid, agentID int64, agentPopupType playground.BotPopupType) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAgentPopupCount", ctx, uid, agentID, agentPopupType)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAgentPopupCount indicates an expected call of GetAgentPopupCount.
func (mr *MockSingleAgentMockRecorder) GetAgentPopupCount(ctx, uid, agentID, agentPopupType any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAgentPopupCount", reflect.TypeOf((*MockSingleAgent)(nil).GetAgentPopupCount), ctx, uid, agentID, agentPopupType)
}
// GetPublishConnectorList mocks base method.
func (m *MockSingleAgent) GetPublishConnectorList(ctx context.Context, agentID int64) (*entity.PublishConnectorData, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublishConnectorList", ctx, agentID)
ret0, _ := ret[0].(*entity.PublishConnectorData)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPublishConnectorList indicates an expected call of GetPublishConnectorList.
func (mr *MockSingleAgentMockRecorder) GetPublishConnectorList(ctx, agentID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublishConnectorList", reflect.TypeOf((*MockSingleAgent)(nil).GetPublishConnectorList), ctx, agentID)
}
// GetPublishedInfo mocks base method.
func (m *MockSingleAgent) GetPublishedInfo(ctx context.Context, agentID int64) (*entity.PublishInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublishedInfo", ctx, agentID)
ret0, _ := ret[0].(*entity.PublishInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPublishedInfo indicates an expected call of GetPublishedInfo.
func (mr *MockSingleAgentMockRecorder) GetPublishedInfo(ctx, agentID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublishedInfo", reflect.TypeOf((*MockSingleAgent)(nil).GetPublishedInfo), ctx, agentID)
}
// GetPublishedTime mocks base method.
func (m *MockSingleAgent) GetPublishedTime(ctx context.Context, agentID int64) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublishedTime", ctx, agentID)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPublishedTime indicates an expected call of GetPublishedTime.
func (mr *MockSingleAgentMockRecorder) GetPublishedTime(ctx, agentID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublishedTime", reflect.TypeOf((*MockSingleAgent)(nil).GetPublishedTime), ctx, agentID)
}
// GetSingleAgent mocks base method.
func (m *MockSingleAgent) GetSingleAgent(ctx context.Context, agentID int64, version string) (*entity.SingleAgent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSingleAgent", ctx, agentID, version)
ret0, _ := ret[0].(*entity.SingleAgent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSingleAgent indicates an expected call of GetSingleAgent.
func (mr *MockSingleAgentMockRecorder) GetSingleAgent(ctx, agentID, version any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSingleAgent", reflect.TypeOf((*MockSingleAgent)(nil).GetSingleAgent), ctx, agentID, version)
}
// GetSingleAgentDraft mocks base method.
func (m *MockSingleAgent) GetSingleAgentDraft(ctx context.Context, agentID int64) (*entity.SingleAgent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSingleAgentDraft", ctx, agentID)
ret0, _ := ret[0].(*entity.SingleAgent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSingleAgentDraft indicates an expected call of GetSingleAgentDraft.
func (mr *MockSingleAgentMockRecorder) GetSingleAgentDraft(ctx, agentID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSingleAgentDraft", reflect.TypeOf((*MockSingleAgent)(nil).GetSingleAgentDraft), ctx, agentID)
}
// IncrAgentPopupCount mocks base method.
func (m *MockSingleAgent) IncrAgentPopupCount(ctx context.Context, uid, agentID int64, agentPopupType playground.BotPopupType) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IncrAgentPopupCount", ctx, uid, agentID, agentPopupType)
ret0, _ := ret[0].(error)
return ret0
}
// IncrAgentPopupCount indicates an expected call of IncrAgentPopupCount.
func (mr *MockSingleAgentMockRecorder) IncrAgentPopupCount(ctx, uid, agentID, agentPopupType any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrAgentPopupCount", reflect.TypeOf((*MockSingleAgent)(nil).IncrAgentPopupCount), ctx, uid, agentID, agentPopupType)
}
// ListAgentPublishHistory mocks base method.
func (m *MockSingleAgent) ListAgentPublishHistory(ctx context.Context, agentID int64, pageIndex, pageSize int32, connectorID *int64) ([]*entity.SingleAgentPublish, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAgentPublishHistory", ctx, agentID, pageIndex, pageSize, connectorID)
ret0, _ := ret[0].([]*entity.SingleAgentPublish)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAgentPublishHistory indicates an expected call of ListAgentPublishHistory.
func (mr *MockSingleAgentMockRecorder) ListAgentPublishHistory(ctx, agentID, pageIndex, pageSize, connectorID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAgentPublishHistory", reflect.TypeOf((*MockSingleAgent)(nil).ListAgentPublishHistory), ctx, agentID, pageIndex, pageSize, connectorID)
}
// MGetSingleAgentDraft mocks base method.
func (m *MockSingleAgent) MGetSingleAgentDraft(ctx context.Context, agentIDs []int64) ([]*entity.SingleAgent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MGetSingleAgentDraft", ctx, agentIDs)
ret0, _ := ret[0].([]*entity.SingleAgent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MGetSingleAgentDraft indicates an expected call of MGetSingleAgentDraft.
func (mr *MockSingleAgentMockRecorder) MGetSingleAgentDraft(ctx, agentIDs any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetSingleAgentDraft", reflect.TypeOf((*MockSingleAgent)(nil).MGetSingleAgentDraft), ctx, agentIDs)
}
// ObtainAgentByIdentity mocks base method.
func (m *MockSingleAgent) ObtainAgentByIdentity(ctx context.Context, identity *entity.AgentIdentity) (*entity.SingleAgent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ObtainAgentByIdentity", ctx, identity)
ret0, _ := ret[0].(*entity.SingleAgent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ObtainAgentByIdentity indicates an expected call of ObtainAgentByIdentity.
func (mr *MockSingleAgentMockRecorder) ObtainAgentByIdentity(ctx, identity any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ObtainAgentByIdentity", reflect.TypeOf((*MockSingleAgent)(nil).ObtainAgentByIdentity), ctx, identity)
}
// SavePublishRecord mocks base method.
func (m *MockSingleAgent) SavePublishRecord(ctx context.Context, p *entity.SingleAgentPublish, e *entity.SingleAgent) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SavePublishRecord", ctx, p, e)
ret0, _ := ret[0].(error)
return ret0
}
// SavePublishRecord indicates an expected call of SavePublishRecord.
func (mr *MockSingleAgentMockRecorder) SavePublishRecord(ctx, p, e any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SavePublishRecord", reflect.TypeOf((*MockSingleAgent)(nil).SavePublishRecord), ctx, p, e)
}
// StreamExecute mocks base method.
func (m *MockSingleAgent) StreamExecute(ctx context.Context, req *entity.ExecuteRequest) (*schema.StreamReader[*entity.AgentEvent], error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StreamExecute", ctx, req)
ret0, _ := ret[0].(*schema.StreamReader[*entity.AgentEvent])
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StreamExecute indicates an expected call of StreamExecute.
func (mr *MockSingleAgentMockRecorder) StreamExecute(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamExecute", reflect.TypeOf((*MockSingleAgent)(nil).StreamExecute), ctx, req)
}
// UpdateAgentDraftDisplayInfo mocks base method.
func (m *MockSingleAgent) UpdateAgentDraftDisplayInfo(ctx context.Context, userID int64, e *entity.AgentDraftDisplayInfo) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAgentDraftDisplayInfo", ctx, userID, e)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateAgentDraftDisplayInfo indicates an expected call of UpdateAgentDraftDisplayInfo.
func (mr *MockSingleAgentMockRecorder) UpdateAgentDraftDisplayInfo(ctx, userID, e any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAgentDraftDisplayInfo", reflect.TypeOf((*MockSingleAgent)(nil).UpdateAgentDraftDisplayInfo), ctx, userID, e)
}
// UpdateSingleAgentDraft mocks base method.
func (m *MockSingleAgent) UpdateSingleAgentDraft(ctx context.Context, agentInfo *entity.SingleAgent) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateSingleAgentDraft", ctx, agentInfo)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateSingleAgentDraft indicates an expected call of UpdateSingleAgentDraft.
func (mr *MockSingleAgentMockRecorder) UpdateSingleAgentDraft(ctx, agentInfo any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSingleAgentDraft", reflect.TypeOf((*MockSingleAgent)(nil).UpdateSingleAgentDraft), ctx, agentInfo)
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Code generated by MockGen. DO NOT EDIT.
// Source: domain/conversation/agentrun/service/agent_run.go
//
// Generated by this command:
//
// mockgen -destination internal/mock/domain/conversation/agentrun/agent_run_mock.go --package mock_agentrun -source domain/conversation/agentrun/service/agent_run.go
//
// Package mock_agentrun is a generated GoMock package.
package mock_agentrun
import (
context "context"
reflect "reflect"
schema "github.com/cloudwego/eino/schema"
entity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
gomock "go.uber.org/mock/gomock"
)
// MockRun is a mock of Run interface.
type MockRun struct {
ctrl *gomock.Controller
recorder *MockRunMockRecorder
isgomock struct{}
}
// MockRunMockRecorder is the mock recorder for MockRun.
type MockRunMockRecorder struct {
mock *MockRun
}
// NewMockRun creates a new mock instance.
func NewMockRun(ctrl *gomock.Controller) *MockRun {
mock := &MockRun{ctrl: ctrl}
mock.recorder = &MockRunMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRun) EXPECT() *MockRunMockRecorder {
return m.recorder
}
// AgentRun mocks base method.
func (m *MockRun) AgentRun(ctx context.Context, req *entity.AgentRunMeta) (*schema.StreamReader[*entity.AgentRunResponse], error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AgentRun", ctx, req)
ret0, _ := ret[0].(*schema.StreamReader[*entity.AgentRunResponse])
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AgentRun indicates an expected call of AgentRun.
func (mr *MockRunMockRecorder) AgentRun(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AgentRun", reflect.TypeOf((*MockRun)(nil).AgentRun), ctx, req)
}
// Cancel mocks base method.
func (m *MockRun) Cancel(ctx context.Context, req *entity.CancelRunMeta) (*entity.RunRecordMeta, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Cancel", ctx, req)
ret0, _ := ret[0].(*entity.RunRecordMeta)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Cancel indicates an expected call of Cancel.
func (mr *MockRunMockRecorder) Cancel(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cancel", reflect.TypeOf((*MockRun)(nil).Cancel), ctx, req)
}
// Create mocks base method.
func (m *MockRun) Create(ctx context.Context, runRecord *entity.AgentRunMeta) (*entity.RunRecordMeta, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, runRecord)
ret0, _ := ret[0].(*entity.RunRecordMeta)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create.
func (mr *MockRunMockRecorder) Create(ctx, runRecord any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRun)(nil).Create), ctx, runRecord)
}
// Delete mocks base method.
func (m *MockRun) Delete(ctx context.Context, runID []int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, runID)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockRunMockRecorder) Delete(ctx, runID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRun)(nil).Delete), ctx, runID)
}
// GetByID mocks base method.
func (m *MockRun) GetByID(ctx context.Context, runID int64) (*entity.RunRecordMeta, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", ctx, runID)
ret0, _ := ret[0].(*entity.RunRecordMeta)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockRunMockRecorder) GetByID(ctx, runID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockRun)(nil).GetByID), ctx, runID)
}
// List mocks base method.
func (m *MockRun) List(ctx context.Context, ListMeta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, ListMeta)
ret0, _ := ret[0].([]*entity.RunRecordMeta)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockRunMockRecorder) List(ctx, ListMeta any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRun)(nil).List), ctx, ListMeta)
}

View File

@ -0,0 +1,163 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Code generated by MockGen. DO NOT EDIT.
// Source: domain/conversation/conversation/service/conversation.go
//
// Generated by this command:
//
// mockgen -destination internal/mock/domain/conversation/conversation/conversation_mock.go --package mock_conversation -source domain/conversation/conversation/service/conversation.go
//
// Package mock_conversation is a generated GoMock package.
package mock_conversation
import (
context "context"
reflect "reflect"
entity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
gomock "go.uber.org/mock/gomock"
)
// MockConversation is a mock of Conversation interface.
type MockConversation struct {
ctrl *gomock.Controller
recorder *MockConversationMockRecorder
isgomock struct{}
}
// MockConversationMockRecorder is the mock recorder for MockConversation.
type MockConversationMockRecorder struct {
mock *MockConversation
}
// NewMockConversation creates a new mock instance.
func NewMockConversation(ctrl *gomock.Controller) *MockConversation {
mock := &MockConversation{ctrl: ctrl}
mock.recorder = &MockConversationMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockConversation) EXPECT() *MockConversationMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockConversation) Create(ctx context.Context, req *entity.CreateMeta) (*entity.Conversation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, req)
ret0, _ := ret[0].(*entity.Conversation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create.
func (mr *MockConversationMockRecorder) Create(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockConversation)(nil).Create), ctx, req)
}
// Delete mocks base method.
func (m *MockConversation) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockConversationMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockConversation)(nil).Delete), ctx, id)
}
// GetByID mocks base method.
func (m *MockConversation) GetByID(ctx context.Context, id int64) (*entity.Conversation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", ctx, id)
ret0, _ := ret[0].(*entity.Conversation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockConversationMockRecorder) GetByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockConversation)(nil).GetByID), ctx, id)
}
// GetCurrentConversation mocks base method.
func (m *MockConversation) GetCurrentConversation(ctx context.Context, req *entity.GetCurrent) (*entity.Conversation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCurrentConversation", ctx, req)
ret0, _ := ret[0].(*entity.Conversation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentConversation indicates an expected call of GetCurrentConversation.
func (mr *MockConversationMockRecorder) GetCurrentConversation(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentConversation", reflect.TypeOf((*MockConversation)(nil).GetCurrentConversation), ctx, req)
}
// List mocks base method.
func (m *MockConversation) List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, req)
ret0, _ := ret[0].([]*entity.Conversation)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// List indicates an expected call of List.
func (mr *MockConversationMockRecorder) List(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockConversation)(nil).List), ctx, req)
}
// NewConversationCtx mocks base method.
func (m *MockConversation) NewConversationCtx(ctx context.Context, req *entity.NewConversationCtxRequest) (*entity.NewConversationCtxResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewConversationCtx", ctx, req)
ret0, _ := ret[0].(*entity.NewConversationCtxResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// NewConversationCtx indicates an expected call of NewConversationCtx.
func (mr *MockConversationMockRecorder) NewConversationCtx(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewConversationCtx", reflect.TypeOf((*MockConversation)(nil).NewConversationCtx), ctx, req)
}
// Update mocks base method.
func (m *MockConversation) Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, req)
ret0, _ := ret[0].(*entity.Conversation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
func (mr *MockConversationMockRecorder) Update(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockConversation)(nil).Update), ctx, req)
}

View File

@ -0,0 +1,132 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Code generated by MockGen. DO NOT EDIT.
// Source: domain/shortcutcmd/service/shortcut_cmd.go
//
// Generated by this command:
//
// mockgen -destination internal/mock/domain/shortcutcmd/shortcut_cmd_mock.go --package mock_shortcutcmd -source domain/shortcutcmd/service/shortcut_cmd.go
//
// Package mock_shortcutcmd is a generated GoMock package.
package mock_shortcutcmd
import (
context "context"
reflect "reflect"
entity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
gomock "go.uber.org/mock/gomock"
)
// MockShortcutCmd is a mock of ShortcutCmd interface.
type MockShortcutCmd struct {
ctrl *gomock.Controller
recorder *MockShortcutCmdMockRecorder
isgomock struct{}
}
// MockShortcutCmdMockRecorder is the mock recorder for MockShortcutCmd.
type MockShortcutCmdMockRecorder struct {
mock *MockShortcutCmd
}
// NewMockShortcutCmd creates a new mock instance.
func NewMockShortcutCmd(ctrl *gomock.Controller) *MockShortcutCmd {
mock := &MockShortcutCmd{ctrl: ctrl}
mock.recorder = &MockShortcutCmdMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockShortcutCmd) EXPECT() *MockShortcutCmdMockRecorder {
return m.recorder
}
// CreateCMD mocks base method.
func (m *MockShortcutCmd) CreateCMD(ctx context.Context, shortcut *entity.ShortcutCmd) (*entity.ShortcutCmd, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateCMD", ctx, shortcut)
ret0, _ := ret[0].(*entity.ShortcutCmd)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateCMD indicates an expected call of CreateCMD.
func (mr *MockShortcutCmdMockRecorder) CreateCMD(ctx, shortcut any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCMD", reflect.TypeOf((*MockShortcutCmd)(nil).CreateCMD), ctx, shortcut)
}
// GetByCmdID mocks base method.
func (m *MockShortcutCmd) GetByCmdID(ctx context.Context, cmdID int64, isOnline int32) (*entity.ShortcutCmd, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByCmdID", ctx, cmdID, isOnline)
ret0, _ := ret[0].(*entity.ShortcutCmd)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByCmdID indicates an expected call of GetByCmdID.
func (mr *MockShortcutCmdMockRecorder) GetByCmdID(ctx, cmdID, isOnline any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByCmdID", reflect.TypeOf((*MockShortcutCmd)(nil).GetByCmdID), ctx, cmdID, isOnline)
}
// ListCMD mocks base method.
func (m *MockShortcutCmd) ListCMD(ctx context.Context, lm *entity.ListMeta) ([]*entity.ShortcutCmd, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListCMD", ctx, lm)
ret0, _ := ret[0].([]*entity.ShortcutCmd)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListCMD indicates an expected call of ListCMD.
func (mr *MockShortcutCmdMockRecorder) ListCMD(ctx, lm any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCMD", reflect.TypeOf((*MockShortcutCmd)(nil).ListCMD), ctx, lm)
}
// PublishCMDs mocks base method.
func (m *MockShortcutCmd) PublishCMDs(ctx context.Context, objID int64, cmdIDs []int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PublishCMDs", ctx, objID, cmdIDs)
ret0, _ := ret[0].(error)
return ret0
}
// PublishCMDs indicates an expected call of PublishCMDs.
func (mr *MockShortcutCmdMockRecorder) PublishCMDs(ctx, objID, cmdIDs any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishCMDs", reflect.TypeOf((*MockShortcutCmd)(nil).PublishCMDs), ctx, objID, cmdIDs)
}
// UpdateCMD mocks base method.
func (m *MockShortcutCmd) UpdateCMD(ctx context.Context, shortcut *entity.ShortcutCmd) (*entity.ShortcutCmd, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateCMD", ctx, shortcut)
ret0, _ := ret[0].(*entity.ShortcutCmd)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateCMD indicates an expected call of UpdateCMD.
func (mr *MockShortcutCmdMockRecorder) UpdateCMD(ctx, shortcut any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCMD", reflect.TypeOf((*MockShortcutCmd)(nil).UpdateCMD), ctx, shortcut)
}

View File

@ -0,0 +1,118 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Code generated by MockGen. DO NOT EDIT.
// Source: domain/upload/service/interface.go
//
// Generated by this command:
//
// mockgen -destination internal/mock/domain/upload/upload_service_mock.go --package mock_upload -source domain/upload/service/interface.go UploadService
//
// Package mock_upload is a generated GoMock package.
package mock_upload
import (
context "context"
reflect "reflect"
service "github.com/coze-dev/coze-studio/backend/domain/upload/service"
gomock "go.uber.org/mock/gomock"
)
// MockUploadService is a mock of UploadService interface.
type MockUploadService struct {
ctrl *gomock.Controller
recorder *MockUploadServiceMockRecorder
isgomock struct{}
}
// MockUploadServiceMockRecorder is the mock recorder for MockUploadService.
type MockUploadServiceMockRecorder struct {
mock *MockUploadService
}
// NewMockUploadService creates a new mock instance.
func NewMockUploadService(ctrl *gomock.Controller) *MockUploadService {
mock := &MockUploadService{ctrl: ctrl}
mock.recorder = &MockUploadServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUploadService) EXPECT() *MockUploadServiceMockRecorder {
return m.recorder
}
// GetFile mocks base method.
func (m *MockUploadService) GetFile(ctx context.Context, req *service.GetFileRequest) (*service.GetFileResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetFile", ctx, req)
ret0, _ := ret[0].(*service.GetFileResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetFile indicates an expected call of GetFile.
func (mr *MockUploadServiceMockRecorder) GetFile(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFile", reflect.TypeOf((*MockUploadService)(nil).GetFile), ctx, req)
}
// GetFiles mocks base method.
func (m *MockUploadService) GetFiles(ctx context.Context, req *service.GetFilesRequest) (*service.GetFilesResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetFiles", ctx, req)
ret0, _ := ret[0].(*service.GetFilesResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetFiles indicates an expected call of GetFiles.
func (mr *MockUploadServiceMockRecorder) GetFiles(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFiles", reflect.TypeOf((*MockUploadService)(nil).GetFiles), ctx, req)
}
// UploadFile mocks base method.
func (m *MockUploadService) UploadFile(ctx context.Context, req *service.UploadFileRequest) (*service.UploadFileResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UploadFile", ctx, req)
ret0, _ := ret[0].(*service.UploadFileResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UploadFile indicates an expected call of UploadFile.
func (mr *MockUploadServiceMockRecorder) UploadFile(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadFile", reflect.TypeOf((*MockUploadService)(nil).UploadFile), ctx, req)
}
// UploadFiles mocks base method.
func (m *MockUploadService) UploadFiles(ctx context.Context, req *service.UploadFilesRequest) (*service.UploadFilesResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UploadFiles", ctx, req)
ret0, _ := ret[0].(*service.UploadFilesResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UploadFiles indicates an expected call of UploadFiles.
func (mr *MockUploadServiceMockRecorder) UploadFiles(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadFiles", reflect.TypeOf((*MockUploadService)(nil).UploadFiles), ctx, req)
}

View File

@ -59,6 +59,8 @@ const (
MQServer = "MQ_NAME_SERVER"
RMQSecretKey = "RMQ_SECRET_KEY"
RMQAccessKey = "RMQ_ACCESS_KEY"
PulsarServiceURL = "PULSAR_SERVICE_URL"
PulsarJWTToken = "PULSAR_JWT_TOKEN"
RMQTopicApp = "opencoze_search_app"
RMQTopicResource = "opencoze_search_resource"
RMQTopicKnowledge = "opencoze_knowledge"

View File

@ -74,12 +74,17 @@ export ES_PASSWORD=""
export ES_NUMBER_OF_SHARDS = "1"
export ES_NUMBER_OF_REPLICAS = "1"
export COZE_MQ_TYPE="nsq" # nsq / kafka / rmq
# Backend Event Bus
export COZE_MQ_TYPE="nsq" # nsq / kafka / rmq / pulsar
export MQ_NAME_SERVER="nsqd:4150"
# RocketMQ
export RMQ_ACCESS_KEY=""
export RMQ_SECRET_KEY=""
# Pulsar
# Use Pulsar as backend eventbus, MQ_NAME_SERVER example is: "pulsar:6650"
# Fill PULSAR_JWT_TOKEN for JWT auth, leave empty for no auth
export PULSAR_SERVICE_URL="pulsar://pulsar-service:6650"
export PULSAR_JWT_TOKEN=""
# Settings for VectorStore
# VectorStore type: milvus / vikingdb / oceanbase

View File

@ -0,0 +1,470 @@
# Pulsar EventBus Integration Guide
## Overview
This document provides a comprehensive guide for integrating Apache Pulsar as an EventBus in Coze Studio, including architecture design, implementation details, configuration instructions, and usage guidelines.
## Integration Background
### Why Choose Pulsar?
In Coze Studio's architecture, EventBus plays a critical role in asynchronous message delivery, including workflow execution, Agent communication, data processing pipelines, and other core functions. As user scale grows and business complexity increases, we need a more powerful and flexible message queue solution.
Pulsar, as a next-generation distributed messaging system, brings the following core advantages to Coze Studio:
1. **High Performance**: Pulsar provides low-latency, high-throughput messaging that can support Coze Studio's large-scale concurrent Agent execution and workflow processing
2. **Multi-tenancy**: Native support for multi-tenant architecture, perfectly matching Coze Studio's multi-user, multi-workspace business model
3. **Persistence**: Supports message persistence storage, ensuring the reliability of Agent execution states and workflow data, preventing task loss due to system restarts
4. **Horizontal Scaling**: Supports separation of compute and storage, easy to scale horizontally, enabling smooth scaling as Coze Studio's user base grows
5. **Message Ordering**: Pulsar provides strong consistency and message ordering guarantees, ensuring that Agent workflow steps execute in the correct sequence, preventing state confusion and data inconsistency
6. **Rich Features**: Supports message deduplication, delayed messages, dead letter queues, and other advanced features, providing stronger reliability guarantees for complex AI workflows
### Comparison with Other MQ Systems
| Feature | Pulsar | NSQ | Kafka | RocketMQ |
| ---------------------- | -------------- | -------------- | -------------- | -------------- |
| **Deployment Complexity** | Medium | Low | Medium | Medium |
| **Performance** | High | Medium | High | High |
| **Multi-tenancy** | Native Support | Not Supported | Limited | Limited |
| **Message Persistence** | Strong | Limited | Strong | Strong |
| **Message Ordering** | Strong | Weak | Strong | Strong |
| **Horizontal Scaling** | Excellent | Medium | Good | Good |
| **Scaling Speed** | Fast | Medium | Slow | Medium |
| **Operational Complexity** | Medium | Low | High | Medium |
| **Ecosystem** | Rich | Simple | Very Rich | Rich |
#### Detailed Comparison of Horizontal Scaling Capabilities
**Pulsar's Scaling Advantages**:
- **Compute-Storage Separation**: Broker (compute) and BookKeeper (storage) scale independently, allowing precise resource adjustment based on business needs
- **Stateless Brokers**: Broker nodes are stateless and can start/stop quickly, enabling second-level scaling
- **Automatic Load Balancing**: Automatic redistribution of Topics and Partitions when new Brokers are added
- **Hot Scaling**: Supports dynamic addition/removal of nodes without service interruption
**Comparison with Other MQ Systems**:
- **Kafka**: Requires manual Partition rebalancing, complex and time-consuming scaling process that may affect business operations
- **RocketMQ**: While supporting dynamic scaling, the coordination mechanism between NameServer and Broker is relatively complex
- **NSQ**: Single-machine architecture limits scaling capabilities, can only improve throughput by increasing Topic count
This excellent scaling capability makes Pulsar particularly suitable for scenarios like Coze Studio with rapid user growth and fluctuating business loads.
## Architecture Design
### Overall Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Coze Studio │ │ Pulsar │ │ EventBus │
│ Application │───▶│ Client │───▶│ Manager │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Apache Pulsar │
│ Cluster │
└─────────────────┘
```
### Core Components
#### 1. Pulsar Producer
**File Location**: `backend/infra/impl/eventbus/pulsar/producer.go`
**Core Functions**:
```go
type Producer interface {
Send(ctx context.Context, body []byte, opts ...SendOpt) error
BatchSend(ctx context.Context, bodyArr [][]byte, opts ...SendOpt) error
}
type producerImpl struct {
topic string
client pulsar.Client
producer pulsar.Producer
}
```
**Features**:
- Supports synchronous and asynchronous sending
- Batch sending for performance optimization
- JWT authentication support
- Graceful shutdown handling
#### 2. Pulsar Consumer
**File Location**: `backend/infra/impl/eventbus/pulsar/consumer.go`
**Core Functions**:
```go
func RegisterConsumer(serviceURL, topic, group string,
consumerHandler eventbus.ConsumerHandler,
opts ...eventbus.ConsumerOpt) error
```
**Features**:
- Exclusive mode consumption for message ordering
- Automatic retry and error handling
- Context cancellation support
- Message acknowledgment and negative acknowledgment mechanisms
#### 3. EventBus Factory
**File Location**: `backend/infra/impl/eventbus/eventbus.go`
**Integration Point**:
```go
case consts.MQTypePulsar:
return pulsar.NewProducer(nameServer, topic, group)
```
## Configuration
### Environment Variables
#### Required Configuration
```bash
# Message queue type
COZE_MQ_TYPE=pulsar
# Pulsar service address
MQ_NAME_SERVER=pulsar://localhost:6650
```
#### Optional Configuration
```bash
# JWT authentication token (if authentication is enabled)
PULSAR_JWT_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9.example_token
```
### Docker Configuration
#### Standalone Pulsar Deployment
```yaml
services:
pulsar:
image: apachepulsar/pulsar:3.0.12
container_name: coze-pulsar
restart: always
command: >
sh -c "bin/pulsar standalone"
ports:
- "6650:6650" # Pulsar service port
- "8080:8080" # Pulsar admin port
volumes:
- ./data/pulsar:/pulsar/data
networks:
- coze-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/admin/v2/clusters"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
```
#### Production Cluster Deployment
For production environments, it's recommended to use Pulsar cluster deployment to achieve high availability and better performance. Cluster deployment involves configuring multiple components including ZooKeeper, BookKeeper, and Broker, which can be quite complex.
**Production Environment Recommendations**:
- Use Pulsar cluster mode deployment for high availability
- Enable JWT authentication for security
- Configure appropriate resource limits and monitoring
For detailed cluster deployment configuration, please refer to the [Apache Pulsar Official Documentation](https://pulsar.apache.org/docs/4.1.x/deploy-kubernetes/).
## Usage Guide
### 1. Prepare Project
```bash
# Clone the project
git clone https://github.com/coze-dev/coze-studio.git
cd coze-studio
```
### 2. Modify Docker Compose Configuration
Add the Pulsar service to your `docker/docker-compose.yml` file:
```yaml
services:
# Add Pulsar service
pulsar:
image: apachepulsar/pulsar:3.0.12
container_name: coze-pulsar
restart: always
command: >
sh -c "bin/pulsar standalone"
ports:
- "6650:6650" # Pulsar service port
- "8080:8080" # Pulsar admin port
volumes:
- ./data/pulsar:/pulsar/data
networks:
- coze-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/admin/v2/clusters"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
# Other existing services...
```
### 3. Configure Environment Variables
Modify the `.env` file to configure Coze Studio to use Pulsar:
```bash
# Enter docker directory
cd docker
# Copy environment configuration file
cp .env.example .env
# Edit .env file and add the following configuration:
# Message queue type
COZE_MQ_TYPE=pulsar
# Pulsar service address
MQ_NAME_SERVER=pulsar://pulsar:6650
# JWT authentication token (optional, if authentication is enabled)
# PULSAR_JWT_TOKEN=your_jwt_token_here
```
### 4. Start Services
```bash
# Start complete Coze Studio services including Pulsar
docker-compose up -d
# Check service startup status
docker-compose ps
```
### 5. Verify Deployment
```bash
# Check Pulsar container status
docker ps | grep pulsar
# Check Pulsar health status
curl -f http://localhost:8080/admin/v2/clusters
# View Pulsar logs
docker logs coze-pulsar
# Test Pulsar connection
docker exec -it coze-pulsar bin/pulsar-admin clusters list
```
### 6. Access Services
- **Coze Studio**: `http://localhost:3000` (based on actual configuration)
- **Pulsar Admin**: `http://localhost:8080`
Now Coze Studio has successfully integrated Pulsar as the message queue, and all EventBus functionality will be handled through Pulsar.
## Appendix
### A. Production Cluster Deployment
For production environments, it's recommended to use Pulsar cluster deployment to achieve high availability and better performance. Cluster deployment involves configuring multiple components including ZooKeeper, BookKeeper, and Broker, which can be quite complex.
**Production Environment Recommendations**:
- Use Pulsar cluster mode deployment for high availability
- Enable JWT authentication for security
- Configure appropriate resource limits and monitoring
For detailed cluster deployment configuration, please refer to the [Apache Pulsar Official Documentation](https://pulsar.apache.org/docs/4.1.x/deploy-kubernetes/).
### B. Visual Management Tools
For users who need a graphical interface to manage Pulsar clusters, consider using ASP Community Edition. ASP Community Edition is a modern management platform designed specifically for Apache Pulsar, providing an intuitive web interface to manage clusters, tenants, namespaces, topics, and other resources. The platform supports real-time monitoring, performance metrics display, configuration management, and other features that greatly simplify the daily operations of Pulsar clusters.
For more information, please refer to: [ASP Community Edition Documentation](https://ascentstream.com/docs/asp/asp-community/overview)
### C. Integration Features
#### 1. Design Principles
**Architecture Compatibility Design**:
- Strictly follows Coze Studio EventBus interface specifications for seamless integration
- Uses factory pattern for unified management of multiple MQ systems
- Maintains interface consistency with NSQ, Kafka, and RocketMQ implementations
**Performance First**:
- Asynchronous batch sending reduces network overhead
- Connection pooling reduces connection costs
- Message acknowledgment mechanism ensures reliability
**Easy Deployment**:
- Standalone mode for quick startup
- Docker containerized deployment
- Environment variable configuration for flexibility
#### 2. Technical Highlights
**JWT Authentication Support**:
```go
// Automatically detect and configure JWT authentication
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
logs.Debugf("Using JWT authentication, token length: %d", len(jwtToken))
}
```
**Batch Sending Optimization**:
```go
// Asynchronous batch sending for improved performance
for _, body := range bodyArr {
msg := &pulsar.ProducerMessage{Payload: body}
if option.ShardingKey != nil {
msg.Key = *option.ShardingKey
}
p.producer.SendAsync(ctx, msg, callback)
}
```
**Graceful Shutdown Handling**:
```go
// Listen for system signals and gracefully close resources
safego.Go(context.Background(), func() {
signal.WaitExit()
logs.Infof("shutting down pulsar consumer for topic: %s, group: %s", topic, group)
cancel()
consumer.Close()
client.Close()
})
```
### C. Troubleshooting
#### 1. Common Issues
**Connection Issues**:
```bash
# Check Pulsar service status
docker exec -it coze-pulsar bin/pulsar-admin brokers healthcheck
# Check network connectivity
telnet localhost 6650
# View connection configuration
docker exec -it coze-pulsar cat conf/standalone.conf | grep -E "(advertisedAddress|bindAddress)"
```
**Authentication Issues**:
```bash
# Check JWT Token configuration
echo $PULSAR_JWT_TOKEN
# Verify token validity
docker exec -it coze-pulsar bin/pulsar-admin --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationToken \
--auth-params token:$PULSAR_JWT_TOKEN \
clusters list
```
**Performance Issues**:
```bash
# View topic backlog
docker exec -it coze-pulsar bin/pulsar-admin topics stats persistent://public/default/your-topic
# Adjust batch sending parameters
# Batch size and delay can be configured through SendOpt in code
```
#### 2. Log Analysis
```bash
# View Pulsar service logs
docker logs coze-pulsar
# View Pulsar-related information in application logs
tail -f logs/coze-studio.log | grep -i "pulsar\|eventbus"
# Enable verbose logging
# Set rootLogLevel=DEBUG in Pulsar configuration
```
#### 3. Monitoring Metrics
```bash
# Get broker metrics
curl http://localhost:8080/metrics/
# Get specific topic metrics
curl http://localhost:8080/admin/v2/persistent/public/default/your-topic/stats
# Monitor consumption lag
docker exec -it coze-pulsar bin/pulsar-admin topics subscriptions persistent://public/default/your-topic
```
### D. Best Practices
#### 1. Production Environment Configuration
```bash
# Recommended production environment configuration
COZE_MQ_TYPE=pulsar
MQ_NAME_SERVER=pulsar://pulsar-broker-1:6650,pulsar://pulsar-broker-2:6650,pulsar://pulsar-broker-3:6650
PULSAR_JWT_TOKEN=your-production-jwt-token
# Pulsar cluster configuration
# Recommend at least 3 Broker nodes
# Recommend at least 3 BookKeeper nodes
# Recommend at least 3 ZooKeeper nodes
```
#### 2. Performance Tuning
```bash
# Producer configuration optimization
# Batch size: 1000 messages or 1MB
# Send timeout: 30 seconds
# Compression algorithm: LZ4
# Consumer configuration optimization
# Receive queue size: 1000
# Acknowledgment timeout: 30 seconds
# Consumer type: Exclusive (ensures ordering)
```
#### 3. Security Configuration
```bash
# Enable JWT authentication
PULSAR_JWT_TOKEN=your-jwt-token
# Configure Access Control Lists (ACL)
# Configure topic-level permissions through Pulsar Admin tools
```
## Summary
The Apache Pulsar EventBus integration in Coze Studio achieves the following goals:
1. **High Performance**: Supports high-throughput, low-latency messaging
2. **High Reliability**: Message persistence storage with acknowledgment mechanisms
3. **Easy Scaling**: Supports horizontal scaling to accommodate business growth
4. **Easy Operations**: Rich management tools and monitoring metrics
5. **Enterprise-grade**: Multi-tenancy support for enterprise applications
Through this integration, Coze Studio provides users with a high-performance, highly reliable, and easily scalable message queue solution, particularly suitable for scenarios requiring high throughput, low latency, and enterprise-grade features.
## Related Links
- [Apache Pulsar Official Documentation](https://pulsar.apache.org/docs/)
- [Pulsar Go Client Documentation](https://pulsar.apache.org/docs/client-libraries-go/)
- [ASP Community Edition Documentation](https://ascentstream.com/docs/asp/asp-community/overview)
- [Coze Studio Project Repository](https://github.com/coze-dev/coze-studio)

View File

@ -0,0 +1,411 @@
# Pulsar EventBus 集成指南
## 概述
本文档详细介绍了 Apache Pulsar 作为 EventBus 在 Coze Studio 中的集成适配情况,包括架构设计、实现细节、配置说明和使用指南。
## 集成背景
### 为什么选择 Pulsar
在 Coze Studio 的架构中EventBus 承担着关键的异步消息传递任务包括工作流执行、Agent 通信、数据处理管道等核心功能。随着用户规模的增长和业务复杂度的提升,我们需要一个更加强大和灵活的消息队列解决方案。
Pulsar 作为新一代的分布式消息系统,为 Coze Studio 带来了以下核心优势:
1. **高性能**: Pulsar 提供低延迟、高吞吐量的消息传递,能够支撑 Coze Studio 大规模并发的 Agent 执行和工作流处理
2. **多租户**: 原生支持多租户架构,完美契合 Coze Studio 多用户、多工作空间的业务模式
3. **持久化**: 支持消息持久化存储,确保 Agent 执行状态和工作流数据的可靠性,避免因系统重启导致的任务丢失
4. **水平扩展**: 支持计算和存储分离,易于水平扩展,能够随着 Coze Studio 用户增长而平滑扩容
5. **顺序性保障**: Pulsar 提供强一致性的消息顺序保证,确保 Agent 工作流中的步骤按正确顺序执行,避免状态混乱和数据不一致
6. **丰富特性**: 支持消息去重、延迟消息、死信队列等高级特性,为复杂的 AI 工作流提供更强的可靠性保障
### 与其他 MQ 的对比
| 特性 | Pulsar | NSQ | Kafka | RocketMQ |
| ---------------------- | -------------- | -------------- | -------------- | -------------- |
| **部署复杂度** | 中等 | 低 | 中等 | 中等 |
| **性能** | 高 | 中等 | 高 | 高 |
| **多租户支持** | 原生支持 | 不支持 | 有限支持 | 有限支持 |
| **消息持久化** | 强 | 有限 | 强 | 强 |
| **顺序性保障** | 强 | 弱 | 强 | 强 |
| **水平扩展性** | 优秀 | 中等 | 良好 | 良好 |
| **扩缩容速度** | 快速 | 中等 | 慢 | 中等 |
| **运维复杂度** | 中等 | 低 | 高 | 中等 |
| **生态系统** | 丰富 | 简单 | 非常丰富 | 丰富 |
#### 水平扩展能力详细对比
**Pulsar 的扩展优势**
- **计算存储分离**Broker计算和 BookKeeper存储独立扩展可以根据业务需求精确调整资源
- **无状态 Broker**Broker 节点无状态,可以快速启动和停止,实现秒级扩缩容
- **自动负载均衡**:新增 Broker 后自动进行 Topic 和 Partition 的负载重分配
- **热扩容**:支持在不停服的情况下动态增减节点,对业务无影响
**与其他 MQ 的对比**
- **Kafka**:需要手动进行 Partition 重分配,扩容过程复杂且耗时,可能影响业务
- **RocketMQ**:虽然支持动态扩容,但 NameServer 和 Broker 的协调机制相对复杂
- **NSQ**:单机架构限制了扩展能力,只能通过增加 Topic 数量来提升吞吐量
这种优秀的扩展能力使得 Pulsar 特别适合 Coze Studio 这种用户增长快速、业务负载波动大的场景。
## 架构设计
### 整体架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Coze Studio │ │ Pulsar │ │ EventBus │
│ Application │───▶│ Client │───▶│ Manager │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Apache Pulsar │
│ Cluster │
└─────────────────┘
```
### 核心组件
#### 1. Pulsar Producer
**文件位置**: `backend/infra/impl/eventbus/pulsar/producer.go`
**核心功能**:
```go
type Producer interface {
Send(ctx context.Context, body []byte, opts ...SendOpt) error
BatchSend(ctx context.Context, bodyArr [][]byte, opts ...SendOpt) error
}
type producerImpl struct {
topic string
client pulsar.Client
producer pulsar.Producer
}
```
**特性**:
- 支持同步和异步发送
- 批量发送优化性能
- JWT 认证支持
- 优雅关闭处理
#### 2. Pulsar Consumer
**文件位置**: `backend/infra/impl/eventbus/pulsar/consumer.go`
**核心功能**:
```go
func RegisterConsumer(serviceURL, topic, group string,
consumerHandler eventbus.ConsumerHandler,
opts ...eventbus.ConsumerOpt) error
```
**特性**:
- 独占模式消费,保证消息顺序
- 自动重试和错误处理
- 上下文取消支持
- 消息确认和否认机制
#### 3. EventBus 工厂
**文件位置**: `backend/infra/impl/eventbus/eventbus.go`
**集成点**:
```go
case consts.MQTypePulsar:
return pulsar.NewProducer(nameServer, topic, group)
```
## 使用指南
### 1. 准备项目
```bash
# 克隆项目
git clone https://github.com/coze-dev/coze-studio.git
cd coze-studio
```
### 2. 修改 Docker Compose 配置
`docker/docker-compose.yml` 文件中添加 Pulsar 服务:
```yaml
services:
# 添加 Pulsar 服务
pulsar:
image: apachepulsar/pulsar:3.0.12
container_name: coze-pulsar
restart: always
command: >
sh -c "bin/pulsar standalone"
ports:
- "6650:6650" # Pulsar 服务端口
- "8080:8080" # Pulsar 管理端口
volumes:
- ./data/pulsar:/pulsar/data
networks:
- coze-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/admin/v2/clusters"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
# 其他现有服务...
```
### 3. 配置环境变量
修改 `.env` 文件,配置 Coze Studio 使用 Pulsar
```bash
# 进入 docker 目录
cd docker
# 复制环境配置文件
cp .env.example .env
# 编辑 .env 文件,添加以下配置:
# 消息队列类型
COZE_MQ_TYPE=pulsar
# Pulsar 服务地址
MQ_NAME_SERVER=pulsar://pulsar:6650
# JWT 认证 Token可选如果启用了认证
# PULSAR_JWT_TOKEN=your_jwt_token_here
```
### 4. 启动服务
```bash
# 启动包含 Pulsar 的完整 Coze Studio 服务
docker-compose up -d
# 查看服务启动状态
docker-compose ps
```
### 5. 验证部署
```bash
# 检查 Pulsar 容器状态
docker ps | grep pulsar
# 检查 Pulsar 健康状态
curl -f http://localhost:8080/admin/v2/clusters
# 查看 Pulsar 日志
docker logs coze-pulsar
# 测试 Pulsar 连接
docker exec -it coze-pulsar bin/pulsar-admin clusters list
```
### 6. 访问服务
- **Coze Studio**: `http://localhost:3000`(根据实际配置)
- **Pulsar Admin**: `http://localhost:8080`
现在 Coze Studio 已经成功集成 Pulsar 作为消息队列,所有的事件总线功能都将通过 Pulsar 处理。
## 附录
### A. 生产环境集群部署
生产环境推荐使用 Pulsar 集群部署以获得高可用性和更好的性能。集群部署涉及 ZooKeeper、BookKeeper 和 Broker 等多个组件的配置,配置较为复杂。
**生产环境建议**
- 使用 Pulsar 集群模式部署,确保高可用性
- 开启 JWT 认证,保障系统安全
- 配置适当的资源限制和监控
详细的集群部署配置请参考 [Apache Pulsar 官方文档](https://pulsar.apache.org/docs/4.1.x/deploy-kubernetes/)。
### B. 可视化管理工具
对于需要图形化界面管理 Pulsar 集群的用户,可以考虑使用 ASP 社区版。ASP 社区版是一个专为 Apache Pulsar 设计的现代化管理平台,提供了直观的 Web 界面来管理集群、租户、命名空间、主题等资源。该平台支持实时监控、性能指标展示、配置管理等功能,大大简化了 Pulsar 集群的日常运维工作。
更多信息请参考:[ASP 社区版文档](https://ascentstream.com/docs/asp/asp-community/overview)
### C. 适配特点
#### 1. 设计原则
**架构兼容性设计**
- 严格遵循 Coze Studio EventBus 接口规范,确保与现有系统无缝集成
- 采用工厂模式实现多种 MQ 的统一管理
- 保持与 NSQ、Kafka、RocketMQ 实现的接口一致性
**性能优先**
- 异步批量发送减少网络开销
- 连接池复用降低连接成本
- 消息确认机制保证可靠性
**易于部署**
- 单机模式快速启动
- Docker 容器化部署
- 环境变量配置,灵活易用
#### 2. 技术亮点
**JWT 认证支持**
```go
// 自动检测和配置 JWT 认证
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
logs.Debugf("Using JWT authentication, token length: %d", len(jwtToken))
}
```
**批量发送优化**
```go
// 异步批量发送提高性能
for _, body := range bodyArr {
msg := &pulsar.ProducerMessage{Payload: body}
if option.ShardingKey != nil {
msg.Key = *option.ShardingKey
}
p.producer.SendAsync(ctx, msg, callback)
}
```
**优雅关闭处理**
```go
// 监听系统信号,优雅关闭资源
safego.Go(context.Background(), func() {
signal.WaitExit()
logs.Infof("shutting down pulsar consumer for topic: %s, group: %s", topic, group)
cancel()
consumer.Close()
client.Close()
})
```
### C. 故障排查
#### 1. 常见问题
**连接问题**
```bash
# 检查 Pulsar 服务状态
docker exec -it coze-pulsar bin/pulsar-admin brokers healthcheck
# 检查网络连通性
telnet localhost 6650
# 查看连接配置
docker exec -it coze-pulsar cat conf/standalone.conf | grep -E "(advertisedAddress|bindAddress)"
```
**认证问题**
```bash
# 检查 JWT Token 配置
echo $PULSAR_JWT_TOKEN
# 验证 Token 有效性
docker exec -it coze-pulsar bin/pulsar-admin --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationToken \
--auth-params token:$PULSAR_JWT_TOKEN \
clusters list
```
**性能问题**
```bash
# 查看 Topic 积压情况
docker exec -it coze-pulsar bin/pulsar-admin topics stats persistent://public/default/your-topic
# 调整批量发送参数
# 在代码中可以通过 SendOpt 配置批量大小和延迟
```
#### 2. 日志分析
```bash
# 查看 Pulsar 服务日志
docker logs coze-pulsar
# 查看应用日志中的 Pulsar 相关信息
tail -f logs/coze-studio.log | grep -i "pulsar\|eventbus"
# 启用详细日志
# 在 Pulsar 配置中设置 rootLogLevel=DEBUG
```
#### 3. 监控指标
```bash
# 获取 Broker 指标
curl http://localhost:8080/metrics/
# 获取特定 Topic 指标
curl http://localhost:8080/admin/v2/persistent/public/default/your-topic/stats
# 监控消费延迟
docker exec -it coze-pulsar bin/pulsar-admin topics subscriptions persistent://public/default/your-topic
```
### D. 最佳实践
#### 1. 生产环境配置
```bash
# 推荐的生产环境配置
COZE_MQ_TYPE=pulsar
MQ_NAME_SERVER=pulsar://pulsar-broker-1:6650,pulsar://pulsar-broker-2:6650,pulsar://pulsar-broker-3:6650
PULSAR_JWT_TOKEN=your-production-jwt-token
# Pulsar 集群配置
# 建议至少 3 个 Broker 节点
# 建议至少 3 个BookKeeper 节点
# 建议至少 3 个 ZooKeeper 节点
```
#### 2. 性能调优
```bash
# Producer 配置优化
# 批量发送大小1000 条消息或 1MB
# 发送超时30 秒
# 压缩算法LZ4
# Consumer 配置优化
# 接收队列大小1000
# 确认超时30 秒
# 消费者类型Exclusive保证顺序
```
#### 3. 安全配置
```bash
# 启用 JWT 认证
PULSAR_JWT_TOKEN=your-jwt-token
# 配置访问控制列表ACL
# 通过 Pulsar Admin 工具配置 Topic 级别的权限
```
## 总结
Apache Pulsar 在 Coze Studio 中的 EventBus 集成实现了以下目标:
1. **高性能**: 支持高吞吐量、低延迟的消息传递
2. **高可靠**: 消息持久化存储,支持消息确认机制
3. **易扩展**: 支持水平扩展,适应业务增长
4. **易运维**: 丰富的管理工具和监控指标
5. **企业级**: 多租户支持,适合企业级应用场景
通过这次集成Coze Studio 为用户提供了一个高性能、高可靠、易扩展的消息队列解决方案,特别适合需要高吞吐量、低延迟、企业级特性的场景。
## 相关链接
- [Apache Pulsar 官方文档](https://pulsar.apache.org/docs/)
- [Pulsar Go Client 文档](https://pulsar.apache.org/docs/client-libraries-go/)
- [ASP 社区版文档](https://ascentstream.com/docs/asp/asp-community/overview)
- [Coze Studio 项目地址](https://github.com/coze-dev/coze-studio)

View File

@ -1,3 +1,3 @@
# @flow-platform/chat-app-sdk
https://bytedance.larkoffice.com/wiki/IdTkw7Kd5iahWLkv6FKcIo53nBe
https://github.com/coze-dev/coze-studio/wiki/8.-Chat-SDK