feat(ai): Agent SSE + Anthropic/responses tools + P1 确认写
- Agent 支持 SSE(步骤边推、正文分块),JSON 非流式 fallback - tool calling 覆盖 openai-chat / anthropic / responses(单测解析回灌) - P1:mcp.json mode=read_write 才可写;需 confirm_danger;Agent 剥离模型自带令牌 - GET /api/ai/mcp-status、POST /api/ai/agent/confirm;审计 source=mcp|agent
This commit is contained in:
1 parent
1d6e89449a
commit
2336c9433c
17 files changed
+1324
-197
No files matched your search
@@ -227,3 +227,75 @@ func TestToolNamesStable(t *testing.T) {
|
||||
t.Fatalf("%v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteQueryNeedConfirmInReadWrite(t *testing.T) {
|
||||
called := false
|
||||
rt := NewRuntime(Policy{
|
||||
Mode: ModeReadWrite,
|
||||
AllowedConnectionIDs: []string{"c1"},
|
||||
DefaultLimit: 100,
|
||||
MaxLimit: 500,
|
||||
})
|
||||
rt.FindConn = func(id string) (*SavedConnection, error) {
|
||||
return &SavedConnection{ID: id, Name: "d", Driver: "mysql", Host: "127.0.0.1", Port: 3306}, nil
|
||||
}
|
||||
rt.ExecSQL = func(_ context.Context, c db.ConnReq) db.ExecResult {
|
||||
called = true
|
||||
return db.ExecResult{RowCount: 1}
|
||||
}
|
||||
rt.Audit = func(AuditEntry) {}
|
||||
|
||||
out, err := rt.Call(context.Background(), ToolExecuteQuery, map[string]any{
|
||||
"connection_id": "c1",
|
||||
"sql": "DELETE FROM users WHERE id=1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := out.(map[string]any)
|
||||
if m["needConfirm"] != true {
|
||||
t.Fatalf("want needConfirm, got %#v", m)
|
||||
}
|
||||
if called {
|
||||
t.Fatal("must not exec without confirm")
|
||||
}
|
||||
|
||||
out2, err := rt.Call(context.Background(), ToolExecuteQuery, map[string]any{
|
||||
"connection_id": "c1",
|
||||
"sql": "DELETE FROM users WHERE id=1",
|
||||
"confirm_danger": true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("expected exec after confirm")
|
||||
}
|
||||
_ = out2
|
||||
}
|
||||
|
||||
func TestExecuteQueryRejectConfirmWhenAgentFlag(t *testing.T) {
|
||||
called := false
|
||||
rt := NewRuntime(Policy{Mode: ModeReadWrite, AllowedConnectionIDs: []string{"c1"}, DefaultLimit: 100, MaxLimit: 500})
|
||||
rt.RejectCallerConfirm = true
|
||||
rt.FindConn = func(id string) (*SavedConnection, error) {
|
||||
return &SavedConnection{ID: id, Driver: "mysql", Host: "h", Port: 1}, nil
|
||||
}
|
||||
rt.ExecSQL = func(_ context.Context, c db.ConnReq) db.ExecResult {
|
||||
called = true
|
||||
return db.ExecResult{}
|
||||
}
|
||||
rt.Audit = func(AuditEntry) {}
|
||||
out, err := rt.Call(context.Background(), ToolExecuteQuery, map[string]any{
|
||||
"connection_id": "c1", "sql": "UPDATE t SET a=1", "confirm_danger": true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out.(map[string]any)["needConfirm"] != true {
|
||||
t.Fatalf("%#v", out)
|
||||
}
|
||||
if called {
|
||||
t.Fatal("agent must not auto-confirm")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user