This commit is contained in:
mlogclub
2026-04-09 10:01:23 +08:00
commit efe801b8bf
707 changed files with 110595 additions and 0 deletions
@@ -0,0 +1,26 @@
package adapter
import "cs-agent/internal/models"
type AIConfigSnapshot struct {
ID int64
Provider string
ModelName string
BaseURL string
MaxOutputTokens int
TimeoutMS int
}
func BuildAIConfigSnapshot(item *models.AIConfig) *AIConfigSnapshot {
if item == nil {
return nil
}
return &AIConfigSnapshot{
ID: item.ID,
Provider: string(item.Provider),
ModelName: item.ModelName,
BaseURL: item.BaseURL,
MaxOutputTokens: item.MaxOutputTokens,
TimeoutMS: item.TimeoutMS,
}
}
@@ -0,0 +1,22 @@
package adapter
import "cs-agent/internal/models"
type ConversationSnapshot struct {
ID int64
AIAgentID int64
LastMessageID int64
CurrentAssigneeID int64
}
func BuildConversationSnapshot(item *models.Conversation) *ConversationSnapshot {
if item == nil {
return nil
}
return &ConversationSnapshot{
ID: item.ID,
AIAgentID: item.AIAgentID,
LastMessageID: item.LastMessageID,
CurrentAssigneeID: item.CurrentAssigneeID,
}
}
@@ -0,0 +1,192 @@
package adapter
import (
"context"
"encoding/json"
"fmt"
"hash/crc32"
"regexp"
"strings"
"cs-agent/internal/ai/mcps"
einojsonschema "github.com/eino-contrib/jsonschema"
einotool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
var toolNameSanitizer = regexp.MustCompile(`[^a-zA-Z0-9_]`)
type MCPToolDefinition struct {
ToolCode string
ServerCode string
ToolName string
ModelName string
Title string
Description string
FixedArgs map[string]string
}
type MCPTool struct {
definition MCPToolDefinition
info *schema.ToolInfo
}
func NewMCPTool(definition MCPToolDefinition, metadata *mcps.ToolInfo) *MCPTool {
return &MCPTool{
definition: definition,
info: buildToolInfo(definition, metadata),
}
}
var _ einotool.InvokableTool = (*MCPTool)(nil)
func (t *MCPTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
if t == nil || t.info == nil {
return nil, nil
}
return t.info, nil
}
func (t *MCPTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) {
if t == nil {
return "", fmt.Errorf("mcp tool is nil")
}
arguments, err := parseArguments(argumentsInJSON)
if err != nil {
return "", err
}
arguments = mergeFixedArguments(arguments, t.definition.FixedArgs)
result, err := mcps.Runtime.CallTool(ctx, t.definition.ServerCode, t.definition.ToolName, arguments)
if err != nil {
return "", err
}
return buildToolResultSummary(result), nil
}
func buildToolInfo(definition MCPToolDefinition, metadata *mcps.ToolInfo) *schema.ToolInfo {
desc := strings.TrimSpace(definition.Description)
if desc == "" && metadata != nil {
desc = strings.TrimSpace(metadata.Description)
}
title := strings.TrimSpace(definition.Title)
if title == "" && metadata != nil {
title = strings.TrimSpace(metadata.Title)
}
if title != "" && desc != "" {
desc = title + "\n\n" + desc
} else if title != "" {
desc = title
}
if desc == "" {
desc = "Call MCP tool " + strings.TrimSpace(definition.ToolCode)
}
info := &schema.ToolInfo{
Name: BuildModelToolName(definition),
Desc: desc,
Extra: map[string]any{
"toolCode": definition.ToolCode,
"serverCode": definition.ServerCode,
"toolName": definition.ToolName,
},
}
if js := buildParamsSchema(metadata); js != nil {
info.ParamsOneOf = schema.NewParamsOneOfByJSONSchema(js)
}
return info
}
func buildParamsSchema(metadata *mcps.ToolInfo) *einojsonschema.Schema {
if metadata == nil || metadata.InputSchema == nil {
return genericObjectSchema()
}
raw, err := json.Marshal(metadata.InputSchema)
if err != nil || len(raw) == 0 {
return genericObjectSchema()
}
js := &einojsonschema.Schema{}
if err := json.Unmarshal(raw, js); err != nil {
return genericObjectSchema()
}
return js
}
func genericObjectSchema() *einojsonschema.Schema {
return &einojsonschema.Schema{
Version: einojsonschema.Version,
Type: "object",
AdditionalProperties: &einojsonschema.Schema{},
}
}
func parseArguments(argumentsInJSON string) (map[string]any, error) {
argumentsInJSON = strings.TrimSpace(argumentsInJSON)
if argumentsInJSON == "" {
return map[string]any{}, nil
}
args := make(map[string]any)
if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil {
return nil, fmt.Errorf("invalid tool arguments: %w", err)
}
return args, nil
}
func mergeFixedArguments(arguments map[string]any, fixedArgs map[string]string) map[string]any {
if len(arguments) == 0 && len(fixedArgs) == 0 {
return map[string]any{}
}
ret := make(map[string]any, len(arguments)+len(fixedArgs))
for key, value := range arguments {
ret[key] = value
}
for key, value := range fixedArgs {
ret[key] = strings.TrimSpace(value)
}
return ret
}
func buildToolResultSummary(result *mcps.ToolCallResult) string {
if result == nil {
return ""
}
lines := make([]string, 0, len(result.Content)+2)
if result.IsError {
lines = append(lines, "tool returned an error")
}
if result.StructuredContent != nil {
if data, err := json.Marshal(result.StructuredContent); err == nil {
lines = append(lines, string(data))
}
}
for _, item := range result.Content {
switch item.Type {
case "text":
if text := strings.TrimSpace(item.Text); text != "" {
lines = append(lines, text)
}
default:
if item.Data == nil {
continue
}
if data, err := json.Marshal(item.Data); err == nil {
lines = append(lines, string(data))
}
}
}
return strings.TrimSpace(strings.Join(lines, "\n"))
}
func BuildModelToolName(definition MCPToolDefinition) string {
if strings.TrimSpace(definition.ModelName) != "" {
return strings.TrimSpace(definition.ModelName)
}
base := "mcp_" + strings.TrimSpace(definition.ServerCode) + "_" + strings.TrimSpace(definition.ToolName)
base = toolNameSanitizer.ReplaceAllString(base, "_")
base = strings.Trim(base, "_")
if base == "" {
base = "mcp_tool"
}
checksum := crc32.ChecksumIEEE([]byte(definition.ToolCode))
return fmt.Sprintf("%s_%08x", base, checksum)
}
@@ -0,0 +1,69 @@
package adapter
import (
"strings"
"cs-agent/internal/models"
"cs-agent/internal/pkg/enums"
"cs-agent/internal/repositories"
"github.com/cloudwego/eino/schema"
"github.com/mlogclub/simple/sqls"
)
const defaultHistoryLimit = 12
type HistoryBuildResult struct {
Messages []*schema.Message
RawItems []models.Message
}
func BuildHistoryMessages(conversationID int64, currentMessageID int64, limit int) HistoryBuildResult {
if conversationID <= 0 {
return HistoryBuildResult{}
}
if limit <= 0 {
limit = defaultHistoryLimit
}
items := repositories.MessageRepository.Find(sqls.DB(), sqls.NewCnd().
Eq("conversation_id", conversationID).
Desc("id").
Limit(limit+1))
for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
items[i], items[j] = items[j], items[i]
}
ret := HistoryBuildResult{
Messages: make([]*schema.Message, 0, len(items)),
RawItems: make([]models.Message, 0, len(items)),
}
for _, item := range items {
if item.ID == currentMessageID {
continue
}
msg := BuildSchemaMessage(&item)
if msg == nil {
continue
}
ret.RawItems = append(ret.RawItems, item)
ret.Messages = append(ret.Messages, msg)
}
return ret
}
func BuildSchemaMessage(item *models.Message) *schema.Message {
if item == nil {
return nil
}
content := strings.TrimSpace(item.Content)
if content == "" {
return nil
}
switch item.SenderType {
case enums.IMSenderTypeCustomer:
return schema.UserMessage(content)
case enums.IMSenderTypeAI, enums.IMSenderTypeAgent:
return schema.AssistantMessage(content, nil)
default:
return nil
}
}