mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-15 15:40:38 +02:00
Add files via upload
This commit is contained in:
@@ -120,7 +120,7 @@ func RunEinoSingleChatModelAgent(
|
||||
}
|
||||
if einoSkillMW != nil {
|
||||
if einoFSTools && einoLoc != nil {
|
||||
fsMw, fsErr := subAgentAgenticFilesystemMiddleware(ctx, einoLoc, toolInvokeNotify, einoSingleAgentName, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
|
||||
fsMw, fsErr := subAgentAgenticFilesystemMiddleware(ctx, einoLoc, toolInvokeNotify, einoSingleAgentName, conversationID, projectID, ma.EinoMiddleware.ReductionRootDir, toolMaxBytesFromMW(&ma.EinoMiddleware), mcpExecBinder, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
|
||||
if fsErr != nil {
|
||||
return nil, fmt.Errorf("eino single filesystem 中间件: %w", fsErr)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package multiagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,11 +11,13 @@ import (
|
||||
"cyberstrike-ai/internal/config"
|
||||
"cyberstrike-ai/internal/einomcp"
|
||||
"cyberstrike-ai/internal/security"
|
||||
"cyberstrike-ai/internal/tooloutput"
|
||||
|
||||
localbk "github.com/cloudwego/eino-ext/adk/backend/local"
|
||||
"github.com/cloudwego/eino/adk"
|
||||
"github.com/cloudwego/eino/adk/middlewares/filesystem"
|
||||
"github.com/cloudwego/eino/adk/middlewares/skill"
|
||||
"github.com/cloudwego/eino/components/tool"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -100,6 +103,11 @@ func subAgentAgenticFilesystemMiddleware(
|
||||
loc *localbk.Local,
|
||||
invokeNotify *einomcp.ToolInvokeNotifyHolder,
|
||||
einoAgentName string,
|
||||
conversationID string,
|
||||
projectID string,
|
||||
reductionRootDir string,
|
||||
toolMaxBytes int,
|
||||
binder *MCPExecutionBinder,
|
||||
beginMonitor func(toolCallID, command string) string,
|
||||
appendPartialMonitor func(executionID, toolCallID, chunk string),
|
||||
registerCancelMonitor func(executionID string, cancel context.CancelFunc),
|
||||
@@ -113,7 +121,7 @@ func subAgentAgenticFilesystemMiddleware(
|
||||
if loc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return filesystem.NewTyped[*schema.AgenticMessage](ctx, &filesystem.MiddlewareConfig{
|
||||
mw, err := filesystem.NewTyped[*schema.AgenticMessage](ctx, &filesystem.MiddlewareConfig{
|
||||
Backend: loc,
|
||||
StreamingShell: &einoStreamingShellWrap{
|
||||
inner: security.NewEinoStreamingShell(),
|
||||
@@ -130,6 +138,71 @@ func subAgentAgenticFilesystemMiddleware(
|
||||
shellNoOutputTimeoutSec: shellNoOutputTimeoutSec,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &einoAgenticFilesystemToolMiddleware{
|
||||
TypedChatModelAgentMiddleware: mw,
|
||||
conversationID: conversationID,
|
||||
projectID: projectID,
|
||||
reductionRootDir: reductionRootDir,
|
||||
toolMaxBytes: toolMaxBytes,
|
||||
binder: binder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type einoAgenticFilesystemToolMiddleware struct {
|
||||
adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
|
||||
conversationID string
|
||||
projectID string
|
||||
reductionRootDir string
|
||||
toolMaxBytes int
|
||||
binder *MCPExecutionBinder
|
||||
}
|
||||
|
||||
func (m *einoAgenticFilesystemToolMiddleware) WrapInvokableToolCall(ctx context.Context, endpoint adk.InvokableToolCallEndpoint, tCtx *adk.ToolContext) (adk.InvokableToolCallEndpoint, error) {
|
||||
wrapped, err := m.TypedChatModelAgentMiddleware.WrapInvokableToolCall(ctx, endpoint, tCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tCtx == nil || !isBuiltinEinoADKFilesystemToolName(tCtx.Name) {
|
||||
return wrapped, nil
|
||||
}
|
||||
return func(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
|
||||
args := parseToolArgumentsObject(argumentsInJSON)
|
||||
if len(args) > 0 && m.binder != nil {
|
||||
m.binder.BindArguments(tCtx.CallID, args)
|
||||
}
|
||||
result, runErr := wrapped(ctx, argumentsInJSON, opts...)
|
||||
if runErr != nil {
|
||||
return result, runErr
|
||||
}
|
||||
return m.boundToolResult(tCtx.CallID, result), nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *einoAgenticFilesystemToolMiddleware) boundToolResult(toolCallID, result string) string {
|
||||
if m == nil || m.toolMaxBytes <= 0 || len(result) <= m.toolMaxBytes {
|
||||
return result
|
||||
}
|
||||
return tooloutput.BoundWithSpill(result, m.toolMaxBytes, tooloutput.SpillOpts{
|
||||
RootDir: m.reductionRootDir,
|
||||
ProjectID: m.projectID,
|
||||
ConversationID: m.conversationID,
|
||||
ExecutionID: toolCallID,
|
||||
})
|
||||
}
|
||||
|
||||
func parseToolArgumentsObject(raw string) map[string]interface{} {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" || raw == "{}" || raw == "null" {
|
||||
return nil
|
||||
}
|
||||
var args map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(raw), &args); err != nil || len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// agentToolTimeoutMinutes 返回 agent.tool_timeout_minutes(与 executeToolViaMCP 一致);cfg 为 nil 时 0。
|
||||
|
||||
@@ -115,7 +115,22 @@ func (e *einoToolResultProgressEmitter) Emit(ctx context.Context, toolName, cont
|
||||
if e.executeStdoutDup != nil {
|
||||
e.executeStdoutDup.Record(toolName, content, displayIsErr)
|
||||
}
|
||||
recordEinoADKFilesystemToolMonitor(ctx, e.filesystemMonitorAgent, e.filesystemMonitorRecord, e.mcpExecutionBinder, toolName, toolCallID, e.messages(), content, displayIsErr)
|
||||
if args := e.toolCallArguments(toolCallID, toolName); len(args) > 0 {
|
||||
data["argumentsObj"] = args
|
||||
data["arguments"] = mustMarshalToolArguments(args)
|
||||
}
|
||||
if execID := recordEinoADKFilesystemToolMonitor(ctx, e.filesystemMonitorAgent, e.filesystemMonitorRecord, e.mcpExecutionBinder, toolName, toolCallID, e.messages(), content, displayIsErr); execID != "" {
|
||||
if stored := e.filesystemMonitorAgent.MCPExecutionResultText(execID); strings.TrimSpace(stored) != "" {
|
||||
content = stored
|
||||
if len(content) > 200 {
|
||||
preview = content[:200] + "..."
|
||||
} else {
|
||||
preview = content
|
||||
}
|
||||
data["result"] = content
|
||||
data["resultPreview"] = preview
|
||||
}
|
||||
}
|
||||
if e.filesystemMonitorAgent != nil && e.mcpExecutionBinder != nil {
|
||||
if execID := e.mcpExecutionBinder.ExecutionID(toolCallID); execID != "" {
|
||||
e.filesystemMonitorAgent.UpdateMCPExecutionDisplayResult(execID, content)
|
||||
@@ -152,3 +167,15 @@ func (e *einoToolResultProgressEmitter) messages() []adk.Message {
|
||||
}
|
||||
return e.runMessages.Messages()
|
||||
}
|
||||
|
||||
func (e *einoToolResultProgressEmitter) toolCallArguments(toolCallID, toolName string) map[string]interface{} {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
if e.mcpExecutionBinder != nil {
|
||||
if args := e.mcpExecutionBinder.Arguments(toolCallID); len(args) > 0 {
|
||||
return args
|
||||
}
|
||||
}
|
||||
return toolCallArgsFromAccumulated(e.messages(), toolCallID, toolName)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package multiagent
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cloudwego/eino/adk"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
func TestEinoToolResultProgressEmitterInfersPendingAndDedupes(t *testing.T) {
|
||||
var events []map[string]interface{}
|
||||
@@ -135,3 +140,44 @@ func TestEinoToolResultProgressEmitterTruncatesPreview(t *testing.T) {
|
||||
t.Fatalf("preview = %q len=%d", got, len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoToolResultProgressEmitterBackfillsArgumentsFromRunMessages(t *testing.T) {
|
||||
var data map[string]interface{}
|
||||
progress := func(eventType, _ string, raw interface{}) {
|
||||
if eventType == "tool_result" {
|
||||
data, _ = raw.(map[string]interface{})
|
||||
}
|
||||
}
|
||||
runMessages := newEinoRunMessageAccumulator([]adk.Message{
|
||||
&schema.Message{
|
||||
Role: schema.Assistant,
|
||||
ToolCalls: []schema.ToolCall{{
|
||||
ID: "call-read",
|
||||
Type: "function",
|
||||
Function: schema.FunctionCall{
|
||||
Name: "read_file",
|
||||
Arguments: `{"path":"/tmp/requirements.txt"}`,
|
||||
},
|
||||
}},
|
||||
},
|
||||
})
|
||||
emitter := newEinoToolResultProgressEmitter(einoToolResultProgressEmitterConfig{
|
||||
ConversationID: "conv-1",
|
||||
Progress: progress,
|
||||
RunMessages: runMessages,
|
||||
})
|
||||
|
||||
if !emitter.Emit(nil, "read_file", "ok", "call-read", false, "lead") {
|
||||
t.Fatal("expected tool result emit")
|
||||
}
|
||||
args, ok := data["argumentsObj"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("argumentsObj = %#v", data["argumentsObj"])
|
||||
}
|
||||
if args["path"] != "/tmp/requirements.txt" {
|
||||
t.Fatalf("path = %#v, want /tmp/requirements.txt", args["path"])
|
||||
}
|
||||
if data["arguments"] != `{"path":"/tmp/requirements.txt"}` {
|
||||
t.Fatalf("arguments = %#v", data["arguments"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,16 @@ import (
|
||||
|
||||
// MCPExecutionBinder maps ADK toolCallID → MCP monitor execution ID for a single agent run.
|
||||
type MCPExecutionBinder struct {
|
||||
mu sync.RWMutex
|
||||
byToolCall map[string]string
|
||||
mu sync.RWMutex
|
||||
byToolCall map[string]string
|
||||
argsByToolCall map[string]map[string]interface{}
|
||||
}
|
||||
|
||||
func NewMCPExecutionBinder() *MCPExecutionBinder {
|
||||
return &MCPExecutionBinder{byToolCall: make(map[string]string)}
|
||||
return &MCPExecutionBinder{
|
||||
byToolCall: make(map[string]string),
|
||||
argsByToolCall: make(map[string]map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *MCPExecutionBinder) Bind(toolCallID, executionID string) {
|
||||
@@ -29,6 +33,19 @@ func (b *MCPExecutionBinder) Bind(toolCallID, executionID string) {
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *MCPExecutionBinder) BindArguments(toolCallID string, args map[string]interface{}) {
|
||||
if b == nil || len(args) == 0 {
|
||||
return
|
||||
}
|
||||
tid := strings.TrimSpace(toolCallID)
|
||||
if tid == "" {
|
||||
return
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.argsByToolCall[tid] = cloneToolArgs(args)
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *MCPExecutionBinder) ExecutionID(toolCallID string) string {
|
||||
if b == nil {
|
||||
return ""
|
||||
@@ -38,3 +55,24 @@ func (b *MCPExecutionBinder) ExecutionID(toolCallID string) string {
|
||||
defer b.mu.RUnlock()
|
||||
return b.byToolCall[tid]
|
||||
}
|
||||
|
||||
func (b *MCPExecutionBinder) Arguments(toolCallID string) map[string]interface{} {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
tid := strings.TrimSpace(toolCallID)
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
return cloneToolArgs(b.argsByToolCall[tid])
|
||||
}
|
||||
|
||||
func cloneToolArgs(args map[string]interface{}) map[string]interface{} {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]interface{}, len(args))
|
||||
for k, v := range args {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -9,9 +9,13 @@ import (
|
||||
func TestMCPExecutionBinder(t *testing.T) {
|
||||
b := NewMCPExecutionBinder()
|
||||
b.Bind("call-1", "exec-1")
|
||||
b.BindArguments("call-1", map[string]interface{}{"file_path": "/tmp/a.txt"})
|
||||
if got := b.ExecutionID("call-1"); got != "exec-1" {
|
||||
t.Fatalf("expected exec-1, got %q", got)
|
||||
}
|
||||
if got := b.Arguments("call-1"); got["file_path"] != "/tmp/a.txt" {
|
||||
t.Fatalf("arguments = %#v", got)
|
||||
}
|
||||
if got := b.ExecutionID("missing"); got != "" {
|
||||
t.Fatalf("expected empty, got %q", got)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ type RunResult struct {
|
||||
type toolCallPendingInfo struct {
|
||||
ToolCallID string
|
||||
ToolName string
|
||||
Arguments map[string]interface{}
|
||||
EinoAgent string
|
||||
EinoRole string
|
||||
}
|
||||
@@ -232,7 +233,7 @@ func RunDeepAgent(
|
||||
}
|
||||
if agenticSkillMW != nil {
|
||||
if agenticFSTools && agenticLoc != nil {
|
||||
subFs, fsErr := subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, id, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
|
||||
subFs, fsErr := subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, id, conversationID, projectID, ma.EinoMiddleware.ReductionRootDir, toolMaxBytesFromMW(&ma.EinoMiddleware), mcpExecBinder, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
|
||||
if fsErr != nil {
|
||||
return nil, fmt.Errorf("子代理 %q filesystem 中间件: %w", id, fsErr)
|
||||
}
|
||||
@@ -492,7 +493,7 @@ func RunDeepAgent(
|
||||
}
|
||||
var peFsMw adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
|
||||
if agenticSkillMW != nil && agenticFSTools && agenticLoc != nil {
|
||||
peFsMw, err = subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, "executor", einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
|
||||
peFsMw, err = subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, "executor", conversationID, projectID, ma.EinoMiddleware.ReductionRootDir, toolMaxBytesFromMW(&ma.EinoMiddleware), mcpExecBinder, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plan_execute agentic filesystem 中间件: %w", err)
|
||||
}
|
||||
@@ -994,6 +995,7 @@ func emitToolCallsFromMessage(
|
||||
markPending(toolCallPendingInfo{
|
||||
ToolCallID: toolCallID,
|
||||
ToolName: display,
|
||||
Arguments: argsObj,
|
||||
EinoAgent: agentName,
|
||||
EinoRole: role,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user