feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
/*
* 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 singleagent
import (
"context"
"time"
modelmgrEntity "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
intelligence "github.com/coze-dev/coze-studio/backend/api/model/intelligence/common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/developer_api"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
"github.com/coze-dev/coze-studio/backend/domain/modelmgr"
searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func (s *SingleAgentApplicationService) CreateSingleAgentDraft(ctx context.Context, req *developer_api.DraftBotCreateRequest) (*developer_api.DraftBotCreateResponse, error) {
do, err := s.draftBotCreateRequestToSingleAgent(ctx, req)
if err != nil {
return nil, err
}
userID := ctxutil.MustGetUIDFromCtx(ctx)
agentID, err := s.DomainSVC.CreateSingleAgentDraft(ctx, userID, do)
if err != nil {
return nil, err
}
err = s.appContext.EventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Created,
Project: &searchEntity.ProjectDocument{
Status: intelligence.IntelligenceStatus_Using,
Type: intelligence.IntelligenceType_Bot,
ID: agentID,
SpaceID: &req.SpaceID,
OwnerID: &userID,
Name: &do.Name,
},
})
if err != nil {
return nil, err
}
return &developer_api.DraftBotCreateResponse{Data: &developer_api.DraftBotCreateData{
BotID: agentID,
}}, nil
}
func (s *SingleAgentApplicationService) draftBotCreateRequestToSingleAgent(ctx context.Context, req *developer_api.DraftBotCreateRequest) (*entity.SingleAgent, error) {
sa, err := s.newDefaultSingleAgent(ctx)
if err != nil {
return nil, err
}
sa.SpaceID = req.SpaceID
sa.Name = req.GetName()
sa.Desc = req.GetDescription()
sa.IconURI = req.GetIconURI()
return sa, nil
}
func (s *SingleAgentApplicationService) newDefaultSingleAgent(ctx context.Context) (*entity.SingleAgent, error) {
mi, err := s.defaultModelInfo(ctx)
if err != nil {
return nil, err
}
now := time.Now().UnixMilli()
return &entity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
OnboardingInfo: &bot_common.OnboardingInfo{},
ModelInfo: mi,
Prompt: &bot_common.PromptInfo{},
Plugin: []*bot_common.PluginInfo{},
Knowledge: &bot_common.Knowledge{
TopK: ptr.Of(int64(1)),
MinScore: ptr.Of(float64(0.01)),
SearchStrategy: ptr.Of(bot_common.SearchStrategy_SemanticSearch),
RecallStrategy: &bot_common.RecallStrategy{
UseNl2sql: ptr.Of(true),
UseRerank: ptr.Of(true),
UseRewrite: ptr.Of(true),
},
},
Workflow: []*bot_common.WorkflowInfo{},
SuggestReply: &bot_common.SuggestReplyInfo{},
JumpConfig: &bot_common.JumpConfig{},
Database: []*bot_common.Database{},
CreatedAt: now,
UpdatedAt: now,
},
}, nil
}
func (s *SingleAgentApplicationService) defaultModelInfo(ctx context.Context) (*bot_common.ModelInfo, error) {
modelResp, err := s.appContext.ModelMgrDomainSVC.ListModel(ctx, &modelmgr.ListModelRequest{
Status: []modelmgrEntity.ModelEntityStatus{modelmgrEntity.ModelEntityStatusDefault, modelmgrEntity.ModelEntityStatusInUse},
Limit: 1,
Cursor: nil,
})
if err != nil {
return nil, err
}
if len(modelResp.ModelList) == 0 {
return nil, errorx.New(errno.ErrAgentResourceNotFound, errorx.KV("type", "model"), errorx.KV("id", "default"))
}
dm := modelResp.ModelList[0]
var temperature *float64
if tp, ok := dm.FindParameter(modelmgrEntity.Temperature); ok {
t, err := tp.GetFloat(modelmgrEntity.DefaultTypeBalance)
if err != nil {
return nil, err
}
temperature = ptr.Of(t)
}
var maxTokens *int32
if tp, ok := dm.FindParameter(modelmgrEntity.MaxTokens); ok {
t, err := tp.GetInt(modelmgrEntity.DefaultTypeBalance)
if err != nil {
return nil, err
}
maxTokens = ptr.Of(int32(t))
} else if dm.Meta.ConnConfig.MaxTokens != nil {
maxTokens = ptr.Of(int32(*dm.Meta.ConnConfig.MaxTokens))
}
var topP *float64
if tp, ok := dm.FindParameter(modelmgrEntity.TopP); ok {
t, err := tp.GetFloat(modelmgrEntity.DefaultTypeBalance)
if err != nil {
return nil, err
}
topP = ptr.Of(t)
}
var topK *int32
if tp, ok := dm.FindParameter(modelmgrEntity.TopK); ok {
t, err := tp.GetInt(modelmgrEntity.DefaultTypeBalance)
if err != nil {
return nil, err
}
topK = ptr.Of(int32(t))
}
var frequencyPenalty *float64
if tp, ok := dm.FindParameter(modelmgrEntity.FrequencyPenalty); ok {
t, err := tp.GetFloat(modelmgrEntity.DefaultTypeBalance)
if err != nil {
return nil, err
}
frequencyPenalty = ptr.Of(t)
}
var presencePenalty *float64
if tp, ok := dm.FindParameter(modelmgrEntity.PresencePenalty); ok {
t, err := tp.GetFloat(modelmgrEntity.DefaultTypeBalance)
if err != nil {
return nil, err
}
presencePenalty = ptr.Of(t)
}
return &bot_common.ModelInfo{
ModelId: ptr.Of(dm.ID),
Temperature: temperature,
MaxTokens: maxTokens,
TopP: topP,
FrequencyPenalty: frequencyPenalty,
PresencePenalty: presencePenalty,
TopK: topK,
ModelStyle: bot_common.ModelStylePtr(bot_common.ModelStyle_Balance),
ShortMemoryPolicy: &bot_common.ShortMemoryPolicy{
ContextMode: bot_common.ContextModePtr(bot_common.ContextMode_FunctionCall_2),
HistoryRound: ptr.Of[int32](3),
},
}, nil
}

View File

@@ -0,0 +1,177 @@
/*
* 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 singleagent
import (
"context"
intelligence "github.com/coze-dev/coze-studio/backend/api/model/intelligence/common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/developer_api"
"github.com/coze-dev/coze-studio/backend/api/model/project_memory"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossplugin"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
shortcutCMDEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
)
type duplicateAgentResourceFn func(ctx context.Context, appContext *ServiceComponents, oldAgent, newAgent *entity.SingleAgent) (*entity.SingleAgent, error)
func (s *SingleAgentApplicationService) DuplicateDraftBot(ctx context.Context, req *developer_api.DuplicateDraftBotRequest) (*developer_api.DuplicateDraftBotResponse, error) {
draftAgent, err := s.ValidateAgentDraftAccess(ctx, req.BotID)
if err != nil {
return nil, err
}
newAgentID, err := s.appContext.IDGen.GenID(ctx)
if err != nil {
return nil, err
}
userID := ctxutil.MustGetUIDFromCtx(ctx)
duplicateInfo := &entity.DuplicateInfo{
NewAgentID: newAgentID,
SpaceID: req.GetSpaceID(),
UserID: userID,
DraftAgent: draftAgent,
}
newAgent, err := s.DomainSVC.DuplicateInMemory(ctx, duplicateInfo)
if err != nil {
return nil, err
}
duplicateFns := []duplicateAgentResourceFn{
duplicateVariables,
duplicatePlugin,
duplicateShortCommand,
}
for _, fn := range duplicateFns {
newAgent, err = fn(ctx, s.appContext, draftAgent, newAgent)
if err != nil {
return nil, err
}
}
_, err = s.DomainSVC.CreateSingleAgentDraftWithID(ctx, userID, newAgentID, newAgent)
if err != nil {
return nil, err
}
userInfo, err := s.appContext.UserDomainSVC.GetUserInfo(ctx, userID)
if err != nil {
return nil, err
}
err = s.appContext.EventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Created,
Project: &searchEntity.ProjectDocument{
Status: intelligence.IntelligenceStatus_Using,
Type: intelligence.IntelligenceType_Bot,
ID: newAgent.AgentID,
SpaceID: &req.SpaceID,
OwnerID: &userID,
Name: &newAgent.Name,
},
})
if err != nil {
return nil, err
}
return &developer_api.DuplicateDraftBotResponse{
Data: &developer_api.DuplicateDraftBotData{
BotID: newAgent.AgentID,
Name: newAgent.Name,
UserInfo: &developer_api.Creator{
ID: userID,
Name: userInfo.Name,
AvatarURL: userInfo.IconURL,
Self: userID == draftAgent.CreatorID,
UserUniqueName: userInfo.UniqueName,
UserLabel: nil,
},
},
Code: 0,
}, nil
}
func duplicateVariables(ctx context.Context, appContext *ServiceComponents, oldAgent, newAgent *entity.SingleAgent) (*entity.SingleAgent, error) {
if oldAgent.VariablesMetaID == nil || *oldAgent.VariablesMetaID <= 0 {
return newAgent, nil
}
vars, err := appContext.VariablesDomainSVC.GetVariableMetaByID(ctx, *oldAgent.VariablesMetaID)
if err != nil {
return nil, err
}
vars.ID = 0
vars.BizID = conv.Int64ToStr(newAgent.AgentID)
vars.BizType = project_memory.VariableConnector_Bot
vars.Version = ""
vars.CreatorID = newAgent.CreatorID
varMetaID, err := appContext.VariablesDomainSVC.UpsertMeta(ctx, vars)
if err != nil {
return nil, err
}
newAgent.VariablesMetaID = &varMetaID
return newAgent, nil
}
func duplicatePlugin(ctx context.Context, _ *ServiceComponents, oldAgent, newAgent *entity.SingleAgent) (*entity.SingleAgent, error) {
err := crossplugin.DefaultSVC().DuplicateDraftAgentTools(ctx, oldAgent.AgentID, newAgent.AgentID)
if err != nil {
return nil, err
}
return newAgent, nil
}
func duplicateShortCommand(ctx context.Context, appContext *ServiceComponents, oldAgent, newAgent *entity.SingleAgent) (*entity.SingleAgent, error) {
metas, err := appContext.ShortcutCMDDomainSVC.ListCMD(ctx, &shortcutCMDEntity.ListMeta{
SpaceID: oldAgent.SpaceID,
ObjectID: oldAgent.AgentID,
IsOnline: 0,
CommandIDs: slices.Transform(oldAgent.ShortcutCommand, func(a string) int64 {
return conv.StrToInt64D(a, 0)
}),
})
if err != nil {
return nil, err
}
shortcutCommandIDs := make([]string, 0, len(metas))
for _, meta := range metas {
meta.ObjectID = newAgent.AgentID
meta.CreatorID = newAgent.CreatorID
do, err := appContext.ShortcutCMDDomainSVC.CreateCMD(ctx, meta)
if err != nil {
return nil, err
}
shortcutCommandIDs = append(shortcutCommandIDs, conv.Int64ToStr(do.CommandID))
}
newAgent.ShortcutCommand = shortcutCommandIDs
return newAgent, nil
}

View File

@@ -0,0 +1,587 @@
/*
* 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 singleagent
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/openapi3"
knowledgeModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/playground"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/workflow"
"github.com/coze-dev/coze-studio/backend/api/model/plugin_develop_common"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
knowledge "github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
"github.com/coze-dev/coze-studio/backend/domain/modelmgr"
modelEntity "github.com/coze-dev/coze-studio/backend/domain/modelmgr/entity"
pluginEntity "github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
"github.com/coze-dev/coze-studio/backend/domain/plugin/service"
shortcutCMDEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
workflowEntity "github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
"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/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func (s *SingleAgentApplicationService) GetAgentBotInfo(ctx context.Context, req *playground.GetDraftBotInfoAgwRequest) (*playground.GetDraftBotInfoAgwResponse, error) {
agentInfo, err := s.DomainSVC.GetSingleAgent(ctx, req.GetBotID(), req.GetVersion())
if err != nil {
return nil, err
}
if agentInfo == nil {
return nil, errorx.New(errno.ErrAgentInvalidParamCode, errorx.KVf("msg", "agent %d not found", req.GetBotID()))
}
vo, err := s.singleAgentDraftDo2Vo(ctx, agentInfo)
if err != nil {
return nil, err
}
klInfos, err := s.fetchKnowledgeDetails(ctx, agentInfo)
if err != nil {
return nil, err
}
modelInfos, err := s.fetchModelDetails(ctx, agentInfo)
if err != nil {
return nil, err
}
toolInfos, err := s.fetchToolDetails(ctx, agentInfo, req)
if err != nil {
return nil, err
}
pluginInfos, err := s.fetchPluginDetails(ctx, agentInfo, toolInfos)
if err != nil {
return nil, err
}
workflowInfos, err := s.fetchWorkflowDetails(ctx, agentInfo)
if err != nil {
return nil, err
}
shortCutCmdResp, err := s.fetchShortcutCMD(ctx, agentInfo)
if err != nil {
return nil, err
}
workflowDetailMap, err := workflowDo2Vo(workflowInfos)
if err != nil {
return nil, err
}
return &playground.GetDraftBotInfoAgwResponse{
Data: &playground.GetDraftBotInfoAgwData{
BotInfo: vo,
BotOptionData: &playground.BotOptionData{
ModelDetailMap: modelInfoDo2Vo(modelInfos),
KnowledgeDetailMap: knowledgeInfoDo2Vo(klInfos),
PluginAPIDetailMap: toolInfoDo2Vo(toolInfos),
PluginDetailMap: s.pluginInfoDo2Vo(ctx, pluginInfos),
WorkflowDetailMap: workflowDetailMap,
ShortcutCommandList: shortCutCmdResp,
},
SpaceID: agentInfo.SpaceID,
Editable: ptr.Of(true),
Deletable: ptr.Of(true),
},
}, nil
}
func (s *SingleAgentApplicationService) fetchShortcutCMD(ctx context.Context, agentInfo *entity.SingleAgent) ([]*playground.ShortcutCommand, error) {
var cmdVOs []*playground.ShortcutCommand
if len(agentInfo.ShortcutCommand) == 0 {
return cmdVOs, nil
}
cmdDOs, err := s.appContext.ShortcutCMDDomainSVC.ListCMD(ctx, &shortcutCMDEntity.ListMeta{
SpaceID: agentInfo.SpaceID,
ObjectID: agentInfo.AgentID,
CommandIDs: slices.Transform(agentInfo.ShortcutCommand, func(a string) int64 {
return conv.StrToInt64D(a, 0)
}),
})
logs.CtxInfof(ctx, "fetchShortcutCMD cmdDOs = %v, err = %v", conv.DebugJsonToStr(cmdDOs), err)
if err != nil {
return nil, err
}
cmdVOs = s.shortcutCMDDo2Vo(cmdDOs)
return cmdVOs, nil
}
func (s *SingleAgentApplicationService) shortcutCMDDo2Vo(cmdDOs []*shortcutCMDEntity.ShortcutCmd) []*playground.ShortcutCommand {
return slices.Transform(cmdDOs, func(cmdDO *shortcutCMDEntity.ShortcutCmd) *playground.ShortcutCommand {
return &playground.ShortcutCommand{
ObjectID: cmdDO.ObjectID,
CommandID: cmdDO.CommandID,
CommandName: cmdDO.CommandName,
ShortcutCommand: cmdDO.ShortcutCommand,
Description: cmdDO.Description,
SendType: playground.SendType(cmdDO.SendType),
ToolType: playground.ToolType(cmdDO.ToolType),
WorkFlowID: conv.Int64ToStr(cmdDO.WorkFlowID),
PluginID: conv.Int64ToStr(cmdDO.PluginID),
PluginAPIName: cmdDO.PluginToolName,
PluginAPIID: cmdDO.PluginToolID,
ShortcutIcon: cmdDO.ShortcutIcon,
TemplateQuery: cmdDO.TemplateQuery,
ComponentsList: cmdDO.Components,
CardSchema: cmdDO.CardSchema,
ToolInfo: cmdDO.ToolInfo,
}
})
}
func (s *SingleAgentApplicationService) fetchModelDetails(ctx context.Context, agentInfo *entity.SingleAgent) ([]*modelEntity.Model, error) {
if agentInfo.ModelInfo.ModelId == nil {
return nil, nil
}
modelID := agentInfo.ModelInfo.GetModelId()
modelInfos, err := s.appContext.ModelMgrDomainSVC.MGetModelByID(ctx, &modelmgr.MGetModelRequest{
IDs: []int64{modelID},
})
if err != nil {
return nil, fmt.Errorf("fetch model(%d) details failed: %v", modelID, err)
}
return modelInfos, nil
}
func (s *SingleAgentApplicationService) fetchKnowledgeDetails(ctx context.Context, agentInfo *entity.SingleAgent) ([]*knowledgeModel.Knowledge, error) {
knowledgeIDs := make([]int64, 0, len(agentInfo.Knowledge.KnowledgeInfo))
for _, v := range agentInfo.Knowledge.KnowledgeInfo {
id, err := conv.StrToInt64(v.GetId())
if err != nil {
return nil, fmt.Errorf("invalid knowledge id: %s", v.GetId())
}
knowledgeIDs = append(knowledgeIDs, id)
}
if len(knowledgeIDs) == 0 {
return nil, nil
}
listResp, err := s.appContext.KnowledgeDomainSVC.ListKnowledge(ctx, &knowledge.ListKnowledgeRequest{
IDs: knowledgeIDs,
})
if err != nil {
return nil, fmt.Errorf("fetch knowledge details failed: %v", err)
}
return listResp.KnowledgeList, err
}
func (s *SingleAgentApplicationService) fetchToolDetails(ctx context.Context, agentInfo *entity.SingleAgent, req *playground.GetDraftBotInfoAgwRequest) ([]*pluginEntity.ToolInfo, error) {
return s.appContext.PluginDomainSVC.MGetAgentTools(ctx, &service.MGetAgentToolsRequest{
SpaceID: agentInfo.SpaceID,
AgentID: req.GetBotID(),
IsDraft: true,
VersionAgentTools: slices.Transform(agentInfo.Plugin, func(a *bot_common.PluginInfo) pluginEntity.VersionAgentTool {
return pluginEntity.VersionAgentTool{
ToolID: a.GetApiId(),
}
}),
})
}
func (s *SingleAgentApplicationService) fetchPluginDetails(ctx context.Context, agentInfo *entity.SingleAgent, toolInfos []*pluginEntity.ToolInfo) ([]*pluginEntity.PluginInfo, error) {
vPlugins := make([]pluginEntity.VersionPlugin, 0, len(agentInfo.Plugin))
vPluginMap := make(map[string]bool, len(agentInfo.Plugin))
for _, v := range toolInfos {
k := fmt.Sprintf("%d:%s", v.PluginID, v.GetVersion())
if vPluginMap[k] {
continue
}
vPluginMap[k] = true
vPlugins = append(vPlugins, pluginEntity.VersionPlugin{
PluginID: v.PluginID,
Version: v.GetVersion(),
})
}
return s.appContext.PluginDomainSVC.MGetVersionPlugins(ctx, vPlugins)
}
func (s *SingleAgentApplicationService) fetchWorkflowDetails(ctx context.Context, agentInfo *entity.SingleAgent) ([]*workflowEntity.Workflow, error) {
if len(agentInfo.Workflow) == 0 {
return nil, nil
}
policy := &vo.MGetPolicy{
MetaQuery: vo.MetaQuery{
IDs: slices.Transform(agentInfo.Workflow, func(a *bot_common.WorkflowInfo) int64 {
return a.GetWorkflowId()
}),
},
QType: vo.FromLatestVersion,
}
ret, _, err := s.appContext.WorkflowDomainSVC.MGet(ctx, policy)
if err != nil {
return nil, fmt.Errorf("fetch workflow details failed: %v", err)
}
return ret, nil
}
func modelInfoDo2Vo(modelInfos []*modelEntity.Model) map[int64]*playground.ModelDetail {
return slices.ToMap(modelInfos, func(e *modelEntity.Model) (int64, *playground.ModelDetail) {
return e.ID, toModelDetail(e)
})
}
func toModelDetail(m *modelEntity.Model) *playground.ModelDetail {
mm := m.Meta
return &playground.ModelDetail{
Name: ptr.Of(m.Name),
ModelName: ptr.Of(m.Meta.Name),
ModelID: ptr.Of(m.ID),
ModelFamily: ptr.Of(int64(mm.Protocol.TOModelClass())),
ModelIconURL: ptr.Of(mm.IconURL),
}
}
func knowledgeInfoDo2Vo(klInfos []*knowledgeModel.Knowledge) map[string]*playground.KnowledgeDetail {
return slices.ToMap(klInfos, func(e *knowledgeModel.Knowledge) (string, *playground.KnowledgeDetail) {
return fmt.Sprintf("%v", e.ID), &playground.KnowledgeDetail{
ID: ptr.Of(fmt.Sprintf("%d", e.ID)),
Name: ptr.Of(e.Name),
IconURL: ptr.Of(e.IconURL),
FormatType: func() playground.DataSetType {
switch e.Type {
case knowledgeModel.DocumentTypeText:
return playground.DataSetType_Text
case knowledgeModel.DocumentTypeTable:
return playground.DataSetType_Table
case knowledgeModel.DocumentTypeImage:
return playground.DataSetType_Image
}
return playground.DataSetType_Text
}(),
}
})
}
func toolInfoDo2Vo(toolInfos []*pluginEntity.ToolInfo) map[int64]*playground.PluginAPIDetal {
return slices.ToMap(toolInfos, func(e *pluginEntity.ToolInfo) (int64, *playground.PluginAPIDetal) {
return e.ID, &playground.PluginAPIDetal{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.GetName()),
Description: ptr.Of(e.GetDesc()),
PluginID: ptr.Of(e.PluginID),
Parameters: parametersDo2Vo(e.Operation),
}
})
}
func (s *SingleAgentApplicationService) pluginInfoDo2Vo(ctx context.Context, pluginInfos []*pluginEntity.PluginInfo) map[int64]*playground.PluginDetal {
return slices.ToMap(pluginInfos, func(v *pluginEntity.PluginInfo) (int64, *playground.PluginDetal) {
e := v.PluginInfo
var iconURL string
if e.GetIconURI() != "" {
var err error
iconURL, err = s.appContext.TosClient.GetObjectUrl(ctx, e.GetIconURI())
if err != nil {
logs.CtxErrorf(ctx, "get icon url failed, err = %v", err)
}
}
return e.ID, &playground.PluginDetal{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.GetName()),
Description: ptr.Of(e.GetDesc()),
PluginType: (*int64)(&e.PluginType),
IconURL: &iconURL,
PluginStatus: (*int64)(ptr.Of(plugin_develop_common.PluginStatus_PUBLISHED)),
IsOfficial: func() *bool {
if e.SpaceID == 0 {
return ptr.Of(true)
}
return ptr.Of(false)
}(),
}
})
}
func parametersDo2Vo(op *plugin.Openapi3Operation) []*playground.PluginParameter {
var convertReqBody func(paramName string, isRequired bool, sc *openapi3.Schema) *playground.PluginParameter
convertReqBody = func(paramName string, isRequired bool, sc *openapi3.Schema) *playground.PluginParameter {
if disabledParam(sc) {
return nil
}
var assistType *int64
if v, ok := sc.Extensions[plugin.APISchemaExtendAssistType]; ok {
if _v, ok := v.(string); ok {
assistType = toParameterAssistType(_v)
}
}
paramInfo := &playground.PluginParameter{
Name: ptr.Of(paramName),
Type: ptr.Of(sc.Type),
Description: ptr.Of(sc.Description),
IsRequired: ptr.Of(isRequired),
AssistType: assistType,
}
switch sc.Type {
case openapi3.TypeObject:
required := slices.ToMap(sc.Required, func(e string) (string, bool) {
return e, true
})
subParams := make([]*playground.PluginParameter, 0, len(sc.Properties))
for subParamName, prop := range sc.Properties {
subParamInfo := convertReqBody(subParamName, required[subParamName], prop.Value)
if subParamInfo != nil {
subParams = append(subParams, subParamInfo)
}
}
paramInfo.SubParameters = subParams
return paramInfo
case openapi3.TypeArray:
paramInfo.SubType = ptr.Of(sc.Items.Value.Type)
if sc.Items.Value.Type != openapi3.TypeObject {
return paramInfo
}
required := slices.ToMap(sc.Required, func(e string) (string, bool) {
return e, true
})
subParams := make([]*playground.PluginParameter, 0, len(sc.Items.Value.Properties))
for subParamName, prop := range sc.Items.Value.Properties {
subParamInfo := convertReqBody(subParamName, required[subParamName], prop.Value)
if subParamInfo != nil {
subParams = append(subParams, subParamInfo)
}
}
paramInfo.SubParameters = subParams
return paramInfo
default:
return paramInfo
}
}
var params []*playground.PluginParameter
for _, prop := range op.Parameters {
paramVal := prop.Value
schemaVal := paramVal.Schema.Value
if schemaVal.Type == openapi3.TypeObject || schemaVal.Type == openapi3.TypeArray {
continue
}
if disabledParam(prop.Value.Schema.Value) {
continue
}
var assistType *int64
if v, ok := schemaVal.Extensions[plugin.APISchemaExtendAssistType]; ok {
if _v, ok := v.(string); ok {
assistType = toParameterAssistType(_v)
}
}
params = append(params, &playground.PluginParameter{
Name: ptr.Of(paramVal.Name),
Description: ptr.Of(paramVal.Description),
IsRequired: ptr.Of(paramVal.Required),
Type: ptr.Of(schemaVal.Type),
AssistType: assistType,
})
}
if op.RequestBody == nil || op.RequestBody.Value == nil || len(op.RequestBody.Value.Content) == 0 {
return params
}
for _, mType := range op.RequestBody.Value.Content {
schemaVal := mType.Schema.Value
if len(schemaVal.Properties) == 0 {
continue
}
required := slices.ToMap(schemaVal.Required, func(e string) (string, bool) {
return e, true
})
for paramName, prop := range schemaVal.Properties {
paramInfo := convertReqBody(paramName, required[paramName], prop.Value)
if paramInfo != nil {
params = append(params, paramInfo)
}
}
break // 只取一种 MIME
}
return params
}
func toParameterAssistType(assistType string) *int64 {
if assistType == "" {
return nil
}
switch plugin.APIFileAssistType(assistType) {
case plugin.AssistTypeFile:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_CODE))
case plugin.AssistTypeImage:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_IMAGE))
case plugin.AssistTypeDoc:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_DOC))
case plugin.AssistTypePPT:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_PPT))
case plugin.AssistTypeCode:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_CODE))
case plugin.AssistTypeExcel:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_EXCEL))
case plugin.AssistTypeZIP:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_ZIP))
case plugin.AssistTypeVideo:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_VIDEO))
case plugin.AssistTypeAudio:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_AUDIO))
case plugin.AssistTypeTXT:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_TXT))
default:
return nil
}
}
func workflowDo2Vo(wfInfos []*workflowEntity.Workflow) (map[int64]*playground.WorkflowDetail, error) {
result := make(map[int64]*playground.WorkflowDetail, len(wfInfos))
for _, e := range wfInfos {
parameters, err := slices.TransformWithErrorCheck(e.InputParams, toPluginParameter)
if err != nil {
return nil, err
}
result[e.ID] = &playground.WorkflowDetail{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.Name),
Description: ptr.Of(e.Desc),
IconURL: ptr.Of(e.IconURL),
PluginID: ptr.Of(e.ID),
APIDetail: &playground.PluginAPIDetal{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.Name),
Description: ptr.Of(e.Desc),
PluginID: ptr.Of(e.ID),
Parameters: parameters,
},
}
}
return result, nil
}
func toPluginParameter(info *vo.NamedTypeInfo) (*playground.PluginParameter, error) {
if info == nil {
return nil, fmt.Errorf("named type info is nil")
}
p := &playground.PluginParameter{
Name: ptr.Of(info.Name),
Description: ptr.Of(info.Desc),
IsRequired: ptr.Of(info.Required),
}
switch info.Type {
case vo.DataTypeString, vo.DataTypeFile, vo.DataTypeTime:
p.Type = ptr.Of("string")
if info.Type == vo.DataTypeFile {
p.AssistType = toWorkflowParameterAssistType(string(*info.FileType))
}
case vo.DataTypeInteger:
p.Type = ptr.Of("integer")
case vo.DataTypeNumber:
p.Type = ptr.Of("number")
case vo.DataTypeBoolean:
p.Type = ptr.Of("boolean")
case vo.DataTypeObject:
p.Type = ptr.Of("object")
p.SubParameters = make([]*playground.PluginParameter, 0, len(info.Properties))
for _, sub := range info.Properties {
subParameter, err := toPluginParameter(sub)
if err != nil {
return nil, err
}
p.SubParameters = append(p.SubParameters, subParameter)
}
case vo.DataTypeArray:
p.Type = ptr.Of("array")
eleParameter, err := toPluginParameter(info.ElemTypeInfo)
if err != nil {
return nil, err
}
p.SubType = eleParameter.Type
p.SubParameters = []*playground.PluginParameter{eleParameter}
default:
return nil, fmt.Errorf("unknown named type info type: %s", info.Type)
}
return p, nil
}
func toWorkflowParameterAssistType(assistType string) *int64 {
if assistType == "" {
return nil
}
switch vo.FileSubType(assistType) {
case vo.FileTypeDefault:
return ptr.Of(int64(workflow.AssistParameterType_DEFAULT))
case vo.FileTypeImage:
return ptr.Of(int64(workflow.AssistParameterType_IMAGE))
case vo.FileTypeDocument:
return ptr.Of(int64(workflow.AssistParameterType_DOC))
case vo.FileTypePPT:
return ptr.Of(int64(workflow.AssistParameterType_PPT))
case vo.FileTypeCode:
return ptr.Of(int64(workflow.AssistParameterType_CODE))
case vo.FileTypeExcel:
return ptr.Of(int64(workflow.AssistParameterType_EXCEL))
case vo.FileTypeZip:
return ptr.Of(int64(workflow.AssistParameterType_ZIP))
case vo.FileTypeVideo:
return ptr.Of(int64(workflow.AssistParameterType_VIDEO))
case vo.FileTypeAudio:
return ptr.Of(int64(workflow.AssistParameterType_AUDIO))
case vo.FileTypeTxt:
return ptr.Of(int64(workflow.AssistParameterType_TXT))
default:
return nil
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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 singleagent
import (
"context"
"strings"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/developer_api"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/playground"
)
func (s *SingleAgentApplicationService) GetUploadAuthToken(ctx context.Context, req *developer_api.GetUploadAuthTokenRequest) (*developer_api.GetUploadAuthTokenResponse, error) {
authToken, err := s.getAuthToken(ctx)
if err != nil {
return nil, err
}
prefix := s.getUploadPrefix(req.Scene, req.DataType)
return &developer_api.GetUploadAuthTokenResponse{
Data: &developer_api.GetUploadAuthTokenData{
ServiceID: authToken.ServiceID,
UploadPathPrefix: prefix,
UploadHost: authToken.UploadHost,
Auth: &developer_api.UploadAuthTokenInfo{
AccessKeyID: authToken.AccessKeyID,
SecretAccessKey: authToken.SecretAccessKey,
SessionToken: authToken.SessionToken,
ExpiredTime: authToken.ExpiredTime,
CurrentTime: authToken.CurrentTime,
},
Schema: authToken.HostScheme,
},
}, nil
}
func (s *SingleAgentApplicationService) getAuthToken(ctx context.Context) (*bot_common.AuthToken, error) {
uploadAuthToken, err := s.appContext.ImageX.GetUploadAuth(ctx)
if err != nil {
return nil, err
}
authToken := &bot_common.AuthToken{
ServiceID: s.appContext.ImageX.GetServerID(),
AccessKeyID: uploadAuthToken.AccessKeyID,
SecretAccessKey: uploadAuthToken.SecretAccessKey,
SessionToken: uploadAuthToken.SessionToken,
ExpiredTime: uploadAuthToken.ExpiredTime,
CurrentTime: uploadAuthToken.CurrentTime,
UploadHost: s.appContext.ImageX.GetUploadHost(ctx),
HostScheme: uploadAuthToken.HostScheme,
}
return authToken, nil
}
func (s *SingleAgentApplicationService) getUploadPrefix(scene, dataType string) string {
return strings.Replace(scene, "_", "-", -1) + "-" + dataType
}
func (s *SingleAgentApplicationService) GetImagexShortUrl(ctx context.Context, req *playground.GetImagexShortUrlRequest) (*playground.GetImagexShortUrlResponse, error) {
urlInfo := make(map[string]*playground.UrlInfo, len(req.Uris))
for _, uri := range req.Uris {
resURL, err := s.appContext.ImageX.GetResourceURL(ctx, uri)
if err != nil {
return nil, err
}
urlInfo[uri] = &playground.UrlInfo{
URL: resURL.URL,
ReviewStatus: true,
}
}
return &playground.GetImagexShortUrlResponse{
Data: &playground.GetImagexShortUrlData{
URLInfo: urlInfo,
},
}, nil
}

View File

@@ -0,0 +1,85 @@
/*
* 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 singleagent
import (
"github.com/cloudwego/eino/compose"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/repository"
singleagent "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/service"
connector "github.com/coze-dev/coze-studio/backend/domain/connector/service"
knowledge "github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
database "github.com/coze-dev/coze-studio/backend/domain/memory/database/service"
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
"github.com/coze-dev/coze-studio/backend/domain/modelmgr"
"github.com/coze-dev/coze-studio/backend/domain/plugin/service"
search "github.com/coze-dev/coze-studio/backend/domain/search/service"
shortcutCmd "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/service"
user "github.com/coze-dev/coze-studio/backend/domain/user/service"
"github.com/coze-dev/coze-studio/backend/domain/workflow"
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
"github.com/coze-dev/coze-studio/backend/infra/impl/chatmodel"
"github.com/coze-dev/coze-studio/backend/pkg/jsoncache"
)
type (
SingleAgent = singleagent.SingleAgent
)
var SingleAgentSVC *SingleAgentApplicationService
type ServiceComponents struct {
IDGen idgen.IDGenerator
DB *gorm.DB
Cache *redis.Client
TosClient storage.Storage
ImageX imagex.ImageX
EventBus search.ProjectEventBus
CounterRepo repository.CounterRepository
KnowledgeDomainSVC knowledge.Knowledge
ModelMgrDomainSVC modelmgr.Manager
PluginDomainSVC service.PluginService
WorkflowDomainSVC workflow.Service
UserDomainSVC user.User
VariablesDomainSVC variables.Variables
ConnectorDomainSVC connector.Connector
DatabaseDomainSVC database.Database
ShortcutCMDDomainSVC shortcutCmd.ShortcutCmd
CPStore compose.CheckPointStore
}
func InitService(c *ServiceComponents) (*SingleAgentApplicationService, error) {
domainComponents := &singleagent.Components{
AgentDraftRepo: repository.NewSingleAgentRepo(c.DB, c.IDGen, c.Cache),
AgentVersionRepo: repository.NewSingleAgentVersionRepo(c.DB, c.IDGen),
PublishInfoRepo: jsoncache.New[entity.PublishInfo]("agent:publish:last:", c.Cache),
CounterRepo: repository.NewCounterRepo(c.Cache),
CPStore: c.CPStore,
ModelFactory: chatmodel.NewDefaultFactory(),
}
singleAgentDomainSVC := singleagent.NewService(domainComponents)
SingleAgentSVC = newApplicationService(c, singleAgentDomainSVC)
return SingleAgentSVC, nil
}

View File

@@ -0,0 +1,260 @@
/*
* 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 singleagent
import (
"context"
"fmt"
"strconv"
"sync"
"time"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
"github.com/coze-dev/coze-studio/backend/api/model/intelligence/common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/developer_api"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/playground"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
search "github.com/coze-dev/coze-studio/backend/domain/search/entity"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/pkg/taskgroup"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func (s *SingleAgentApplicationService) PublishAgent(ctx context.Context, req *developer_api.PublishDraftBotRequest) (*developer_api.PublishDraftBotResponse, error) {
draftAgent, err := s.ValidateAgentDraftAccess(ctx, req.BotID)
if err != nil {
return nil, err
}
version, err := s.getPublishAgentVersion(ctx, req)
if err != nil {
return nil, err
}
connectorIDs := make([]int64, 0, len(req.Connectors))
for v := range req.Connectors {
var id int64
id, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, err
}
if !entity.PublishConnectorIDWhiteList[id] {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", fmt.Sprintf("connector %d not allowed", id)))
}
connectorIDs = append(connectorIDs, id)
}
p := &entity.SingleAgentPublish{
ConnectorIds: connectorIDs,
Version: version,
PublishID: req.GetPublishID(),
PublishInfo: req.HistoryInfo,
}
publishFns := []publishFn{
publishAgentVariables,
publishAgentPlugins,
publishShortcutCommand,
publishDatabase,
}
for _, pubFn := range publishFns {
draftAgent, err = pubFn(ctx, s.appContext, p, draftAgent)
if err != nil {
return nil, err
}
}
err = s.DomainSVC.SavePublishRecord(ctx, p, draftAgent)
if err != nil {
return nil, err
}
tasks := taskgroup.NewUninterruptibleTaskGroup(ctx, len(connectorIDs))
publishResult := make(map[string]*developer_api.ConnectorBindResult, len(connectorIDs))
lock := sync.Mutex{}
for _, connectorID := range connectorIDs {
tasks.Go(func() error {
_, err = s.DomainSVC.CreateSingleAgent(ctx, connectorID, version, draftAgent)
if err != nil {
logs.CtxWarnf(ctx, "create single agent failed: %v, agentID: %d, connectorID: %d , version : %s", err, draftAgent.AgentID, connectorID, version)
lock.Lock()
publishResult[conv.Int64ToStr(connectorID)] = &developer_api.ConnectorBindResult{
PublishResultStatus: ptr.Of(developer_api.PublishResultStatus_Failed),
}
lock.Unlock()
return err
}
// do other connector publish logic if need
lock.Lock()
publishResult[conv.Int64ToStr(connectorID)] = &developer_api.ConnectorBindResult{
PublishResultStatus: ptr.Of(developer_api.PublishResultStatus_Success),
}
lock.Unlock()
return nil
})
}
_ = tasks.Wait()
err = s.appContext.EventBus.PublishProject(ctx, &search.ProjectDomainEvent{
OpType: search.Updated,
Project: &search.ProjectDocument{
ID: draftAgent.AgentID,
HasPublished: ptr.Of(1),
PublishTimeMS: ptr.Of(time.Now().UnixMilli()),
Type: common.IntelligenceType_Bot,
},
})
if err != nil {
logs.CtxWarnf(ctx, "publish project event failed, agentID: %d, err : %v", draftAgent.AgentID, err)
}
return &developer_api.PublishDraftBotResponse{
Data: &developer_api.PublishDraftBotData{
CheckNotPass: false,
PublishResult: publishResult,
},
}, nil
}
func (s *SingleAgentApplicationService) getPublishAgentVersion(ctx context.Context, req *developer_api.PublishDraftBotRequest) (string, error) {
version := req.GetCommitVersion()
if version != "" {
return version, nil
}
v, err := s.appContext.IDGen.GenID(ctx)
if err != nil {
return "", err
}
version = fmt.Sprintf("%v", v)
return version, nil
}
func (s *SingleAgentApplicationService) GetAgentPopupInfo(ctx context.Context, req *playground.GetBotPopupInfoRequest) (*playground.GetBotPopupInfoResponse, error) {
uid := ctxutil.MustGetUIDFromCtx(ctx)
agentPopupCountInfo := make(map[playground.BotPopupType]int64, len(req.BotPopupTypes))
for _, agentPopupType := range req.BotPopupTypes {
count, err := s.DomainSVC.GetAgentPopupCount(ctx, uid, req.GetBotID(), agentPopupType)
if err != nil {
return nil, err
}
agentPopupCountInfo[agentPopupType] = count
}
return &playground.GetBotPopupInfoResponse{
Data: &playground.BotPopupInfoData{
BotPopupCountInfo: agentPopupCountInfo,
},
}, nil
}
func (s *SingleAgentApplicationService) UpdateAgentPopupInfo(ctx context.Context, req *playground.UpdateBotPopupInfoRequest) (*playground.UpdateBotPopupInfoResponse, error) {
uid := ctxutil.MustGetUIDFromCtx(ctx)
err := s.DomainSVC.IncrAgentPopupCount(ctx, uid, req.GetBotID(), req.GetBotPopupType())
if err != nil {
return nil, err
}
return &playground.UpdateBotPopupInfoResponse{
Code: 0,
Msg: "success",
}, nil
}
func (s *SingleAgentApplicationService) GetPublishConnectorList(ctx context.Context, req *developer_api.PublishConnectorListRequest) (*developer_api.PublishConnectorListResponse, error) {
data, err := s.DomainSVC.GetPublishConnectorList(ctx, req.BotID)
if err != nil {
return nil, err
}
return &developer_api.PublishConnectorListResponse{
PublishConnectorList: data.PublishConnectorList,
Code: 0,
Msg: "success",
}, nil
}
type publishFn func(ctx context.Context, appContext *ServiceComponents, publishInfo *entity.SingleAgentPublish, agent *entity.SingleAgent) (*entity.SingleAgent, error)
func publishAgentVariables(ctx context.Context, appContext *ServiceComponents, publishInfo *entity.SingleAgentPublish, agent *entity.SingleAgent) (*entity.SingleAgent, error) {
draftAgent := agent
if draftAgent.VariablesMetaID != nil || *draftAgent.VariablesMetaID == 0 {
return draftAgent, nil
}
var newVariableMetaID int64
newVariableMetaID, err := appContext.VariablesDomainSVC.PublishMeta(ctx, *draftAgent.VariablesMetaID, publishInfo.Version)
if err != nil {
return nil, err
}
draftAgent.VariablesMetaID = ptr.Of(newVariableMetaID)
return draftAgent, nil
}
func publishAgentPlugins(ctx context.Context, appContext *ServiceComponents, publishInfo *entity.SingleAgentPublish, agent *entity.SingleAgent) (*entity.SingleAgent, error) {
err := appContext.PluginDomainSVC.PublishAgentTools(ctx, agent.AgentID, publishInfo.Version)
if err != nil {
return nil, err
}
return agent, nil
}
func publishShortcutCommand(ctx context.Context, appContext *ServiceComponents, publishInfo *entity.SingleAgentPublish, agent *entity.SingleAgent) (*entity.SingleAgent, error) {
logs.CtxInfof(ctx, "publishShortcutCommand agentID: %d, shortcutCommand: %v", agent.AgentID, agent.ShortcutCommand)
if agent.ShortcutCommand == nil || len(agent.ShortcutCommand) == 0 {
return agent, nil
}
cmdIDs := slices.Transform(agent.ShortcutCommand, func(a string) int64 {
return conv.StrToInt64D(a, 0)
})
err := appContext.ShortcutCMDDomainSVC.PublishCMDs(ctx, agent.AgentID, cmdIDs)
if err != nil {
return nil, err
}
return agent, nil
}
func publishDatabase(ctx context.Context, appContext *ServiceComponents, publishInfo *entity.SingleAgentPublish, agent *entity.SingleAgent) (*entity.SingleAgent, error) {
onlineResp, err := appContext.DatabaseDomainSVC.PublishDatabase(ctx, &database.PublishDatabaseRequest{AgentID: agent.AgentID})
if err != nil {
return nil, err
}
agent.Database = onlineResp.OnlineDatabases
return agent, nil
}

View File

@@ -0,0 +1,733 @@
/*
* 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 singleagent
import (
"context"
"fmt"
"strconv"
"time"
shortcutCmd "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/service"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/types/consts"
"github.com/bytedance/sonic"
"github.com/getkin/kin-openapi/openapi3"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
intelligence "github.com/coze-dev/coze-studio/backend/api/model/intelligence/common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_common"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/developer_api"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/playground"
"github.com/coze-dev/coze-studio/backend/api/model/table"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossdatabase"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
singleagent "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/service"
variableEntity "github.com/coze-dev/coze-studio/backend/domain/memory/variables/entity"
shortcutEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
type SingleAgentApplicationService struct {
appContext *ServiceComponents
DomainSVC singleagent.SingleAgent
ShortcutCMDSVC shortcutCmd.ShortcutCmd
}
func newApplicationService(s *ServiceComponents, domain singleagent.SingleAgent) *SingleAgentApplicationService {
return &SingleAgentApplicationService{
appContext: s,
DomainSVC: domain,
ShortcutCMDSVC: s.ShortcutCMDDomainSVC,
}
}
const onboardingInfoMaxLength = 65535
func (s *SingleAgentApplicationService) generateOnboardingStr(onboardingInfo *bot_common.OnboardingInfo) (string, error) {
onboarding := playground.OnboardingContent{}
if onboardingInfo != nil {
onboarding.Prologue = ptr.Of(onboardingInfo.GetPrologue())
onboarding.SuggestedQuestions = onboardingInfo.GetSuggestedQuestions()
onboarding.SuggestedQuestionsShowMode = onboardingInfo.SuggestedQuestionsShowMode
}
onboardingInfoStr, err := sonic.MarshalString(onboarding)
if err != nil {
return "", err
}
return onboardingInfoStr, nil
}
func (s *SingleAgentApplicationService) UpdateSingleAgentDraft(ctx context.Context, req *playground.UpdateDraftBotInfoAgwRequest) (*playground.UpdateDraftBotInfoAgwResponse, error) {
if req.BotInfo.OnboardingInfo != nil {
infoStr, err := s.generateOnboardingStr(req.BotInfo.OnboardingInfo)
if err != nil {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "onboarding_info invalidate"))
}
if len(infoStr) > onboardingInfoMaxLength {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "onboarding_info is too long"))
}
}
agentID := req.BotInfo.GetBotId()
currentAgentInfo, err := s.ValidateAgentDraftAccess(ctx, agentID)
if err != nil {
return nil, err
}
userID := ctxutil.MustGetUIDFromCtx(ctx)
updateAgentInfo, err := s.applyAgentUpdates(currentAgentInfo, req.BotInfo)
if err != nil {
return nil, err
}
if req.BotInfo.VariableList != nil {
var (
varsMetaID int64
vars = variableEntity.NewVariablesWithAgentVariables(req.BotInfo.VariableList)
)
varsMetaID, err = s.appContext.VariablesDomainSVC.UpsertBotMeta(ctx, agentID, "", userID, vars)
if err != nil {
return nil, err
}
updateAgentInfo.VariablesMetaID = &varsMetaID
}
err = s.DomainSVC.UpdateSingleAgentDraft(ctx, updateAgentInfo)
if err != nil {
return nil, err
}
err = s.appContext.EventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Updated,
Project: &searchEntity.ProjectDocument{
ID: agentID,
Name: &updateAgentInfo.Name,
Type: intelligence.IntelligenceType_Bot,
},
})
if err != nil {
return nil, err
}
return &playground.UpdateDraftBotInfoAgwResponse{
Data: &playground.UpdateDraftBotInfoAgwData{
HasChange: ptr.Of(true),
CheckNotPass: false,
Branch: playground.BranchPtr(playground.Branch_PersonalDraft),
},
}, nil
}
func (s *SingleAgentApplicationService) UpdatePromptDisable(ctx context.Context, req *table.UpdateDatabaseBotSwitchRequest) (*table.UpdateDatabaseBotSwitchResponse, error) {
agentID := req.GetBotID()
draft, err := s.ValidateAgentDraftAccess(ctx, agentID)
if err != nil {
return nil, err
}
if len(draft.Database) == 0 {
return nil, fmt.Errorf("agent %d has no database", agentID) // TODO@fanlv: 错误码
}
dbInfos := draft.Database
var found bool
for _, db := range dbInfos {
if db.GetTableId() == conv.Int64ToStr(req.GetDatabaseID()) {
db.PromptDisabled = ptr.Of(req.GetPromptDisable())
found = true
break
}
}
if !found {
return nil, fmt.Errorf("database %d not found in agent %d", req.GetDatabaseID(), agentID) // TODO@fanlv: 错误码
}
draft.Database = dbInfos
err = s.DomainSVC.UpdateSingleAgentDraft(ctx, draft)
if err != nil {
return nil, err
}
return &table.UpdateDatabaseBotSwitchResponse{
Code: 0,
Msg: "success",
}, nil
}
func (s *SingleAgentApplicationService) UnBindDatabase(ctx context.Context, req *table.BindDatabaseToBotRequest) (*table.BindDatabaseToBotResponse, error) {
agentID := req.GetBotID()
draft, err := s.ValidateAgentDraftAccess(ctx, agentID)
if err != nil {
return nil, err
}
if len(draft.Database) == 0 {
return nil, fmt.Errorf("agent %d has no database", agentID)
}
dbInfos := draft.Database
var found bool
newDBInfos := make([]*bot_common.Database, 0)
for _, db := range dbInfos {
if db.GetTableId() == conv.Int64ToStr(req.GetDatabaseID()) {
found = true
continue
}
newDBInfos = append(newDBInfos, db)
}
if !found {
return nil, fmt.Errorf("database %d not found in agent %d", req.GetDatabaseID(), agentID)
}
draft.Database = newDBInfos
err = s.DomainSVC.UpdateSingleAgentDraft(ctx, draft)
if err != nil {
return nil, err
}
err = crossdatabase.DefaultSVC().UnBindDatabase(ctx, &database.UnBindDatabaseToAgentRequest{
AgentID: agentID,
DraftDatabaseID: req.GetDatabaseID(),
})
if err != nil {
return nil, err
}
return &table.BindDatabaseToBotResponse{
Code: 0,
Msg: "success",
}, nil
}
func (s *SingleAgentApplicationService) BindDatabase(ctx context.Context, req *table.BindDatabaseToBotRequest) (*table.BindDatabaseToBotResponse, error) {
agentID := req.GetBotID()
draft, err := s.ValidateAgentDraftAccess(ctx, agentID)
if err != nil {
return nil, err
}
dbMap := slices.ToMap(draft.Database, func(d *bot_common.Database) (string, *bot_common.Database) {
return d.GetTableId(), d
})
if _, ok := dbMap[conv.Int64ToStr(req.GetDatabaseID())]; ok {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KVf("msg", "database %d already bound to agent %d", req.GetDatabaseID(), agentID))
}
basics := []*database.DatabaseBasic{
{
ID: req.DatabaseID,
TableType: table.TableType_DraftTable,
},
}
draftRes, err := crossdatabase.DefaultSVC().MGetDatabase(ctx, &database.MGetDatabaseRequest{
Basics: basics,
})
if err != nil {
return nil, err
}
if len(draftRes.Databases) == 0 {
return nil, fmt.Errorf("database %d not found", req.DatabaseID)
}
draftDatabase := draftRes.Databases[0]
fields := make([]*bot_common.FieldItem, 0, len(draftDatabase.FieldList))
for _, field := range draftDatabase.FieldList {
fields = append(fields, &bot_common.FieldItem{
Name: ptr.Of(field.Name),
Desc: ptr.Of(field.Desc),
Type: ptr.Of(bot_common.FieldItemType(field.Type)),
MustRequired: ptr.Of(field.MustRequired),
AlterId: ptr.Of(field.AlterID),
Id: ptr.Of(int64(0)),
})
}
bindDB := &bot_common.Database{
TableId: ptr.Of(strconv.FormatInt(draftDatabase.ID, 10)),
TableName: ptr.Of(draftDatabase.TableName),
TableDesc: ptr.Of(draftDatabase.TableDesc),
FieldList: fields,
RWMode: ptr.Of(bot_common.BotTableRWMode(draftDatabase.RwMode)),
}
if len(draft.Database) == 0 {
draft.Database = make([]*bot_common.Database, 0, 1)
}
draft.Database = append(draft.Database, bindDB)
err = s.DomainSVC.UpdateSingleAgentDraft(ctx, draft)
if err != nil {
return nil, err
}
err = crossdatabase.DefaultSVC().BindDatabase(ctx, &database.BindDatabaseToAgentRequest{
AgentID: agentID,
DraftDatabaseID: req.GetDatabaseID(),
})
if err != nil {
return nil, err
}
return &table.BindDatabaseToBotResponse{
Code: 0,
Msg: "success",
}, nil
}
func (s *SingleAgentApplicationService) applyAgentUpdates(target *entity.SingleAgent, patch *bot_common.BotInfoForUpdate) (*entity.SingleAgent, error) {
if patch.Name != nil {
target.Name = *patch.Name
}
if patch.Description != nil {
target.Desc = *patch.Description
}
if patch.IconUri != nil {
target.IconURI = *patch.IconUri
}
if patch.OnboardingInfo != nil {
target.OnboardingInfo = patch.OnboardingInfo
}
if patch.ModelInfo != nil {
target.ModelInfo = patch.ModelInfo
}
if patch.PromptInfo != nil {
target.Prompt = patch.PromptInfo
}
if patch.WorkflowInfoList != nil {
target.Workflow = patch.WorkflowInfoList
}
if patch.PluginInfoList != nil {
target.Plugin = patch.PluginInfoList
}
if patch.Knowledge != nil {
target.Knowledge = patch.Knowledge
}
if patch.SuggestReplyInfo != nil {
target.SuggestReply = patch.SuggestReplyInfo
}
if patch.BackgroundImageInfoList != nil {
target.BackgroundImageInfoList = patch.BackgroundImageInfoList
}
if patch.Agents != nil && len(patch.Agents) > 0 && patch.Agents[0].JumpConfig != nil {
target.JumpConfig = patch.Agents[0].JumpConfig
}
if patch.ShortcutSort != nil {
target.ShortcutCommand = patch.ShortcutSort
}
if patch.DatabaseList != nil {
for _, db := range patch.DatabaseList {
if db.PromptDisabled == nil {
db.PromptDisabled = ptr.Of(false) // default is false
}
}
target.Database = patch.DatabaseList
}
return target, nil
}
func (s *SingleAgentApplicationService) DeleteAgentDraft(ctx context.Context, req *developer_api.DeleteDraftBotRequest) (*developer_api.DeleteDraftBotResponse, error) {
_, err := s.ValidateAgentDraftAccess(ctx, req.GetBotID())
if err != nil {
return nil, err
}
err = s.DomainSVC.DeleteAgentDraft(ctx, req.GetSpaceID(), req.GetBotID())
if err != nil {
return nil, err
}
err = s.appContext.EventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Deleted,
Project: &searchEntity.ProjectDocument{
ID: req.GetBotID(),
Type: intelligence.IntelligenceType_Bot,
},
})
if err != nil {
logs.CtxWarnf(ctx, "publish delete project event failed id = %v , err = %v", req.GetBotID(), err)
}
return &developer_api.DeleteDraftBotResponse{
Data: &developer_api.DeleteDraftBotData{},
Code: 0,
}, nil
}
func (s *SingleAgentApplicationService) singleAgentDraftDo2Vo(ctx context.Context, do *entity.SingleAgent) (*bot_common.BotInfo, error) {
vo := &bot_common.BotInfo{
BotId: do.AgentID,
Name: do.Name,
Description: do.Desc,
IconUri: do.IconURI,
OnboardingInfo: do.OnboardingInfo,
ModelInfo: do.ModelInfo,
PromptInfo: do.Prompt,
PluginInfoList: do.Plugin,
Knowledge: do.Knowledge,
WorkflowInfoList: do.Workflow,
SuggestReplyInfo: do.SuggestReply,
CreatorId: do.CreatorID,
TaskInfo: &bot_common.TaskInfo{},
CreateTime: do.CreatedAt / 1000,
UpdateTime: do.UpdatedAt / 1000,
BotMode: bot_common.BotMode_SingleMode,
BackgroundImageInfoList: do.BackgroundImageInfoList,
Status: bot_common.BotStatus_Using,
DatabaseList: do.Database,
ShortcutSort: do.ShortcutCommand,
}
if do.VariablesMetaID != nil {
vars, err := s.appContext.VariablesDomainSVC.GetVariableMetaByID(ctx, *do.VariablesMetaID)
if err != nil {
return nil, err
}
if vars != nil {
vo.VariableList = vars.ToAgentVariables()
}
}
if vo.IconUri != "" {
url, err := s.appContext.TosClient.GetObjectUrl(ctx, vo.IconUri)
if err != nil {
return nil, err
}
vo.IconUrl = url
}
if vo.ModelInfo == nil || vo.ModelInfo.ModelId == nil {
mi, err := s.defaultModelInfo(ctx)
if err != nil {
return nil, err
}
vo.ModelInfo = mi
}
return vo, nil
}
func disabledParam(schemaVal *openapi3.Schema) bool {
if len(schemaVal.Extensions) == 0 {
return false
}
globalDisable, localDisable := false, false
if v, ok := schemaVal.Extensions[plugin.APISchemaExtendLocalDisable]; ok {
localDisable = v.(bool)
}
if v, ok := schemaVal.Extensions[plugin.APISchemaExtendGlobalDisable]; ok {
globalDisable = v.(bool)
}
return globalDisable || localDisable
}
func (s *SingleAgentApplicationService) UpdateAgentDraftDisplayInfo(ctx context.Context, req *developer_api.UpdateDraftBotDisplayInfoRequest) (*developer_api.UpdateDraftBotDisplayInfoResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "session required"))
}
_, err := s.ValidateAgentDraftAccess(ctx, req.BotID)
if err != nil {
return nil, err
}
draftInfoDo := &entity.AgentDraftDisplayInfo{
AgentID: req.BotID,
DisplayInfo: req.DisplayInfo,
SpaceID: req.SpaceID,
}
err = s.DomainSVC.UpdateAgentDraftDisplayInfo(ctx, *uid, draftInfoDo)
if err != nil {
return nil, err
}
return &developer_api.UpdateDraftBotDisplayInfoResponse{
Code: 0,
Msg: "success",
}, nil
}
func (s *SingleAgentApplicationService) GetAgentDraftDisplayInfo(ctx context.Context, req *developer_api.GetDraftBotDisplayInfoRequest) (*developer_api.GetDraftBotDisplayInfoResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "session required"))
}
_, err := s.ValidateAgentDraftAccess(ctx, req.BotID)
if err != nil {
return nil, err
}
draftInfoDo, err := s.DomainSVC.GetAgentDraftDisplayInfo(ctx, *uid, req.BotID)
if err != nil {
return nil, err
}
return &developer_api.GetDraftBotDisplayInfoResponse{
Code: 0,
Msg: "success",
Data: draftInfoDo.DisplayInfo,
}, nil
}
func (s *SingleAgentApplicationService) ValidateAgentDraftAccess(ctx context.Context, agentID int64) (*entity.SingleAgent, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
uid = ptr.Of(int64(888))
// return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "session uid not found"))
}
do, err := s.DomainSVC.GetSingleAgentDraft(ctx, agentID)
if err != nil {
return nil, err
}
if do == nil {
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KVf("msg", "No agent draft(%d) found for the given agent ID", agentID))
}
if do.SpaceID == consts.TemplateSpaceID { // duplicate template, not need check uid permission
return do, nil
}
if do.CreatorID != *uid {
logs.CtxErrorf(ctx, "user(%d) is not the creator(%d) of the agent draft", *uid, do.CreatorID)
return do, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("detail", "you are not the agent owner"))
}
return do, nil
}
func (s *SingleAgentApplicationService) ListAgentPublishHistory(ctx context.Context, req *developer_api.ListDraftBotHistoryRequest) (*developer_api.ListDraftBotHistoryResponse, error) {
resp := &developer_api.ListDraftBotHistoryResponse{}
draftAgent, err := s.ValidateAgentDraftAccess(ctx, req.BotID)
if err != nil {
return nil, err
}
var connectorID *int64
if req.GetConnectorID() != "" {
var id int64
id, err = conv.StrToInt64(req.GetConnectorID())
if err != nil {
return nil, errorx.New(errno.ErrAgentInvalidParamCode, errorx.KV("msg", fmt.Sprintf("ConnectorID %v invalidate", *req.ConnectorID)))
}
connectorID = ptr.Of(id)
}
historyList, err := s.DomainSVC.ListAgentPublishHistory(ctx, draftAgent.AgentID, req.PageIndex, req.PageSize, connectorID)
if err != nil {
return nil, err
}
uid := ctxutil.MustGetUIDFromCtx(ctx)
resp.Data = &developer_api.ListDraftBotHistoryData{}
for _, v := range historyList {
connectorInfos := make([]*developer_api.ConnectorInfo, 0, len(v.ConnectorIds))
infos, err := s.appContext.ConnectorDomainSVC.GetByIDs(ctx, v.ConnectorIds)
if err != nil {
return nil, err
}
for _, info := range infos {
connectorInfos = append(connectorInfos, info.ToVO())
}
creator, err := s.appContext.UserDomainSVC.GetUserProfiles(ctx, v.CreatorID)
if err != nil {
return nil, err
}
info := ""
if v.PublishInfo != nil {
info = *v.PublishInfo
}
historyInfo := &developer_api.HistoryInfo{
HistoryType: developer_api.HistoryType_FLAG,
Version: v.Version,
Info: info,
CreateTime: conv.Int64ToStr(v.CreatedAt / 1000),
ConnectorInfos: connectorInfos,
Creator: &developer_api.Creator{
ID: v.CreatorID,
Name: creator.Name,
AvatarURL: creator.IconURL,
Self: uid == v.CreatorID,
// UserUniqueName: creator.UserUniqueName, // TODO(@fanlv) : user domain 补完以后再改
// UserLabel TODO
},
PublishID: &v.PublishID,
}
resp.Data.HistoryInfos = append(resp.Data.HistoryInfos, historyInfo)
}
return resp, nil
}
func (s *SingleAgentApplicationService) ReportUserBehavior(ctx context.Context, req *playground.ReportUserBehaviorRequest) (resp *playground.ReportUserBehaviorResponse, err error) {
err = s.appContext.EventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Updated,
Project: &searchEntity.ProjectDocument{
ID: req.ResourceID,
SpaceID: req.SpaceID,
Type: intelligence.IntelligenceType_Bot,
IsRecentlyOpen: ptr.Of(1),
RecentlyOpenMS: ptr.Of(time.Now().UnixMilli()),
},
})
if err != nil {
logs.CtxWarnf(ctx, "publish updated project event failed id=%v, err=%v", req.ResourceID, err)
}
return &playground.ReportUserBehaviorResponse{}, nil
}
func (s *SingleAgentApplicationService) GetAgentOnlineInfo(ctx context.Context, req *playground.GetBotOnlineInfoReq) (*bot_common.OpenAPIBotInfo, error) {
uid := ctxutil.MustGetUIDFromApiAuthCtx(ctx)
connectorID, err := conv.StrToInt64(ptr.From(req.ConnectorID))
if err != nil {
return nil, err
}
if connectorID == 0 {
connectorID = ctxutil.GetApiAuthFromCtx(ctx).ConnectorID
}
agentInfo, err := s.DomainSVC.ObtainAgentByIdentity(ctx, &entity.AgentIdentity{
AgentID: req.BotID,
ConnectorID: connectorID,
Version: ptr.From(req.Version),
})
if err != nil {
return nil, err
}
if agentInfo == nil {
logs.CtxErrorf(ctx, "agent(%d) is not exist", req.BotID)
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "agent not exist"))
}
if agentInfo.CreatorID != uid {
return nil, errorx.New(errno.ErrPromptPermissionCode, errorx.KV("msg", "agent not own"))
}
combineInfo := &bot_common.OpenAPIBotInfo{
BotID: agentInfo.AgentID,
Name: agentInfo.Name,
Description: agentInfo.Desc,
IconURL: agentInfo.IconURI,
Version: agentInfo.Version,
BotMode: bot_common.BotMode_SingleMode,
PromptInfo: agentInfo.Prompt,
OnboardingInfo: agentInfo.OnboardingInfo,
ModelInfo: agentInfo.ModelInfo,
WorkflowInfoList: agentInfo.Workflow,
PluginInfoList: agentInfo.Plugin,
}
if agentInfo.IconURI != "" {
url, err := s.appContext.TosClient.GetObjectUrl(ctx, agentInfo.IconURI)
if err != nil {
return nil, err
}
combineInfo.IconURL = url
}
if len(agentInfo.ShortcutCommand) > 0 {
shortcutInfos, err := s.ShortcutCMDSVC.ListCMD(ctx, &shortcutEntity.ListMeta{
ObjectID: agentInfo.AgentID,
IsOnline: 1,
CommandIDs: slices.Transform(agentInfo.ShortcutCommand, func(s string) int64 {
i, _ := conv.StrToInt64(s)
return i
}),
})
if err != nil {
return nil, err
}
combineInfo.ShortcutCommands = make([]*bot_common.ShortcutCommandInfo, 0, len(shortcutInfos))
combineInfo.ShortcutCommands = slices.Transform(shortcutInfos, func(si *shortcutEntity.ShortcutCmd) *bot_common.ShortcutCommandInfo {
url := ""
if si.ShortcutIcon != nil && si.ShortcutIcon.URI != "" {
getUrl, e := s.appContext.TosClient.GetObjectUrl(ctx, si.ShortcutIcon.URI)
if e == nil {
url = getUrl
}
}
return &bot_common.ShortcutCommandInfo{
ID: si.CommandID,
Name: si.CommandName,
Description: si.Description,
IconURL: url,
QueryTemplate: si.TemplateQuery,
AgentID: ptr.Of(si.ObjectID),
Command: si.ShortcutCommand,
Components: slices.Transform(si.Components, func(i *playground.Components) *bot_common.ShortcutCommandComponent {
return &bot_common.ShortcutCommandComponent{
Name: i.Name,
Description: i.Description,
Type: i.InputType.String(),
ToolParameter: ptr.Of(i.Parameter),
Options: i.Options,
DefaultValue: ptr.Of(i.DefaultValue.Value),
IsHide: i.Hide,
}
}),
}
})
}
return combineInfo, nil
}