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,47 @@
/*
* 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 crossagent
import (
"context"
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
)
// Requests and responses must not reference domain entities and can only use models under api/model/crossdomain.
type SingleAgent interface {
StreamExecute(ctx context.Context, historyMsg []*message.Message, query *message.Message,
agentRuntime *singleagent.AgentRuntime) (*schema.StreamReader[*singleagent.AgentEvent], error)
ObtainAgentByIdentity(ctx context.Context, identity *singleagent.AgentIdentity) (*singleagent.SingleAgent, error)
}
type ResumeInfo = singleagent.InterruptInfo
type AgentEvent = singleagent.AgentEvent
var defaultSVC SingleAgent
func DefaultSVC() SingleAgent {
return defaultSVC
}
func SetDefaultSVC(svc SingleAgent) {
defaultSVC = svc
}

View File

@@ -0,0 +1,35 @@
/*
* 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 crossagentrun
import (
"context"
)
type AgentRun interface {
Delete(ctx context.Context, runID []int64) error
}
var defaultSVC AgentRun
func DefaultSVC() AgentRun {
return defaultSVC
}
func SetDefaultSVC(svc AgentRun) {
defaultSVC = svc
}

View File

@@ -0,0 +1,39 @@
/*
* 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 crossconnector
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/connector"
)
type Connector interface {
List(ctx context.Context) ([]*connector.Connector, error)
GetByIDs(ctx context.Context, ids []int64) (map[int64]*connector.Connector, error)
GetByID(ctx context.Context, id int64) (*connector.Connector, error)
}
var defaultSVC Connector
func DefaultSVC() Connector {
return defaultSVC
}
func SetDefaultSVC(c Connector) {
defaultSVC = c
}

View File

@@ -0,0 +1,37 @@
/*
* 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 crossconversation
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/conversation"
)
type Conversation interface {
GetCurrentConversation(ctx context.Context, req *conversation.GetCurrent) (*conversation.Conversation, error)
}
var defaultSVC Conversation
func DefaultSVC() Conversation {
return defaultSVC
}
func SetDefaultSVC(c Conversation) {
defaultSVC = c
}

View File

@@ -0,0 +1,43 @@
/*
* 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 crossdatabase
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
)
type Database interface {
ExecuteSQL(ctx context.Context, req *database.ExecuteSQLRequest) (*database.ExecuteSQLResponse, error)
PublishDatabase(ctx context.Context, req *database.PublishDatabaseRequest) (resp *database.PublishDatabaseResponse, err error)
DeleteDatabase(ctx context.Context, req *database.DeleteDatabaseRequest) error
BindDatabase(ctx context.Context, req *database.BindDatabaseToAgentRequest) error
UnBindDatabase(ctx context.Context, req *database.UnBindDatabaseToAgentRequest) error
MGetDatabase(ctx context.Context, req *database.MGetDatabaseRequest) (*database.MGetDatabaseResponse, error)
GetAllDatabaseByAppID(ctx context.Context, req *database.GetAllDatabaseByAppIDRequest) (*database.GetAllDatabaseByAppIDResponse, error)
}
var defaultSVC Database
func DefaultSVC() Database {
return defaultSVC
}
func SetDefaultSVC(c Database) {
defaultSVC = c
}

View File

@@ -0,0 +1,41 @@
/*
* 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 crossdatacopy
import (
"context"
"gorm.io/gorm"
"github.com/coze-dev/coze-studio/backend/domain/datacopy"
)
type DataCopy interface {
CheckAndGenCopyTask(ctx context.Context, req *datacopy.CheckAndGenCopyTaskReq) (*datacopy.CheckAndGenCopyTaskResp, error)
UpdateCopyTask(ctx context.Context, req *datacopy.UpdateCopyTaskReq) error
UpdateCopyTaskWithTX(ctx context.Context, req *datacopy.UpdateCopyTaskReq, tx *gorm.DB) error
}
var defaultSVC DataCopy
func DefaultSVC() DataCopy {
return defaultSVC
}
func SetDefaultSVC(c DataCopy) {
defaultSVC = c
}

View File

@@ -0,0 +1,40 @@
/*
* 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 crossknowledge
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
)
type Knowledge interface {
ListKnowledge(ctx context.Context, request *knowledge.ListKnowledgeRequest) (response *knowledge.ListKnowledgeResponse, err error)
GetKnowledgeByID(ctx context.Context, request *knowledge.GetKnowledgeByIDRequest) (response *knowledge.GetKnowledgeByIDResponse, err error)
Retrieve(ctx context.Context, req *knowledge.RetrieveRequest) (*knowledge.RetrieveResponse, error)
DeleteKnowledge(ctx context.Context, request *knowledge.DeleteKnowledgeRequest) error
}
var defaultSVC Knowledge
func DefaultSVC() Knowledge {
return defaultSVC
}
func SetDefaultSVC(c Knowledge) {
defaultSVC = c
}

View File

@@ -0,0 +1,41 @@
/*
* 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 crossmessage
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
)
type Message interface {
GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*message.Message, error)
Create(ctx context.Context, msg *message.Message) (*message.Message, error)
Edit(ctx context.Context, msg *message.Message) (*message.Message, error)
}
var defaultSVC Message
type MessageMeta = message.Message
func DefaultSVC() Message {
return defaultSVC
}
func SetDefaultSVC(c Message) {
defaultSVC = c
}

View File

@@ -0,0 +1,39 @@
/*
* 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 crossmodelmgr
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr"
)
type ModelMgr interface {
MGetModelByID(ctx context.Context, req *modelmgr.MGetModelRequest) ([]*modelmgr.Model, error)
}
type Model = modelmgr.Model
var defaultSVC ModelMgr
func DefaultSVC() ModelMgr {
return defaultSVC
}
func SetDefaultSVC(c ModelMgr) {
defaultSVC = c
}

View File

@@ -0,0 +1,48 @@
/*
* 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 crossplugin
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
)
type PluginService interface {
MGetVersionPlugins(ctx context.Context, versionPlugins []model.VersionPlugin) (plugins []*model.PluginInfo, err error)
MGetPluginLatestVersion(ctx context.Context, pluginIDs []int64) (resp *model.MGetPluginLatestVersionResponse, err error)
BindAgentTools(ctx context.Context, agentID int64, toolIDs []int64) (err error)
DuplicateDraftAgentTools(ctx context.Context, fromAgentID, toAgentID int64) (err error)
MGetAgentTools(ctx context.Context, req *model.MGetAgentToolsRequest) (tools []*model.ToolInfo, err error)
ExecuteTool(ctx context.Context, req *model.ExecuteToolRequest, opts ...model.ExecuteToolOpt) (resp *model.ExecuteToolResponse, err error)
PublishAgentTools(ctx context.Context, agentID int64, agentVersion string) (err error)
DeleteDraftPlugin(ctx context.Context, PluginID int64) (err error)
PublishPlugin(ctx context.Context, req *model.PublishPluginRequest) (err error)
PublishAPPPlugins(ctx context.Context, req *model.PublishAPPPluginsRequest) (resp *model.PublishAPPPluginsResponse, err error)
GetAPPAllPlugins(ctx context.Context, appID int64) (plugins []*model.PluginInfo, err error)
MGetVersionTools(ctx context.Context, versionTools []model.VersionTool) (tools []*model.ToolInfo, err error)
}
var defaultSVC PluginService
func DefaultSVC() PluginService {
return defaultSVC
}
func SetDefaultSVC(svc PluginService) {
defaultSVC = svc
}

View File

@@ -0,0 +1,37 @@
/*
* 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 crosssearch
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/search"
)
type Search interface {
SearchResources(ctx context.Context, req *model.SearchResourcesRequest) (resp *model.SearchResourcesResponse, err error)
}
var defaultSVC Search
func DefaultSVC() Search {
return defaultSVC
}
func SetDefaultSVC(svc Search) {
defaultSVC = svc
}

View File

@@ -0,0 +1,40 @@
/*
* 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 crossuser
import (
"context"
"github.com/coze-dev/coze-studio/backend/domain/user/entity"
)
type EntitySpace = entity.Space
//go:generate mockgen -destination ../../../internal/mock/crossdomain/crossuser/crossuser.go --package mockCrossUser -source crossuser.go
type User interface {
GetUserSpaceList(ctx context.Context, userID int64) (spaces []*EntitySpace, err error)
}
var defaultSVC User
func DefaultSVC() User {
return defaultSVC
}
func SetDefaultSVC(u User) {
defaultSVC = u
}

View File

@@ -0,0 +1,40 @@
/*
* 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 crossvariables
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/variables"
"github.com/coze-dev/coze-studio/backend/api/model/kvmemory"
)
type Variables interface {
GetVariableInstance(ctx context.Context, e *variables.UserVariableMeta, keywords []string) ([]*kvmemory.KVItem, error)
SetVariableInstance(ctx context.Context, e *variables.UserVariableMeta, items []*kvmemory.KVItem) ([]string, error)
DecryptSysUUIDKey(ctx context.Context, encryptSysUUIDKey string) *variables.UserVariableMeta
}
var defaultSVC Variables
func DefaultSVC() Variables {
return defaultSVC
}
func SetDefaultSVC(svc Variables) {
defaultSVC = svc
}

View File

@@ -0,0 +1,77 @@
/*
* 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 crossworkflow
import (
"context"
einoCompose "github.com/cloudwego/eino/compose"
"github.com/coze-dev/coze-studio/backend/domain/workflow"
workflowEntity "github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
)
// TODO(@fanlv): 参数引用需要修改。
type Workflow interface {
WorkflowAsModelTool(ctx context.Context, policies []*vo.GetPolicy) ([]workflow.ToolFromWorkflow, error)
DeleteWorkflow(ctx context.Context, id int64) error
PublishWorkflow(ctx context.Context, info *vo.PublishPolicy) (err error)
WithResumeToolWorkflow(resumingEvent *workflowEntity.ToolInterruptEvent, resumeData string,
allInterruptEvents map[string]*workflowEntity.ToolInterruptEvent) einoCompose.Option
ReleaseApplicationWorkflows(ctx context.Context, appID int64, config *ReleaseWorkflowConfig) ([]*vo.ValidateIssue, error)
GetWorkflowIDsByAppID(ctx context.Context, appID int64) ([]int64, error)
SyncExecuteWorkflow(ctx context.Context, config vo.ExecuteConfig, input map[string]any) (*workflowEntity.WorkflowExecution, vo.TerminatePlan, error)
WithExecuteConfig(cfg vo.ExecuteConfig) einoCompose.Option
}
type ExecuteConfig = vo.ExecuteConfig
type ExecuteMode = vo.ExecuteMode
const (
ExecuteModeDebug ExecuteMode = "debug"
ExecuteModeRelease ExecuteMode = "release"
ExecuteModeNodeDebug ExecuteMode = "node_debug"
)
type TaskType = vo.TaskType
const (
TaskTypeForeground TaskType = "foreground"
TaskTypeBackground TaskType = "background"
)
type BizType = vo.BizType
const (
BizTypeAgent BizType = "agent"
BizTypeWorkflow BizType = "workflow"
)
type ReleaseWorkflowConfig = vo.ReleaseWorkflowConfig
type ToolInterruptEvent = workflowEntity.ToolInterruptEvent
var defaultSVC Workflow
func DefaultSVC() Workflow {
return defaultSVC
}
func SetDefaultSVC(svc Workflow) {
defaultSVC = svc
}

View File

@@ -0,0 +1,46 @@
/*
* 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 agentrun
import (
"context"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossagentrun"
agentrun "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/service"
)
type AgentRun interface {
Delete(ctx context.Context, runID []int64) error
}
var defaultSVC crossagentrun.AgentRun
type impl struct {
DomainSVC agentrun.Run
}
func InitDomainService(c agentrun.Run) crossagentrun.AgentRun {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (c *impl) Delete(ctx context.Context, runID []int64) error {
return c.DomainSVC.Delete(ctx, runID)
}

View File

@@ -0,0 +1,80 @@
/*
* 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 connector
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/connector"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossconnector"
connector "github.com/coze-dev/coze-studio/backend/domain/connector/service"
)
var defaultSVC crossconnector.Connector
func InitDomainService(c connector.Connector) crossconnector.Connector {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func DefaultSVC() crossconnector.Connector {
return defaultSVC
}
type impl struct {
DomainSVC connector.Connector
}
func (c *impl) GetByIDs(ctx context.Context, ids []int64) (map[int64]*model.Connector, error) {
res, err := c.DomainSVC.GetByIDs(ctx, ids)
if err != nil {
return nil, err
}
ret := make(map[int64]*model.Connector, len(res))
for _, v := range res {
ret[v.ID] = v.Connector
}
return ret, nil
}
func (c *impl) List(ctx context.Context) ([]*model.Connector, error) {
res, err := c.DomainSVC.List(ctx)
if err != nil {
return nil, err
}
ret := make([]*model.Connector, 0, len(res))
for _, v := range res {
ret = append(ret, v.Connector)
}
return ret, nil
}
func (c *impl) GetByID(ctx context.Context, id int64) (*model.Connector, error) {
info, err := c.DomainSVC.GetByID(ctx, id)
if err != nil {
return nil, err
}
return info.Connector, nil
}

View File

@@ -0,0 +1,42 @@
/*
* 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"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/conversation"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossconversation"
conversation "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/service"
)
var defaultSVC crossconversation.Conversation
type impl struct {
DomainSVC conversation.Conversation
}
func InitDomainService(c conversation.Conversation) crossconversation.Conversation {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (s *impl) GetCurrentConversation(ctx context.Context, req *model.GetCurrent) (*model.Conversation, error) {
return s.DomainSVC.GetCurrentConversation(ctx, req)
}

View File

@@ -0,0 +1,42 @@
/*
* 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 crossuser
import (
"context"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossuser"
"github.com/coze-dev/coze-studio/backend/domain/user/entity"
"github.com/coze-dev/coze-studio/backend/domain/user/service"
)
var defaultSVC crossuser.User
type impl struct {
DomainSVC service.User
}
func InitDomainService(u service.User) crossuser.User {
defaultSVC = &impl{
DomainSVC: u,
}
return defaultSVC
}
func (u *impl) GetUserSpaceList(ctx context.Context, userID int64) (spaces []*entity.Space, err error) {
return u.DomainSVC.GetUserSpaceList(ctx, userID)
}

View File

@@ -0,0 +1,67 @@
/*
* 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 database
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossdatabase"
database "github.com/coze-dev/coze-studio/backend/domain/memory/database/service"
)
var defaultSVC crossdatabase.Database
type databaseImpl struct {
DomainSVC database.Database
}
func InitDomainService(c database.Database) crossdatabase.Database {
defaultSVC = &databaseImpl{
DomainSVC: c,
}
return defaultSVC
}
func (c *databaseImpl) ExecuteSQL(ctx context.Context, req *model.ExecuteSQLRequest) (*model.ExecuteSQLResponse, error) {
return c.DomainSVC.ExecuteSQL(ctx, req)
}
func (c *databaseImpl) PublishDatabase(ctx context.Context, req *model.PublishDatabaseRequest) (resp *model.PublishDatabaseResponse, err error) {
return c.DomainSVC.PublishDatabase(ctx, req)
}
func (c *databaseImpl) DeleteDatabase(ctx context.Context, req *model.DeleteDatabaseRequest) error {
return c.DomainSVC.DeleteDatabase(ctx, req)
}
func (c *databaseImpl) BindDatabase(ctx context.Context, req *model.BindDatabaseToAgentRequest) error {
return c.DomainSVC.BindDatabase(ctx, req)
}
func (c *databaseImpl) UnBindDatabase(ctx context.Context, req *model.UnBindDatabaseToAgentRequest) error {
return c.DomainSVC.UnBindDatabase(ctx, req)
}
func (c *databaseImpl) MGetDatabase(ctx context.Context, req *model.MGetDatabaseRequest) (*model.MGetDatabaseResponse, error) {
return c.DomainSVC.MGetDatabase(ctx, req)
}
func (c *databaseImpl) GetAllDatabaseByAppID(ctx context.Context, req *model.GetAllDatabaseByAppIDRequest) (*model.GetAllDatabaseByAppIDResponse, error) {
return c.DomainSVC.GetAllDatabaseByAppID(ctx, req)
}

View File

@@ -0,0 +1,53 @@
/*
* 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 datacopy
import (
"context"
"gorm.io/gorm"
"github.com/coze-dev/coze-studio/backend/application/base/appinfra"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossdatacopy"
"github.com/coze-dev/coze-studio/backend/domain/datacopy"
"github.com/coze-dev/coze-studio/backend/domain/datacopy/service"
)
var defaultSVC crossdatacopy.DataCopy
type impl struct {
DomainSVC datacopy.DataCopy
}
func InitDomainService(a *appinfra.AppDependencies) crossdatacopy.DataCopy {
svc := service.NewDataCopySVC(&service.DataCopySVCConfig{
DB: a.DB,
IDGen: a.IDGenSVC,
})
return svc
}
func (i *impl) CheckAndGenCopyTask(ctx context.Context, req *datacopy.CheckAndGenCopyTaskReq) (*datacopy.CheckAndGenCopyTaskResp, error) {
return i.DomainSVC.CheckAndGenCopyTask(ctx, req)
}
func (i *impl) UpdateCopyTask(ctx context.Context, req *datacopy.UpdateCopyTaskReq) error {
return i.DomainSVC.UpdateCopyTask(ctx, req)
}
func (i *impl) UpdateCopyTaskWithTX(ctx context.Context, req *datacopy.UpdateCopyTaskReq, tx *gorm.DB) error {
return i.DomainSVC.UpdateCopyTaskWithTX(ctx, req, tx)
}

View File

@@ -0,0 +1,59 @@
/*
* 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 knowledge
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossknowledge"
"github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
)
var defaultSVC crossknowledge.Knowledge
type impl struct {
DomainSVC service.Knowledge
}
func InitDomainService(c service.Knowledge) crossknowledge.Knowledge {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (i *impl) ListKnowledge(ctx context.Context, request *model.ListKnowledgeRequest) (response *model.ListKnowledgeResponse, err error) {
return i.DomainSVC.ListKnowledge(ctx, request)
}
func (i *impl) Retrieve(ctx context.Context, req *model.RetrieveRequest) (*model.RetrieveResponse, error) {
return i.DomainSVC.Retrieve(ctx, req)
}
func (i *impl) DeleteKnowledge(ctx context.Context, req *model.DeleteKnowledgeRequest) error {
return i.DomainSVC.DeleteKnowledge(ctx, req)
}
func (i *impl) GetKnowledgeByID(ctx context.Context, request *model.GetKnowledgeByIDRequest) (response *model.GetKnowledgeByIDResponse, err error) {
return i.DomainSVC.GetKnowledgeByID(ctx, request)
}
func (i *impl) MGetKnowledgeByID(ctx context.Context, request *model.MGetKnowledgeByIDRequest) (response *model.MGetKnowledgeByIDResponse, err error) {
return i.DomainSVC.MGetKnowledgeByID(ctx, request)
}

View File

@@ -0,0 +1,51 @@
/*
* 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 message
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossmessage"
message "github.com/coze-dev/coze-studio/backend/domain/conversation/message/service"
)
var defaultSVC crossmessage.Message
type impl struct {
DomainSVC message.Message
}
func InitDomainService(c message.Message) crossmessage.Message {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (c *impl) GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*model.Message, error) {
return c.DomainSVC.GetByRunIDs(ctx, conversationID, runIDs)
}
func (c *impl) Create(ctx context.Context, msg *model.Message) (*model.Message, error) {
return c.DomainSVC.Create(ctx, msg)
}
func (c *impl) Edit(ctx context.Context, msg *model.Message) (*model.Message, error) {
return c.DomainSVC.Edit(ctx, msg)
}

View File

@@ -0,0 +1,52 @@
/*
* 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 modelmgr
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossmodelmgr"
"github.com/coze-dev/coze-studio/backend/domain/modelmgr"
)
var defaultSVC crossmodelmgr.ModelMgr
type impl struct {
DomainSVC modelmgr.Manager
}
func InitDomainService(c modelmgr.Manager) crossmodelmgr.ModelMgr {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (s *impl) MGetModelByID(ctx context.Context, req *modelmgr.MGetModelRequest) ([]*model.Model, error) {
res, err := s.DomainSVC.MGetModelByID(ctx, req)
if err != nil {
return nil, err
}
ret := make([]*model.Model, 0, len(res))
for _, v := range res {
ret = append(ret, v.Model)
}
return ret, nil
}

View File

@@ -0,0 +1,107 @@
/*
* 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 plugin
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossplugin"
"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
plugin "github.com/coze-dev/coze-studio/backend/domain/plugin/service"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
)
var defaultSVC crossplugin.PluginService
type impl struct {
DomainSVC plugin.PluginService
}
func InitDomainService(c plugin.PluginService) crossplugin.PluginService {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (s *impl) MGetVersionPlugins(ctx context.Context, versionPlugins []model.VersionPlugin) (mPlugins []*model.PluginInfo, err error) {
plugins, err := s.DomainSVC.MGetVersionPlugins(ctx, versionPlugins)
if err != nil {
return nil, err
}
mPlugins = slices.Transform(plugins, func(e *entity.PluginInfo) *model.PluginInfo {
return e.PluginInfo
})
return mPlugins, nil
}
func (s *impl) BindAgentTools(ctx context.Context, agentID int64, toolIDs []int64) (err error) {
return s.DomainSVC.BindAgentTools(ctx, agentID, toolIDs)
}
func (s *impl) DuplicateDraftAgentTools(ctx context.Context, fromAgentID, toAgentID int64) (err error) {
return s.DomainSVC.DuplicateDraftAgentTools(ctx, fromAgentID, toAgentID)
}
func (s *impl) MGetAgentTools(ctx context.Context, req *model.MGetAgentToolsRequest) (tools []*model.ToolInfo, err error) {
return s.DomainSVC.MGetAgentTools(ctx, req)
}
func (s *impl) ExecuteTool(ctx context.Context, req *model.ExecuteToolRequest, opts ...model.ExecuteToolOpt) (resp *model.ExecuteToolResponse, err error) {
return s.DomainSVC.ExecuteTool(ctx, req, opts...)
}
func (s *impl) PublishAgentTools(ctx context.Context, agentID int64, agentVersion string) (err error) {
return s.DomainSVC.PublishAgentTools(ctx, agentID, agentVersion)
}
func (s *impl) DeleteDraftPlugin(ctx context.Context, pluginID int64) (err error) {
return s.DomainSVC.DeleteDraftPlugin(ctx, pluginID)
}
func (s *impl) PublishPlugin(ctx context.Context, req *model.PublishPluginRequest) (err error) {
return s.DomainSVC.PublishPlugin(ctx, req)
}
func (s *impl) PublishAPPPlugins(ctx context.Context, req *model.PublishAPPPluginsRequest) (resp *model.PublishAPPPluginsResponse, err error) {
return s.DomainSVC.PublishAPPPlugins(ctx, req)
}
func (s *impl) MGetPluginLatestVersion(ctx context.Context, pluginIDs []int64) (resp *model.MGetPluginLatestVersionResponse, err error) {
return s.DomainSVC.MGetPluginLatestVersion(ctx, pluginIDs)
}
func (s *impl) MGetVersionTools(ctx context.Context, versionTools []model.VersionTool) (tools []*model.ToolInfo, err error) {
return s.DomainSVC.MGetVersionTools(ctx, versionTools)
}
func (s *impl) GetAPPAllPlugins(ctx context.Context, appID int64) (plugins []*model.PluginInfo, err error) {
_plugins, err := s.DomainSVC.GetAPPAllPlugins(ctx, appID)
if err != nil {
return nil, err
}
plugins = slices.Transform(_plugins, func(e *entity.PluginInfo) *model.PluginInfo {
return e.PluginInfo
})
return plugins, nil
}

View File

@@ -0,0 +1,43 @@
/*
* 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 search
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/search"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crosssearch"
"github.com/coze-dev/coze-studio/backend/domain/search/service"
)
var defaultSVC crosssearch.Search
type impl struct {
DomainSVC crosssearch.Search
}
func (i impl) SearchResources(ctx context.Context, req *model.SearchResourcesRequest) (resp *model.SearchResourcesResponse, err error) {
return i.DomainSVC.SearchResources(ctx, req)
}
func InitDomainService(c service.Search) crosssearch.Search {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}

View File

@@ -0,0 +1,191 @@
/*
* 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 agent
import (
"context"
"encoding/json"
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossagent"
singleagent "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/service"
"github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
)
var defaultSVC crossagent.SingleAgent
type impl struct {
DomainSVC singleagent.SingleAgent
}
func InitDomainService(c singleagent.SingleAgent) crossagent.SingleAgent {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (c *impl) StreamExecute(ctx context.Context, historyMsg []*message.Message,
query *message.Message, agentRuntime *model.AgentRuntime,
) (*schema.StreamReader[*model.AgentEvent], error) {
historyMsg = c.historyPairs(historyMsg)
singleAgentStreamExecReq := c.buildSingleAgentStreamExecuteReq(ctx, historyMsg, query, agentRuntime)
streamEvent, err := c.DomainSVC.StreamExecute(ctx, singleAgentStreamExecReq)
logs.CtxInfof(ctx, "agent StreamExecute req:%v, streamEvent:%v, err:%v", conv.DebugJsonToStr(singleAgentStreamExecReq), streamEvent, err)
return streamEvent, err
}
func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, historyMsg []*message.Message,
input *message.Message, agentRuntime *model.AgentRuntime,
) *model.ExecuteRequest {
identity := c.buildIdentity(input, agentRuntime)
inputBuild := c.buildSchemaMessage([]*message.Message{input})
history := c.buildSchemaMessage(historyMsg)
resumeInfo := c.checkResumeInfo(ctx, historyMsg)
return &model.ExecuteRequest{
Identity: identity,
Input: inputBuild[0],
History: history,
UserID: input.UserID,
PreCallTools: slices.Transform(agentRuntime.PreRetrieveTools, func(tool *agentrun.Tool) *agentrun.ToolsRetriever {
return &agentrun.ToolsRetriever{
PluginID: tool.PluginID,
ToolName: tool.ToolName,
ToolID: tool.ToolID,
Arguments: tool.Arguments,
Type: func(toolType agentrun.ToolType) agentrun.ToolType {
switch toolType {
case agentrun.ToolTypeWorkflow:
return agentrun.ToolTypeWorkflow
case agentrun.ToolTypePlugin:
return agentrun.ToolTypePlugin
}
return agentrun.ToolTypePlugin
}(tool.Type),
}
}),
ResumeInfo: resumeInfo,
}
}
func (c *impl) historyPairs(historyMsg []*message.Message) []*message.Message {
fcMsgPairs := make(map[int64][]*message.Message)
for _, one := range historyMsg {
if one.MessageType != message.MessageTypeFunctionCall && one.MessageType != message.MessageTypeToolResponse {
continue
}
if _, ok := fcMsgPairs[one.RunID]; !ok {
fcMsgPairs[one.RunID] = []*message.Message{one}
} else {
fcMsgPairs[one.RunID] = append(fcMsgPairs[one.RunID], one)
}
}
var historyAfterPairs []*message.Message
for _, value := range historyMsg {
if value.MessageType == message.MessageTypeFunctionCall {
if len(fcMsgPairs[value.RunID])%2 == 0 {
historyAfterPairs = append(historyAfterPairs, value)
}
} else {
historyAfterPairs = append(historyAfterPairs, value)
}
}
return historyAfterPairs
}
func (c *impl) checkResumeInfo(_ context.Context, historyMsg []*message.Message) *crossagent.ResumeInfo {
var resumeInfo *crossagent.ResumeInfo
for i := len(historyMsg) - 1; i >= 0; i-- {
if historyMsg[i].MessageType == message.MessageTypeQuestion {
break
}
if historyMsg[i].MessageType == message.MessageTypeVerbose {
if historyMsg[i].Ext[string(entity.ExtKeyResumeInfo)] != "" {
err := json.Unmarshal([]byte(historyMsg[i].Ext[string(entity.ExtKeyResumeInfo)]), &resumeInfo)
if err != nil {
return nil
}
}
}
}
return resumeInfo
}
func (c *impl) buildSchemaMessage(msgs []*message.Message) []*schema.Message {
schemaMessage := make([]*schema.Message, 0, len(msgs))
for _, msgOne := range msgs {
if msgOne.ModelContent == "" {
continue
}
if msgOne.MessageType == message.MessageTypeVerbose || msgOne.MessageType == message.MessageTypeFlowUp {
continue
}
var sm *schema.Message
err := json.Unmarshal([]byte(msgOne.ModelContent), &sm)
if err != nil {
continue
}
schemaMessage = append(schemaMessage, sm)
}
return schemaMessage
}
func (c *impl) buildIdentity(input *message.Message, agentRuntime *model.AgentRuntime) *model.AgentIdentity {
return &model.AgentIdentity{
AgentID: input.AgentID,
Version: agentRuntime.AgentVersion,
IsDraft: agentRuntime.IsDraft,
ConnectorID: agentRuntime.ConnectorID,
}
}
func (c *impl) GetSingleAgent(ctx context.Context, agentID int64, version string) (agent *model.SingleAgent, err error) {
agentInfo, err := c.DomainSVC.GetSingleAgent(ctx, agentID, version)
if err != nil {
return nil, err
}
return agentInfo.SingleAgent, nil
}
func (c *impl) ObtainAgentByIdentity(ctx context.Context, identity *model.AgentIdentity) (*model.SingleAgent, error) {
agentInfo, err := c.DomainSVC.ObtainAgentByIdentity(ctx, identity)
if err != nil {
return nil, err
}
return agentInfo.SingleAgent, nil
}

View File

@@ -0,0 +1,66 @@
/*
* 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 variables
import (
"context"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/variables"
"github.com/coze-dev/coze-studio/backend/api/model/kvmemory"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossvariables"
"github.com/coze-dev/coze-studio/backend/domain/memory/variables/entity"
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
)
var defaultSVC crossvariables.Variables
type impl struct {
DomainSVC variables.Variables
}
func InitDomainService(c variables.Variables) crossvariables.Variables {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (s *impl) GetVariableInstance(ctx context.Context, e *model.UserVariableMeta, keywords []string) ([]*kvmemory.KVItem, error) {
m := entity.NewUserVariableMeta(e)
return s.DomainSVC.GetVariableInstance(ctx, m, keywords)
}
func (s *impl) SetVariableInstance(ctx context.Context, e *model.UserVariableMeta, items []*kvmemory.KVItem) ([]string, error) {
m := entity.NewUserVariableMeta(e)
return s.DomainSVC.SetVariableInstance(ctx, m, items)
}
func (s *impl) DecryptSysUUIDKey(ctx context.Context, encryptSysUUIDKey string) *model.UserVariableMeta {
m := s.DomainSVC.DecryptSysUUIDKey(ctx, encryptSysUUIDKey)
if m == nil {
return nil
}
return &model.UserVariableMeta{
BizType: m.BizType,
BizID: m.BizID,
Version: m.Version,
ConnectorUID: m.ConnectorUID,
ConnectorID: m.ConnectorID,
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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 workflow
import (
"context"
einoCompose "github.com/cloudwego/eino/compose"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossworkflow"
"github.com/coze-dev/coze-studio/backend/domain/workflow"
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/lang/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
)
var defaultSVC crossworkflow.Workflow
type impl struct {
DomainSVC workflow.Service
}
func InitDomainService(c workflow.Service) crossworkflow.Workflow {
defaultSVC = &impl{
DomainSVC: c,
}
return defaultSVC
}
func (i *impl) WorkflowAsModelTool(ctx context.Context, policies []*vo.GetPolicy) ([]workflow.ToolFromWorkflow, error) {
return i.DomainSVC.WorkflowAsModelTool(ctx, policies)
}
func (i *impl) PublishWorkflow(ctx context.Context, info *vo.PublishPolicy) (err error) {
return i.DomainSVC.Publish(ctx, info)
}
func (i *impl) DeleteWorkflow(ctx context.Context, id int64) error {
return i.DomainSVC.Delete(ctx, &vo.DeletePolicy{
ID: ptr.Of(id),
})
}
func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, config *vo.ReleaseWorkflowConfig) ([]*vo.ValidateIssue, error) {
return i.DomainSVC.ReleaseApplicationWorkflows(ctx, appID, config)
}
func (i *impl) WithResumeToolWorkflow(resumingEvent *workflowEntity.ToolInterruptEvent, resumeData string, allInterruptEvents map[string]*workflowEntity.ToolInterruptEvent) einoCompose.Option {
return i.DomainSVC.WithResumeToolWorkflow(resumingEvent, resumeData, allInterruptEvents)
}
func (i *impl) SyncExecuteWorkflow(ctx context.Context, config vo.ExecuteConfig, input map[string]any) (*workflowEntity.WorkflowExecution, vo.TerminatePlan, error) {
return i.DomainSVC.SyncExecute(ctx, config, input)
}
func (i *impl) WithExecuteConfig(cfg vo.ExecuteConfig) einoCompose.Option {
return i.DomainSVC.WithExecuteConfig(cfg)
}
func (i *impl) GetWorkflowIDsByAppID(ctx context.Context, appID int64) ([]int64, error) {
metas, _, err := i.DomainSVC.MGet(ctx, &vo.MGetPolicy{
MetaQuery: vo.MetaQuery{
AppID: ptr.Of(appID),
},
MetaOnly: true,
})
if err != nil {
return nil, err
}
return slices.Transform(metas, func(a *workflowEntity.Workflow) int64 {
return a.ID
}), err
}

View File

@@ -0,0 +1,442 @@
/*
* 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 database
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/spf13/cast"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
"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/domain/memory/database/service"
nodedatabase "github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/database"
"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/ternary"
)
type DatabaseRepository struct {
client service.Database
}
func NewDatabaseRepository(client service.Database) *DatabaseRepository {
return &DatabaseRepository{
client: client,
}
}
func (d *DatabaseRepository) Execute(ctx context.Context, request *nodedatabase.CustomSQLRequest) (*nodedatabase.Response, error) {
var (
err error
databaseInfoID = request.DatabaseInfoID
tableType = ternary.IFElse[table.TableType](request.IsDebugRun, table.TableType_DraftTable, table.TableType_OnlineTable)
)
if request.IsDebugRun {
databaseInfoID, err = d.getDraftTableID(ctx, databaseInfoID)
if err != nil {
return nil, err
}
}
req := &service.ExecuteSQLRequest{
DatabaseID: databaseInfoID,
OperateType: database.OperateType_Custom,
SQL: &request.SQL,
TableType: tableType,
UserID: strconv.FormatInt(request.UserID, 10),
}
req.SQLParams = make([]*database.SQLParamVal, 0, len(request.Params))
for i := range request.Params {
param := request.Params[i]
req.SQLParams = append(req.SQLParams, &database.SQLParamVal{
ValueType: table.FieldItemType_Text,
Value: &param.Value,
ISNull: param.IsNull,
})
}
response, err := d.client.ExecuteSQL(ctx, req)
if err != nil {
return nil, err
}
// if rows affected is nil use 0 instead
if response.RowsAffected == nil {
response.RowsAffected = ptr.Of(int64(0))
}
return toNodeDateBaseResponse(response), nil
}
func (d *DatabaseRepository) Delete(ctx context.Context, request *nodedatabase.DeleteRequest) (*nodedatabase.Response, error) {
var (
err error
databaseInfoID = request.DatabaseInfoID
tableType = ternary.IFElse[table.TableType](request.IsDebugRun, table.TableType_DraftTable, table.TableType_OnlineTable)
)
if request.IsDebugRun {
databaseInfoID, err = d.getDraftTableID(ctx, databaseInfoID)
if err != nil {
return nil, err
}
}
req := &service.ExecuteSQLRequest{
DatabaseID: databaseInfoID,
OperateType: database.OperateType_Delete,
TableType: tableType,
UserID: strconv.FormatInt(request.UserID, 10),
}
if request.ConditionGroup != nil {
req.Condition, req.SQLParams, err = buildComplexCondition(request.ConditionGroup)
if err != nil {
return nil, err
}
}
response, err := d.client.ExecuteSQL(ctx, req)
if err != nil {
return nil, err
}
return toNodeDateBaseResponse(response), nil
}
func (d *DatabaseRepository) Query(ctx context.Context, request *nodedatabase.QueryRequest) (*nodedatabase.Response, error) {
var (
err error
databaseInfoID = request.DatabaseInfoID
tableType = ternary.IFElse[table.TableType](request.IsDebugRun, table.TableType_DraftTable, table.TableType_OnlineTable)
)
if request.IsDebugRun {
databaseInfoID, err = d.getDraftTableID(ctx, databaseInfoID)
if err != nil {
return nil, err
}
}
req := &service.ExecuteSQLRequest{
DatabaseID: databaseInfoID,
OperateType: database.OperateType_Select,
TableType: tableType,
UserID: strconv.FormatInt(request.UserID, 10),
}
req.SelectFieldList = &database.SelectFieldList{FieldID: make([]string, 0, len(request.SelectFields))}
for i := range request.SelectFields {
req.SelectFieldList.FieldID = append(req.SelectFieldList.FieldID, request.SelectFields[i])
}
req.OrderByList = make([]database.OrderBy, 0)
for i := range request.OrderClauses {
clause := request.OrderClauses[i]
req.OrderByList = append(req.OrderByList, database.OrderBy{
Field: clause.FieldID,
Direction: toOrderDirection(clause.IsAsc),
})
}
if request.ConditionGroup != nil {
req.Condition, req.SQLParams, err = buildComplexCondition(request.ConditionGroup)
if err != nil {
return nil, err
}
}
limit := request.Limit
req.Limit = &limit
response, err := d.client.ExecuteSQL(ctx, req)
if err != nil {
return nil, err
}
return toNodeDateBaseResponse(response), nil
}
func (d *DatabaseRepository) Update(ctx context.Context, request *nodedatabase.UpdateRequest) (*nodedatabase.Response, error) {
var (
err error
condition *database.ComplexCondition
params []*database.SQLParamVal
databaseInfoID = request.DatabaseInfoID
tableType = ternary.IFElse[table.TableType](request.IsDebugRun, table.TableType_DraftTable, table.TableType_OnlineTable)
)
if request.IsDebugRun {
databaseInfoID, err = d.getDraftTableID(ctx, databaseInfoID)
if err != nil {
return nil, err
}
}
req := &service.ExecuteSQLRequest{
DatabaseID: databaseInfoID,
OperateType: database.OperateType_Update,
SQLParams: make([]*database.SQLParamVal, 0),
TableType: tableType,
}
uid := ctxutil.GetUIDFromCtx(ctx)
if uid != nil {
req.UserID = conv.Int64ToStr(*uid)
}
req.UpsertRows, req.SQLParams, err = resolveUpsertRow(request.Fields)
if err != nil {
return nil, err
}
if request.ConditionGroup != nil {
condition, params, err = buildComplexCondition(request.ConditionGroup)
if err != nil {
return nil, err
}
req.Condition = condition
req.SQLParams = append(req.SQLParams, params...)
}
response, err := d.client.ExecuteSQL(ctx, req)
if err != nil {
return nil, err
}
return toNodeDateBaseResponse(response), nil
}
func (d *DatabaseRepository) Insert(ctx context.Context, request *nodedatabase.InsertRequest) (*nodedatabase.Response, error) {
var (
err error
databaseInfoID = request.DatabaseInfoID
tableType = ternary.IFElse[table.TableType](request.IsDebugRun, table.TableType_DraftTable, table.TableType_OnlineTable)
)
if request.IsDebugRun {
databaseInfoID, err = d.getDraftTableID(ctx, databaseInfoID)
if err != nil {
return nil, err
}
}
req := &service.ExecuteSQLRequest{
DatabaseID: databaseInfoID,
OperateType: database.OperateType_Insert,
TableType: tableType,
UserID: strconv.FormatInt(request.UserID, 10),
}
req.UpsertRows, req.SQLParams, err = resolveUpsertRow(request.Fields)
if err != nil {
return nil, err
}
response, err := d.client.ExecuteSQL(ctx, req)
if err != nil {
return nil, err
}
return toNodeDateBaseResponse(response), nil
}
func (d *DatabaseRepository) getDraftTableID(ctx context.Context, onlineID int64) (int64, error) {
resp, err := d.client.GetDraftDatabaseByOnlineID(ctx, &service.GetDraftDatabaseByOnlineIDRequest{OnlineID: onlineID})
if err != nil {
return 0, err
}
return resp.Database.ID, nil
}
func buildComplexCondition(conditionGroup *nodedatabase.ConditionGroup) (*database.ComplexCondition, []*database.SQLParamVal, error) {
condition := &database.ComplexCondition{}
logic, err := toLogic(conditionGroup.Relation)
if err != nil {
return nil, nil, err
}
condition.Logic = logic
params := make([]*database.SQLParamVal, 0)
for i := range conditionGroup.Conditions {
var (
nCond = conditionGroup.Conditions[i]
vals []*database.SQLParamVal
dCond = &database.Condition{
Left: nCond.Left,
}
)
opt, err := toOperation(nCond.Operator)
if err != nil {
return nil, nil, err
}
dCond.Operation = opt
if isNullOrNotNull(opt) {
condition.Conditions = append(condition.Conditions, dCond)
continue
}
dCond.Right, vals, err = resolveRightValue(opt, nCond.Right)
if err != nil {
return nil, nil, err
}
condition.Conditions = append(condition.Conditions, dCond)
params = append(params, vals...)
}
return condition, params, nil
}
func toMapStringAny(m map[string]string) map[string]any {
ret := make(map[string]any, len(m))
for k, v := range m {
ret[k] = v
}
return ret
}
func toOperation(operator nodedatabase.Operator) (database.Operation, error) {
switch operator {
case nodedatabase.OperatorEqual:
return database.Operation_EQUAL, nil
case nodedatabase.OperatorNotEqual:
return database.Operation_NOT_EQUAL, nil
case nodedatabase.OperatorGreater:
return database.Operation_GREATER_THAN, nil
case nodedatabase.OperatorGreaterOrEqual:
return database.Operation_GREATER_EQUAL, nil
case nodedatabase.OperatorLesser:
return database.Operation_LESS_THAN, nil
case nodedatabase.OperatorLesserOrEqual:
return database.Operation_LESS_EQUAL, nil
case nodedatabase.OperatorIn:
return database.Operation_IN, nil
case nodedatabase.OperatorNotIn:
return database.Operation_NOT_IN, nil
case nodedatabase.OperatorIsNotNull:
return database.Operation_IS_NOT_NULL, nil
case nodedatabase.OperatorIsNull:
return database.Operation_IS_NULL, nil
case nodedatabase.OperatorLike:
return database.Operation_LIKE, nil
case nodedatabase.OperatorNotLike:
return database.Operation_NOT_LIKE, nil
default:
return database.Operation(0), fmt.Errorf("invalid operator %v", operator)
}
}
func resolveRightValue(operator database.Operation, right any) (string, []*database.SQLParamVal, error) {
if isInOrNotIn(operator) {
var (
vals = make([]*database.SQLParamVal, 0)
anyVals = make([]any, 0)
commas = make([]string, 0, len(anyVals))
)
anyVals = right.([]any)
for i := range anyVals {
v := cast.ToString(anyVals[i])
vals = append(vals, &database.SQLParamVal{ValueType: table.FieldItemType_Text, Value: &v})
commas = append(commas, "?")
}
value := "(" + strings.Join(commas, ",") + ")"
return value, vals, nil
}
rightValue, err := cast.ToStringE(right)
if err != nil {
return "", nil, err
}
if isLikeOrNotLike(operator) {
var (
value = "?"
v = "%s" + rightValue + "%s"
)
return value, []*database.SQLParamVal{{ValueType: table.FieldItemType_Text, Value: &v}}, nil
}
return "?", []*database.SQLParamVal{{ValueType: table.FieldItemType_Text, Value: &rightValue}}, nil
}
func resolveUpsertRow(fields map[string]any) ([]*database.UpsertRow, []*database.SQLParamVal, error) {
upsertRow := &database.UpsertRow{Records: make([]*database.Record, 0, len(fields))}
params := make([]*database.SQLParamVal, 0)
for key, value := range fields {
val, err := cast.ToStringE(value)
if err != nil {
return nil, nil, err
}
record := &database.Record{
FieldId: key,
FieldValue: "?",
}
upsertRow.Records = append(upsertRow.Records, record)
params = append(params, &database.SQLParamVal{
ValueType: table.FieldItemType_Text,
Value: &val,
})
}
return []*database.UpsertRow{upsertRow}, params, nil
}
func isNullOrNotNull(opt database.Operation) bool {
return opt == database.Operation_IS_NOT_NULL || opt == database.Operation_IS_NULL
}
func isLikeOrNotLike(opt database.Operation) bool {
return opt == database.Operation_LIKE || opt == database.Operation_NOT_LIKE
}
func isInOrNotIn(opt database.Operation) bool {
return opt == database.Operation_IN || opt == database.Operation_NOT_IN
}
func toOrderDirection(isAsc bool) table.SortDirection {
if isAsc {
return table.SortDirection_ASC
}
return table.SortDirection_Desc
}
func toLogic(relation nodedatabase.ClauseRelation) (database.Logic, error) {
switch relation {
case nodedatabase.ClauseRelationOR:
return database.Logic_Or, nil
case nodedatabase.ClauseRelationAND:
return database.Logic_And, nil
default:
return database.Logic(0), fmt.Errorf("invalid relation %v", relation)
}
}
func toNodeDateBaseResponse(response *service.ExecuteSQLResponse) *nodedatabase.Response {
objects := make([]nodedatabase.Object, 0, len(response.Records))
for i := range response.Records {
objects = append(objects, response.Records[i])
}
return &nodedatabase.Response{
Objects: objects,
RowNumber: response.RowsAffected,
}
}

View File

@@ -0,0 +1,224 @@
/*
* 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 database
import (
"context"
"testing"
"github.com/spf13/cast"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
"github.com/coze-dev/coze-studio/backend/domain/memory/database/service"
nodedatabase "github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/database"
mockDatabase "github.com/coze-dev/coze-studio/backend/internal/mock/domain/memory/database"
)
func mockExecuteSQL(t *testing.T) func(ctx context.Context, request *service.ExecuteSQLRequest) (*service.ExecuteSQLResponse, error) {
return func(ctx context.Context, request *service.ExecuteSQLRequest) (*service.ExecuteSQLResponse, error) {
if request.OperateType == database.OperateType_Custom {
assert.Equal(t, *request.SQL, "select * from table where v1=? and v2=?")
rs := make([]string, 0)
for idx := range request.SQLParams {
rs = append(rs, *request.SQLParams[idx].Value)
}
assert.Equal(t, rs, []string{"1", "2"})
return &service.ExecuteSQLResponse{
Records: []map[string]any{
{"v1": "1", "v2": "2"},
},
}, nil
}
if request.OperateType == database.OperateType_Select {
sFields := []string{"v1", "v2", "v3", "v4"}
assert.Equal(t, request.SelectFieldList.FieldID, sFields)
cond := request.Condition.Conditions[1] // in
assert.Equal(t, "(?,?)", cond.Right)
assert.Equal(t, database.Operation_IN, cond.Operation)
assert.Equal(t, "v2_1", *request.SQLParams[1].Value)
assert.Equal(t, "v2_2", *request.SQLParams[2].Value)
assert.Equal(t, "%sv4%s", *request.SQLParams[3].Value)
rowsAffected := int64(10)
return &service.ExecuteSQLResponse{
Records: []map[string]any{
{"v1": "1", "v2": "2", "v3": "3", "v4": "4"},
},
RowsAffected: &rowsAffected,
}, nil
}
if request.OperateType == database.OperateType_Delete {
cond := request.Condition.Conditions[1] // in
assert.Equal(t, "(?,?)", cond.Right)
assert.Equal(t, database.Operation_NOT_IN, cond.Operation)
assert.Equal(t, "v2_1", *request.SQLParams[1].Value)
assert.Equal(t, "v2_2", *request.SQLParams[2].Value)
assert.Equal(t, "%sv4%s", *request.SQLParams[3].Value)
rowsAffected := int64(10)
return &service.ExecuteSQLResponse{
Records: []map[string]any{
{"v1": "1", "v2": "2", "v3": "3", "v4": "4"},
},
RowsAffected: &rowsAffected,
}, nil
}
if request.OperateType == database.OperateType_Insert {
records := request.UpsertRows[0].Records
ret := map[string]interface{}{
"v1": "1",
"v2": 2,
"v3": 33,
"v4": "44aacc",
}
for idx := range records {
assert.Equal(t, *request.SQLParams[idx].Value, cast.ToString(ret[records[idx].FieldId]))
}
}
if request.OperateType == database.OperateType_Update {
records := request.UpsertRows[0].Records
ret := map[string]interface{}{
"v1": "1",
"v2": 2,
"v3": 33,
"v4": "aabbcc",
}
for idx := range records {
assert.Equal(t, *request.SQLParams[idx].Value, cast.ToString(ret[records[idx].FieldId]))
}
request.SQLParams = request.SQLParams[len(records):]
cond := request.Condition.Conditions[1] // in
assert.Equal(t, "(?,?)", cond.Right)
assert.Equal(t, database.Operation_IN, cond.Operation)
assert.Equal(t, "v2_1", *request.SQLParams[1].Value)
assert.Equal(t, "v2_2", *request.SQLParams[2].Value)
assert.Equal(t, "%sv4%s", *request.SQLParams[3].Value)
}
return &service.ExecuteSQLResponse{}, nil
}
}
func TestDatabase_Database(t *testing.T) {
ctrl := gomock.NewController(t)
mockClient := mockDatabase.NewMockDatabase(ctrl)
defer ctrl.Finish()
ds := DatabaseRepository{
client: mockClient,
}
mockClient.EXPECT().ExecuteSQL(gomock.Any(), gomock.Any()).DoAndReturn(mockExecuteSQL(t)).AnyTimes()
t.Run("execute", func(t *testing.T) {
response, err := ds.Execute(context.Background(), &nodedatabase.CustomSQLRequest{
DatabaseInfoID: 1,
SQL: "select * from table where v1=? and v2=?",
Params: []nodedatabase.SQLParam{
nodedatabase.SQLParam{Value: "1"},
nodedatabase.SQLParam{Value: "2"},
},
})
assert.Nil(t, err)
assert.Equal(t, response.Objects, []nodedatabase.Object{
{"v1": "1", "v2": "2"},
})
})
t.Run("select", func(t *testing.T) {
req := &nodedatabase.QueryRequest{
DatabaseInfoID: 1,
SelectFields: []string{"v1", "v2", "v3", "v4"},
Limit: 10,
OrderClauses: []*nodedatabase.OrderClause{
{FieldID: "v1", IsAsc: true},
{FieldID: "v2", IsAsc: false},
},
ConditionGroup: &nodedatabase.ConditionGroup{
Conditions: []*nodedatabase.Condition{
{Left: "v1", Operator: nodedatabase.OperatorEqual, Right: "1"},
{Left: "v2", Operator: nodedatabase.OperatorIn, Right: []any{"v2_1", "v2_2"}},
{Left: "v3", Operator: nodedatabase.OperatorIsNull},
{Left: "v4", Operator: nodedatabase.OperatorLike, Right: "v4"},
},
Relation: nodedatabase.ClauseRelationOR,
},
}
response, err := ds.Query(context.Background(), req)
assert.Nil(t, err)
assert.Equal(t, *response.RowNumber, int64(10))
})
t.Run("delete", func(t *testing.T) {
req := &nodedatabase.DeleteRequest{
DatabaseInfoID: 1,
ConditionGroup: &nodedatabase.ConditionGroup{
Conditions: []*nodedatabase.Condition{
{Left: "v1", Operator: nodedatabase.OperatorEqual, Right: "1"},
{Left: "v2", Operator: nodedatabase.OperatorNotIn, Right: []any{"v2_1", "v2_2"}},
{Left: "v3", Operator: nodedatabase.OperatorIsNotNull},
{Left: "v4", Operator: nodedatabase.OperatorNotLike, Right: "v4"},
},
Relation: nodedatabase.ClauseRelationOR,
},
}
response, err := ds.Delete(context.Background(), req)
assert.Nil(t, err)
assert.Equal(t, *response.RowNumber, int64(10))
})
t.Run("insert", func(t *testing.T) {
req := &nodedatabase.InsertRequest{
DatabaseInfoID: 1,
Fields: map[string]interface{}{
"v1": "1",
"v2": 2,
"v3": 33,
"v4": "44aacc",
},
}
_, err := ds.Insert(context.Background(), req)
assert.Nil(t, err)
})
t.Run("update", func(t *testing.T) {
req := &nodedatabase.UpdateRequest{
DatabaseInfoID: 1,
ConditionGroup: &nodedatabase.ConditionGroup{
Conditions: []*nodedatabase.Condition{
{Left: "v1", Operator: nodedatabase.OperatorEqual, Right: "1"},
{Left: "v2", Operator: nodedatabase.OperatorIn, Right: []any{"v2_1", "v2_2"}},
{Left: "v3", Operator: nodedatabase.OperatorIsNull},
{Left: "v4", Operator: nodedatabase.OperatorLike, Right: "v4"},
},
Relation: nodedatabase.ClauseRelationOR,
},
Fields: map[string]interface{}{
"v1": "1",
"v2": 2,
"v3": 33,
"v4": "aabbcc",
},
}
_, err := ds.Update(context.Background(), req)
assert.Nil(t, err)
})
}

View File

@@ -0,0 +1,217 @@
/*
* 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 knowledge
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/domain/knowledge/entity"
domainknowledge "github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
crossknowledge "github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/infra/contract/document/parser"
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
)
type Knowledge struct {
client domainknowledge.Knowledge
idGen idgen.IDGenerator
}
func NewKnowledgeRepository(client domainknowledge.Knowledge, idGen idgen.IDGenerator) *Knowledge {
return &Knowledge{
client: client,
idGen: idGen,
}
}
func (k *Knowledge) Store(ctx context.Context, document *crossknowledge.CreateDocumentRequest) (*crossknowledge.CreateDocumentResponse, error) {
var (
ps *entity.ParsingStrategy
cs = &entity.ChunkingStrategy{}
)
if document.ParsingStrategy == nil {
return nil, errors.New("document parsing strategy is required")
}
if document.ChunkingStrategy == nil {
return nil, errors.New("document chunking strategy is required")
}
if document.ParsingStrategy.ParseMode == crossknowledge.AccurateParseMode {
ps = &entity.ParsingStrategy{}
ps.ExtractImage = document.ParsingStrategy.ExtractImage
ps.ExtractTable = document.ParsingStrategy.ExtractTable
ps.ImageOCR = document.ParsingStrategy.ImageOCR
}
chunkType, err := toChunkType(document.ChunkingStrategy.ChunkType)
if err != nil {
return nil, err
}
cs.ChunkType = chunkType
cs.Separator = document.ChunkingStrategy.Separator
cs.ChunkSize = document.ChunkingStrategy.ChunkSize
cs.Overlap = document.ChunkingStrategy.Overlap
req := &entity.Document{
Info: knowledge.Info{
Name: document.FileName,
},
KnowledgeID: document.KnowledgeID,
Type: knowledge.DocumentTypeText,
URL: document.FileURL,
Source: entity.DocumentSourceLocal,
ParsingStrategy: ps,
ChunkingStrategy: cs,
FileExtension: document.FileExtension,
}
uid := ctxutil.GetUIDFromCtx(ctx)
if uid != nil {
req.Info.CreatorID = *uid
}
response, err := k.client.CreateDocument(ctx, &domainknowledge.CreateDocumentRequest{
Documents: []*entity.Document{req},
})
if err != nil {
return nil, err
}
kCResponse := &crossknowledge.CreateDocumentResponse{
FileURL: document.FileURL,
DocumentID: response.Documents[0].Info.ID,
FileName: response.Documents[0].Info.Name,
}
return kCResponse, nil
}
func (k *Knowledge) Retrieve(ctx context.Context, r *crossknowledge.RetrieveRequest) (*crossknowledge.RetrieveResponse, error) {
rs := &entity.RetrievalStrategy{}
if r.RetrievalStrategy != nil {
rs.TopK = r.RetrievalStrategy.TopK
rs.MinScore = r.RetrievalStrategy.MinScore
searchType, err := toSearchType(r.RetrievalStrategy.SearchType)
if err != nil {
return nil, err
}
rs.SearchType = searchType
rs.EnableQueryRewrite = r.RetrievalStrategy.EnableQueryRewrite
rs.EnableRerank = r.RetrievalStrategy.EnableRerank
rs.EnableNL2SQL = r.RetrievalStrategy.EnableNL2SQL
}
req := &domainknowledge.RetrieveRequest{
Query: r.Query,
KnowledgeIDs: r.KnowledgeIDs,
Strategy: rs,
}
response, err := k.client.Retrieve(ctx, req)
if err != nil {
return nil, err
}
ss := make([]*crossknowledge.Slice, 0, len(response.RetrieveSlices))
for _, s := range response.RetrieveSlices {
if s.Slice == nil {
continue
}
ss = append(ss, &crossknowledge.Slice{
DocumentID: strconv.FormatInt(s.Slice.DocumentID, 10),
Output: s.Slice.GetSliceContent(),
})
}
return &crossknowledge.RetrieveResponse{
Slices: ss,
}, nil
}
func (k *Knowledge) Delete(ctx context.Context, r *crossknowledge.DeleteDocumentRequest) (*crossknowledge.DeleteDocumentResponse, error) {
docID, err := strconv.ParseInt(r.DocumentID, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid document id: %s", r.DocumentID)
}
err = k.client.DeleteDocument(ctx, &domainknowledge.DeleteDocumentRequest{
DocumentID: docID,
})
if err != nil {
return &crossknowledge.DeleteDocumentResponse{IsSuccess: false}, err
}
return &crossknowledge.DeleteDocumentResponse{IsSuccess: true}, nil
}
func (k *Knowledge) ListKnowledgeDetail(ctx context.Context, req *crossknowledge.ListKnowledgeDetailRequest) (*crossknowledge.ListKnowledgeDetailResponse, error) {
response, err := k.client.MGetKnowledgeByID(ctx, &domainknowledge.MGetKnowledgeByIDRequest{
KnowledgeIDs: req.KnowledgeIDs,
})
if err != nil {
return nil, err
}
resp := &crossknowledge.ListKnowledgeDetailResponse{
KnowledgeDetails: slices.Transform(response.Knowledge, func(a *knowledge.Knowledge) *crossknowledge.KnowledgeDetail {
return &crossknowledge.KnowledgeDetail{
ID: a.ID,
Name: a.Name,
Description: a.Description,
IconURL: a.IconURL,
FormatType: int64(a.Type),
}
}),
}
return resp, nil
}
func toSearchType(typ crossknowledge.SearchType) (knowledge.SearchType, error) {
switch typ {
case crossknowledge.SearchTypeSemantic:
return knowledge.SearchTypeSemantic, nil
case crossknowledge.SearchTypeFullText:
return knowledge.SearchTypeFullText, nil
case crossknowledge.SearchTypeHybrid:
return knowledge.SearchTypeHybrid, nil
default:
return 0, fmt.Errorf("unknown search type: %v", typ)
}
}
func toChunkType(typ crossknowledge.ChunkType) (parser.ChunkType, error) {
switch typ {
case crossknowledge.ChunkTypeDefault:
return parser.ChunkTypeDefault, nil
case crossknowledge.ChunkTypeCustom:
return parser.ChunkTypeCustom, nil
case crossknowledge.ChunkTypeLeveled:
return parser.ChunkTypeLeveled, nil
default:
return 0, fmt.Errorf("unknown chunk type: %v", typ)
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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 model
import (
"context"
"fmt"
model2 "github.com/cloudwego/eino/components/model"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossmodelmgr"
"github.com/coze-dev/coze-studio/backend/domain/modelmgr"
"github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/model"
"github.com/coze-dev/coze-studio/backend/infra/contract/chatmodel"
chatmodel2 "github.com/coze-dev/coze-studio/backend/infra/impl/chatmodel"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
)
type ModelManager struct {
modelMgr modelmgr.Manager
factory chatmodel.Factory
}
func NewModelManager(m modelmgr.Manager, f chatmodel.Factory) *ModelManager {
if f == nil {
f = chatmodel2.NewDefaultFactory()
}
return &ModelManager{
modelMgr: m,
factory: f,
}
}
func (m *ModelManager) GetModel(ctx context.Context, params *model.LLMParams) (model2.BaseChatModel, *crossmodelmgr.Model, error) {
modelID := params.ModelType
models, err := crossmodelmgr.DefaultSVC().MGetModelByID(ctx, &modelmgr.MGetModelRequest{
IDs: []int64{modelID},
})
if err != nil {
return nil, nil, err
}
var config *chatmodel.Config
var protocol chatmodel.Protocol
var mdl *crossmodelmgr.Model
for i := range models {
md := models[i]
if md.ID == modelID {
protocol = md.Meta.Protocol
config = md.Meta.ConnConfig
mdl = md
break
}
}
if config == nil {
return nil, nil, fmt.Errorf("model type %v ,not found config ", modelID)
}
if len(protocol) == 0 {
return nil, nil, fmt.Errorf("model type %v ,not found protocol ", modelID)
}
if params.TopP != nil {
config.TopP = ptr.Of(float32(ptr.From(params.TopP)))
}
if params.TopK != nil {
config.TopK = params.TopK
}
if params.Temperature != nil {
config.Temperature = ptr.Of(float32(ptr.From(params.Temperature)))
}
config.MaxTokens = ptr.Of(params.MaxTokens)
// Whether you need to use a pointer
config.FrequencyPenalty = ptr.Of(float32(params.FrequencyPenalty))
config.PresencePenalty = ptr.Of(float32(params.PresencePenalty))
cm, err := m.factory.CreateChatModel(ctx, protocol, config)
if err != nil {
return nil, nil, err
}
return cm, mdl, nil
}

View File

@@ -0,0 +1,510 @@
/*
* 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 plugin
import (
"context"
"fmt"
"strconv"
"github.com/getkin/kin-openapi/openapi3"
"golang.org/x/exp/maps"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
workflow3 "github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/workflow"
common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop_common"
"github.com/coze-dev/coze-studio/backend/application/base/pluginutil"
"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
"github.com/coze-dev/coze-studio/backend/domain/plugin/service"
"github.com/coze-dev/coze-studio/backend/domain/workflow"
crossplugin "github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/plugin"
entity2 "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/infra/contract/storage"
"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/sonic"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
type pluginService struct {
client service.PluginService
tos storage.Storage
}
func NewPluginService(client service.PluginService, tos storage.Storage) crossplugin.Service {
return &pluginService{client: client, tos: tos}
}
type pluginInfo struct {
*entity.PluginInfo
LatestVersion *string
}
func (t *pluginService) getPluginsWithTools(ctx context.Context, pluginEntity *crossplugin.Entity, toolIDs []int64, isDraft bool) (
_ *pluginInfo, toolsInfo []*entity.ToolInfo, err error) {
defer func() {
if err != nil {
err = vo.WrapIfNeeded(errno.ErrPluginAPIErr, err)
}
}()
var pluginsInfo []*entity.PluginInfo
var latestPluginInfo *entity.PluginInfo
pluginID := pluginEntity.PluginID
if isDraft {
plugins, err := t.client.MGetDraftPlugins(ctx, []int64{pluginID})
if err != nil {
return nil, nil, err
}
pluginsInfo = plugins
} else if pluginEntity.PluginVersion == nil || (pluginEntity.PluginVersion != nil && *pluginEntity.PluginVersion == "") {
plugins, err := t.client.MGetOnlinePlugins(ctx, []int64{pluginID})
if err != nil {
return nil, nil, err
}
pluginsInfo = plugins
} else {
plugins, err := t.client.MGetVersionPlugins(ctx, []entity.VersionPlugin{
{PluginID: pluginID, Version: *pluginEntity.PluginVersion},
})
if err != nil {
return nil, nil, err
}
pluginsInfo = plugins
onlinePlugins, err := t.client.MGetOnlinePlugins(ctx, []int64{pluginID})
if err != nil {
return nil, nil, err
}
for _, pi := range onlinePlugins {
if pi.ID == pluginID {
latestPluginInfo = pi
break
}
}
}
var pInfo *entity.PluginInfo
for _, p := range pluginsInfo {
if p.ID == pluginID {
pInfo = p
break
}
}
if pInfo == nil {
return nil, nil, vo.NewError(errno.ErrPluginIDNotFound, errorx.KV("id", strconv.FormatInt(pluginID, 10)))
}
if isDraft {
tools, err := t.client.MGetDraftTools(ctx, toolIDs)
if err != nil {
return nil, nil, err
}
toolsInfo = tools
} else if pluginEntity.PluginVersion == nil || (pluginEntity.PluginVersion != nil && *pluginEntity.PluginVersion == "") {
tools, err := t.client.MGetOnlineTools(ctx, toolIDs)
if err != nil {
return nil, nil, err
}
toolsInfo = tools
} else {
eVersionTools := slices.Transform(toolIDs, func(tid int64) entity.VersionTool {
return entity.VersionTool{
ToolID: tid,
Version: *pluginEntity.PluginVersion,
}
})
tools, err := t.client.MGetVersionTools(ctx, eVersionTools)
if err != nil {
return nil, nil, err
}
toolsInfo = tools
}
if latestPluginInfo != nil {
return &pluginInfo{PluginInfo: pInfo, LatestVersion: latestPluginInfo.Version}, toolsInfo, nil
}
return &pluginInfo{PluginInfo: pInfo}, toolsInfo, nil
}
func (t *pluginService) GetPluginToolsInfo(ctx context.Context, req *crossplugin.ToolsInfoRequest) (
_ *crossplugin.ToolsInfoResponse, err error) {
defer func() {
if err != nil {
err = vo.WrapIfNeeded(errno.ErrPluginAPIErr, err)
}
}()
var toolsInfo []*entity.ToolInfo
isDraft := req.IsDraft || (req.PluginEntity.PluginVersion != nil && *req.PluginEntity.PluginVersion == "0")
pInfo, toolsInfo, err := t.getPluginsWithTools(ctx, &crossplugin.Entity{PluginID: req.PluginEntity.PluginID, PluginVersion: req.PluginEntity.PluginVersion}, req.ToolIDs, isDraft)
if err != nil {
return nil, err
}
url, err := t.tos.GetObjectUrl(ctx, pInfo.GetIconURI())
if err != nil {
return nil, vo.WrapIfNeeded(errno.ErrTOSError, err)
}
response := &crossplugin.ToolsInfoResponse{
PluginID: pInfo.ID,
SpaceID: pInfo.SpaceID,
Version: pInfo.GetVersion(),
PluginName: pInfo.GetName(),
Description: pInfo.GetDesc(),
IconURL: url,
PluginType: int64(pInfo.PluginType),
ToolInfoList: make(map[int64]crossplugin.ToolInfo),
LatestVersion: pInfo.LatestVersion,
IsOfficial: pInfo.IsOfficial(),
AppID: pInfo.GetAPPID(),
}
for _, tf := range toolsInfo {
inputs, err := tf.ToReqAPIParameter()
if err != nil {
return nil, err
}
outputs, err := tf.ToRespAPIParameter()
if err != nil {
return nil, err
}
toolExample := pInfo.GetToolExample(ctx, tf.GetName())
var (
requestExample string
responseExample string
)
if toolExample != nil {
requestExample = toolExample.RequestExample
responseExample = toolExample.RequestExample
}
response.ToolInfoList[tf.ID] = crossplugin.ToolInfo{
ToolID: tf.ID,
ToolName: tf.GetName(),
Inputs: slices.Transform(inputs, toWorkflowAPIParameter),
Outputs: slices.Transform(outputs, toWorkflowAPIParameter),
Description: tf.GetDesc(),
DebugExample: &crossplugin.DebugExample{
ReqExample: requestExample,
RespExample: responseExample,
},
}
}
return response, nil
}
func (t *pluginService) GetPluginInvokableTools(ctx context.Context, req *crossplugin.ToolsInvokableRequest) (
_ map[int64]crossplugin.InvokableTool, err error) {
defer func() {
if err != nil {
err = vo.WrapIfNeeded(errno.ErrPluginAPIErr, err)
}
}()
var toolsInfo []*entity.ToolInfo
isDraft := req.IsDraft || (req.PluginEntity.PluginVersion != nil && *req.PluginEntity.PluginVersion == "0")
pInfo, toolsInfo, err := t.getPluginsWithTools(ctx, &crossplugin.Entity{
PluginID: req.PluginEntity.PluginID,
PluginVersion: req.PluginEntity.PluginVersion,
}, maps.Keys(req.ToolsInvokableInfo), isDraft)
if err != nil {
return nil, err
}
result := map[int64]crossplugin.InvokableTool{}
for _, tf := range toolsInfo {
tl := &pluginInvokeTool{
pluginEntity: crossplugin.Entity{
PluginID: pInfo.ID,
PluginVersion: pInfo.Version,
},
client: t.client,
toolInfo: tf,
IsDraft: isDraft,
}
if r, ok := req.ToolsInvokableInfo[tf.ID]; ok && (r.RequestAPIParametersConfig != nil && r.ResponseAPIParametersConfig != nil) {
reqPluginCommonAPIParameters := slices.Transform(r.RequestAPIParametersConfig, toPluginCommonAPIParameter)
respPluginCommonAPIParameters := slices.Transform(r.ResponseAPIParametersConfig, toPluginCommonAPIParameter)
tl.toolOperation, err = pluginutil.APIParamsToOpenapiOperation(reqPluginCommonAPIParameters, respPluginCommonAPIParameters)
if err != nil {
return nil, err
}
tl.toolOperation.OperationID = tf.Operation.OperationID
tl.toolOperation.Summary = tf.Operation.Summary
}
result[tf.ID] = tl
}
return result, nil
}
func (t *pluginService) ExecutePlugin(ctx context.Context, input map[string]any, pe *crossplugin.Entity,
toolID int64, cfg crossplugin.ExecConfig) (map[string]any, error) {
args, err := sonic.MarshalString(input)
if err != nil {
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
}
req := &service.ExecuteToolRequest{
UserID: conv.Int64ToStr(cfg.Operator),
PluginID: pe.PluginID,
ToolID: toolID,
ExecScene: plugin.ExecSceneOfWorkflow,
ArgumentsInJson: args,
ExecDraftTool: pe.PluginVersion == nil || *pe.PluginVersion == "0",
}
execOpts := []entity.ExecuteToolOpt{
plugin.WithInvalidRespProcessStrategy(plugin.InvalidResponseProcessStrategyOfReturnDefault),
}
if pe.PluginVersion != nil {
execOpts = append(execOpts, plugin.WithToolVersion(*pe.PluginVersion))
}
r, err := t.client.ExecuteTool(ctx, req, execOpts...)
if err != nil {
if extra, ok := compose.IsInterruptRerunError(err); ok {
pluginTIE, ok := extra.(*plugin.ToolInterruptEvent)
if !ok {
return nil, vo.WrapError(errno.ErrPluginAPIErr, fmt.Errorf("expects ToolInterruptEvent, got %T", extra))
}
var eventType workflow3.EventType
switch pluginTIE.Event {
case plugin.InterruptEventTypeOfToolNeedOAuth:
eventType = workflow3.EventType_WorkflowOauthPlugin
default:
return nil, vo.WrapError(errno.ErrPluginAPIErr,
fmt.Errorf("unsupported interrupt event type: %s", pluginTIE.Event))
}
id, err := workflow.GetRepository().GenID(ctx)
if err != nil {
return nil, vo.WrapError(errno.ErrIDGenError, err)
}
ie := &entity2.InterruptEvent{
ID: id,
InterruptData: pluginTIE.ToolNeedOAuth.Message,
EventType: eventType,
}
// temporarily replace interrupt with real error, until frontend can handle plugin oauth interrupt
interruptData := ie.InterruptData
return nil, vo.NewError(errno.ErrAuthorizationRequired, errorx.KV("extra", interruptData))
}
return nil, err
}
var output map[string]any
err = sonic.UnmarshalString(r.RawResp, &output)
if err != nil {
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
}
return output, nil
}
type pluginInvokeTool struct {
pluginEntity crossplugin.Entity
client service.PluginService
toolInfo *entity.ToolInfo
toolOperation *openapi3.Operation
IsDraft bool
}
func (p *pluginInvokeTool) Info(ctx context.Context) (_ *schema.ToolInfo, err error) {
defer func() {
if err != nil {
err = vo.WrapIfNeeded(errno.ErrPluginAPIErr, err)
}
}()
var parameterInfo map[string]*schema.ParameterInfo
if p.toolOperation != nil {
parameterInfo, err = plugin.NewOpenapi3Operation(p.toolOperation).ToEinoSchemaParameterInfo(ctx)
} else {
parameterInfo, err = p.toolInfo.Operation.ToEinoSchemaParameterInfo(ctx)
}
if err != nil {
return nil, err
}
return &schema.ToolInfo{
Name: p.toolInfo.GetName(),
Desc: p.toolInfo.GetDesc(),
ParamsOneOf: schema.NewParamsOneOfByParams(parameterInfo),
}, nil
}
func (p *pluginInvokeTool) PluginInvoke(ctx context.Context, argumentsInJSON string, cfg crossplugin.ExecConfig) (string, error) {
req := &service.ExecuteToolRequest{
UserID: conv.Int64ToStr(cfg.Operator),
PluginID: p.pluginEntity.PluginID,
ToolID: p.toolInfo.ID,
ExecScene: plugin.ExecSceneOfWorkflow,
ArgumentsInJson: argumentsInJSON,
ExecDraftTool: p.IsDraft,
}
execOpts := []entity.ExecuteToolOpt{
plugin.WithInvalidRespProcessStrategy(plugin.InvalidResponseProcessStrategyOfReturnDefault),
}
if p.pluginEntity.PluginVersion != nil {
execOpts = append(execOpts, plugin.WithToolVersion(*p.pluginEntity.PluginVersion))
}
if p.toolOperation != nil {
execOpts = append(execOpts, plugin.WithOpenapiOperation(plugin.NewOpenapi3Operation(p.toolOperation)))
}
r, err := p.client.ExecuteTool(ctx, req, execOpts...)
if err != nil {
if extra, ok := compose.IsInterruptRerunError(err); ok {
pluginTIE, ok := extra.(*plugin.ToolInterruptEvent)
if !ok {
return "", vo.WrapError(errno.ErrPluginAPIErr, fmt.Errorf("expects ToolInterruptEvent, got %T", extra))
}
var eventType workflow3.EventType
switch pluginTIE.Event {
case plugin.InterruptEventTypeOfToolNeedOAuth:
eventType = workflow3.EventType_WorkflowOauthPlugin
default:
return "", vo.WrapError(errno.ErrPluginAPIErr,
fmt.Errorf("unsupported interrupt event type: %s", pluginTIE.Event))
}
id, err := workflow.GetRepository().GenID(ctx)
if err != nil {
return "", vo.WrapError(errno.ErrIDGenError, err)
}
ie := &entity2.InterruptEvent{
ID: id,
InterruptData: pluginTIE.ToolNeedOAuth.Message,
EventType: eventType,
}
tie := &entity2.ToolInterruptEvent{
ToolCallID: compose.GetToolCallID(ctx),
ToolName: p.toolInfo.GetName(),
InterruptEvent: ie,
}
// temporarily replace interrupt with real error, until frontend can handle plugin oauth interrupt
_ = tie
interruptData := ie.InterruptData
return "", vo.NewError(errno.ErrAuthorizationRequired, errorx.KV("extra", interruptData))
}
return "", err
}
return r.TrimmedResp, nil
}
func toPluginCommonAPIParameter(parameter *workflow3.APIParameter) *common.APIParameter {
if parameter == nil {
return nil
}
p := &common.APIParameter{
ID: parameter.ID,
Name: parameter.Name,
Desc: parameter.Desc,
Type: common.ParameterType(parameter.Type),
Location: common.ParameterLocation(parameter.Location),
IsRequired: parameter.IsRequired,
GlobalDefault: parameter.GlobalDefault,
GlobalDisable: parameter.GlobalDisable,
LocalDefault: parameter.LocalDefault,
LocalDisable: parameter.LocalDisable,
VariableRef: parameter.VariableRef,
}
if parameter.SubType != nil {
p.SubType = ptr.Of(common.ParameterType(*parameter.SubType))
}
if parameter.DefaultParamSource != nil {
p.DefaultParamSource = ptr.Of(common.DefaultParamSource(*parameter.DefaultParamSource))
}
if parameter.AssistType != nil {
p.AssistType = ptr.Of(common.AssistParameterType(*parameter.AssistType))
}
if len(parameter.SubParameters) > 0 {
p.SubParameters = make([]*common.APIParameter, 0, len(parameter.SubParameters))
for _, subParam := range parameter.SubParameters {
p.SubParameters = append(p.SubParameters, toPluginCommonAPIParameter(subParam))
}
}
return p
}
func toWorkflowAPIParameter(parameter *common.APIParameter) *workflow3.APIParameter {
if parameter == nil {
return nil
}
p := &workflow3.APIParameter{
ID: parameter.ID,
Name: parameter.Name,
Desc: parameter.Desc,
Type: workflow3.ParameterType(parameter.Type),
Location: workflow3.ParameterLocation(parameter.Location),
IsRequired: parameter.IsRequired,
GlobalDefault: parameter.GlobalDefault,
GlobalDisable: parameter.GlobalDisable,
LocalDefault: parameter.LocalDefault,
LocalDisable: parameter.LocalDisable,
VariableRef: parameter.VariableRef,
}
if parameter.SubType != nil {
p.SubType = ptr.Of(workflow3.ParameterType(*parameter.SubType))
}
if parameter.DefaultParamSource != nil {
p.DefaultParamSource = ptr.Of(workflow3.DefaultParamSource(*parameter.DefaultParamSource))
}
if parameter.AssistType != nil {
p.AssistType = ptr.Of(workflow3.AssistParameterType(*parameter.AssistType))
}
if len(parameter.SubParameters) > 0 {
p.SubParameters = make([]*workflow3.APIParameter, 0, len(parameter.SubParameters))
for _, subParam := range parameter.SubParameters {
p.SubParameters = append(p.SubParameters, toWorkflowAPIParameter(subParam))
}
}
return p
}

View File

@@ -0,0 +1,74 @@
/*
* 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 search
import (
"context"
"github.com/coze-dev/coze-studio/backend/api/model/resource/common"
"github.com/coze-dev/coze-studio/backend/domain/search/entity"
search "github.com/coze-dev/coze-studio/backend/domain/search/service"
crosssearch "github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/search"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
)
type Notifier interface {
PublishWorkflowResource(ctx context.Context, OpType crosssearch.OpType, event *crosssearch.Resource) error
}
type Notify struct {
client search.ResourceEventBus
}
func NewNotify(client search.ResourceEventBus) *Notify {
return &Notify{client: client}
}
func (n *Notify) PublishWorkflowResource(ctx context.Context, op crosssearch.OpType, r *crosssearch.Resource) error {
entityResource := &entity.ResourceDocument{
ResType: common.ResType_Workflow,
ResID: r.WorkflowID,
ResSubType: r.Mode,
Name: r.Name,
SpaceID: r.SpaceID,
OwnerID: r.OwnerID,
APPID: r.APPID,
}
if r.PublishStatus != nil {
publishStatus := *r.PublishStatus
entityResource.PublishStatus = ptr.Of(common.PublishStatus(publishStatus))
entityResource.PublishTimeMS = r.PublishedAt
}
resource := &entity.ResourceDomainEvent{
OpType: entity.OpType(op),
Resource: entityResource,
}
if op == crosssearch.Created {
resource.Resource.CreateTimeMS = r.CreatedAt
resource.Resource.UpdateTimeMS = r.UpdatedAt
} else if op == crosssearch.Updated {
resource.Resource.UpdateTimeMS = r.UpdatedAt
}
err := n.client.PublishResources(ctx, resource)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,399 @@
/*
* 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 variable
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/bytedance/sonic"
"github.com/cloudwego/eino/compose"
variablesModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/variables"
"github.com/coze-dev/coze-studio/backend/api/model/kvmemory"
"github.com/coze-dev/coze-studio/backend/api/model/project_memory"
"github.com/coze-dev/coze-studio/backend/domain/memory/variables/entity"
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
"github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/variable"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
"github.com/coze-dev/coze-studio/backend/types/errno"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
)
type varStore struct {
variableChannel project_memory.VariableChannel
vs variables.Variables
}
func NewVariableHandler(vs variables.Variables) *variable.Handler {
return &variable.Handler{
UserVarStore: newUserVarStore(vs),
AppVarStore: newAppVarStore(vs),
SystemVarStore: newSystemVarStore(vs),
}
}
func newUserVarStore(vs variables.Variables) variable.Store {
return &varStore{
variableChannel: project_memory.VariableChannel_Custom,
vs: vs,
}
}
func newAppVarStore(vs variables.Variables) variable.Store {
return &varStore{
variableChannel: project_memory.VariableChannel_APP,
vs: vs,
}
}
func newSystemVarStore(vs variables.Variables) variable.Store {
return &varStore{
variableChannel: project_memory.VariableChannel_System,
vs: vs,
}
}
func (v *varStore) Init(ctx context.Context) {
}
func (v *varStore) Get(ctx context.Context, path compose.FieldPath, opts ...variable.OptionFn) (any, error) {
opt := &variable.StoreConfig{}
for _, o := range opts {
o(opt)
}
var (
bizID string
bizType project_memory.VariableConnector
)
if opt.StoreInfo.AppID != nil {
bizID = strconv.FormatInt(*opt.StoreInfo.AppID, 10)
bizType = project_memory.VariableConnector_Project
} else if opt.StoreInfo.AgentID != nil {
bizID = strconv.FormatInt(*opt.StoreInfo.AgentID, 10)
bizType = project_memory.VariableConnector_Bot
} else {
return nil, fmt.Errorf("there must be one of the App ID or Agent ID")
}
meta := entity.NewUserVariableMeta(&variablesModel.UserVariableMeta{
BizType: bizType,
BizID: bizID,
ConnectorID: opt.StoreInfo.ConnectorID,
ConnectorUID: opt.StoreInfo.ConnectorUID,
})
if len(path) == 0 {
return nil, errors.New("field path is required")
}
key := path[0]
kvItems, err := v.vs.GetVariableChannelInstance(ctx, meta, []string{key}, project_memory.VariableChannelPtr(v.variableChannel))
if err != nil {
return nil, err
}
if len(kvItems) == 0 {
return nil, fmt.Errorf("variable %s not exists", key)
}
value := kvItems[0].GetValue()
schema := kvItems[0].GetSchema()
varSchema, err := entity.NewVariableMetaSchema([]byte(schema))
if err != nil {
return nil, err
}
if varSchema.IsArrayType() {
if value == "" {
return nil, nil
}
result := make([]interface{}, 0)
err = sonic.Unmarshal([]byte(value), &result)
if err != nil {
return nil, err
}
return result, nil
}
if varSchema.IsObjectType() {
if value == "" {
return nil, nil
}
result := make(map[string]any)
err = sonic.Unmarshal([]byte(value), &result)
if err != nil {
return nil, err
}
if len(path) > 1 {
if val, ok := takeMapValue(result, path[1:]); ok {
return val, nil
}
return nil, nil
}
return result, nil
}
if varSchema.IsStringType() {
return value, nil
}
if varSchema.IsBooleanType() {
if value == "" {
return false, nil
}
result, err := strconv.ParseBool(value)
if err != nil {
return nil, err
}
return result, nil
}
if varSchema.IsNumberType() {
if value == "" {
return 0, nil
}
result, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, err
}
return result, nil
}
if varSchema.IsIntegerType() {
if value == "" {
return 0, nil
}
result, err := strconv.ParseInt(value, 64, 10)
if err != nil {
return nil, err
}
return result, nil
}
return value, nil
}
func (v *varStore) Set(ctx context.Context, path compose.FieldPath, value any, opts ...variable.OptionFn) (err error) {
opt := &variable.StoreConfig{}
for _, o := range opts {
o(opt)
}
var (
bizID string
bizType project_memory.VariableConnector
)
if opt.StoreInfo.AppID != nil {
bizID = strconv.FormatInt(*opt.StoreInfo.AppID, 10)
bizType = project_memory.VariableConnector_Project
} else if opt.StoreInfo.AgentID != nil {
bizID = strconv.FormatInt(*opt.StoreInfo.AgentID, 10)
bizType = project_memory.VariableConnector_Bot
} else {
return fmt.Errorf("there must be one of the App ID or Agent ID")
}
meta := entity.NewUserVariableMeta(&variablesModel.UserVariableMeta{
BizType: bizType,
BizID: bizID,
ConnectorID: opt.StoreInfo.ConnectorID,
ConnectorUID: opt.StoreInfo.ConnectorUID,
})
if len(path) == 0 {
return errors.New("field path is required")
}
key := path[0]
kvItems := make([]*kvmemory.KVItem, 0, 1)
valueString := ""
if _, ok := value.(string); ok {
valueString = value.(string)
} else {
valueString, err = sonic.MarshalString(value)
if err != nil {
return err
}
}
isSystem := ternary.IFElse[bool](v.variableChannel == project_memory.VariableChannel_System, true, false)
kvItems = append(kvItems, &kvmemory.KVItem{
Keyword: key,
Value: valueString,
IsSystem: isSystem,
})
_, err = v.vs.SetVariableInstance(ctx, meta, kvItems)
if err != nil {
return err
}
return nil
}
type variablesMetaGetter struct {
vs variables.Variables
}
func NewVariablesMetaGetter(vs variables.Variables) variable.VariablesMetaGetter {
return &variablesMetaGetter{
vs: vs,
}
}
func (v variablesMetaGetter) GetAppVariablesMeta(ctx context.Context, id, version string) (m map[string]*vo.TypeInfo, err error) {
var varMetas *entity.VariablesMeta
varMetas, err = v.vs.GetProjectVariablesMeta(ctx, id, version)
if err != nil {
return nil, err
}
m = make(map[string]*vo.TypeInfo, len(varMetas.Variables))
for _, v := range varMetas.Variables {
varSchema, err := v.GetSchema(ctx)
if err != nil {
return nil, vo.WrapIfNeeded(errno.ErrVariablesAPIFail, err)
}
t, err := varMeta2TypeInfo(varSchema)
if err != nil {
return nil, err
}
m[v.Keyword] = t
}
return m, nil
}
func (v variablesMetaGetter) GetAgentVariablesMeta(ctx context.Context, id int64, version string) (m map[string]*vo.TypeInfo, err error) {
var varMetas *entity.VariablesMeta
varMetas, err = v.vs.GetAgentVariableMeta(ctx, id, version)
if err != nil {
return nil, err
}
m = make(map[string]*vo.TypeInfo, len(varMetas.Variables))
for _, v := range varMetas.Variables {
varSchema, err := v.GetSchema(ctx)
if err != nil {
return nil, vo.WrapIfNeeded(errno.ErrVariablesAPIFail, err)
}
t, err := varMeta2TypeInfo(varSchema)
if err != nil {
return nil, err
}
m[v.Keyword] = t
}
return m, nil
}
func varMeta2TypeInfo(v *entity.VariableMetaSchema) (*vo.TypeInfo, error) {
if v.IsBooleanType() {
return &vo.TypeInfo{
Type: vo.DataTypeBoolean,
}, nil
}
if v.IsStringType() {
return &vo.TypeInfo{
Type: vo.DataTypeString,
}, nil
}
if v.IsNumberType() {
return &vo.TypeInfo{
Type: vo.DataTypeNumber,
}, nil
}
if v.IsIntegerType() {
return &vo.TypeInfo{
Type: vo.DataTypeInteger,
}, nil
}
if v.IsArrayType() {
if len(v.Schema) == 0 {
return nil, vo.WrapError(errno.ErrVariablesAPIFail, fmt.Errorf("array type should contain element type info"))
}
elemType, err := entity.NewVariableMetaSchema(v.Schema)
if err != nil {
return nil, vo.WrapIfNeeded(errno.ErrVariablesAPIFail, err)
}
et, err := varMeta2TypeInfo(elemType)
if err != nil {
return nil, err
}
return &vo.TypeInfo{
Type: vo.DataTypeArray,
ElemTypeInfo: et,
}, nil
}
if v.IsObjectType() {
ps, err := v.GetObjectProperties(v.Schema)
if err != nil {
return nil, vo.WrapIfNeeded(errno.ErrVariablesAPIFail, err)
}
properties := make(map[string]*vo.TypeInfo, len(ps))
for k, p := range ps {
pt, err := varMeta2TypeInfo(p)
if err != nil {
return nil, err
}
properties[k] = pt
}
return &vo.TypeInfo{
Type: vo.DataTypeObject,
Properties: properties,
}, nil
}
return nil, vo.WrapError(errno.ErrVariablesAPIFail, fmt.Errorf("invalid variable type"))
}
func takeMapValue(m map[string]any, path []string) (any, bool) {
if m == nil {
return nil, false
}
container := m
for _, p := range path[:len(path)-1] {
if _, ok := container[p]; !ok {
return nil, false
}
container = container[p].(map[string]any)
}
if v, ok := container[path[len(path)-1]]; ok {
return v, true
}
return nil, false
}