Files
opsxdb/server/internal/aitools/policy.go
T
logan 2336c9433c 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
2026-10-15 13:00:40 +08:00

117 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package aitools
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// Mode P0 仅支持 read_only;read_write 留给 P1(写 + 确认)。 // Logan.Li
const (
ModeReadOnly = "read_only"
ModeReadWrite = "read_write" // P1 才启用写路径;P0 加载后仍强制只读执行
)
// Policy ~/.opsxdb/mcp.json
type Policy struct {
AllowedConnectionIDs []string `json:"allowed_connection_ids"`
Mode string `json:"mode"` // read_only | read_write
DefaultLimit int `json:"default_limit,omitempty"`
MaxLimit int `json:"max_limit,omitempty"`
}
func DefaultPolicy() Policy {
return Policy{
AllowedConnectionIDs: nil,
Mode: ModeReadOnly,
DefaultLimit: 100,
MaxLimit: 500,
}
}
func opsxdbDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".opsxdb"), nil
}
func PolicyPath() (string, error) {
dir, err := opsxdbDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "mcp.json"), nil
}
// LoadPolicy 读 mcp.json;缺省文件返回 DefaultPolicy(白名单空=拒绝全部连接)。
func LoadPolicy() (Policy, error) {
p := DefaultPolicy()
path, err := PolicyPath()
if err != nil {
return p, err
}
b, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return p, nil
}
return p, err
}
if err := json.Unmarshal(b, &p); err != nil {
return DefaultPolicy(), fmt.Errorf("mcp.json: %w", err)
}
p.Mode = strings.ToLower(strings.TrimSpace(p.Mode))
if p.Mode == "" {
p.Mode = ModeReadOnly
}
if p.DefaultLimit <= 0 {
p.DefaultLimit = 100
}
if p.MaxLimit <= 0 {
p.MaxLimit = 500
}
if p.DefaultLimit > p.MaxLimit {
p.DefaultLimit = p.MaxLimit
}
return p, nil
}
func (p Policy) IsReadOnly() bool {
return p.Mode != ModeReadWrite
}
// WritesEnabled P1:仅当 mcp.json mode=read_write 时允许写路径(仍须确认令牌)。
func (p Policy) WritesEnabled() bool {
return strings.EqualFold(strings.TrimSpace(p.Mode), ModeReadWrite)
}
func (p Policy) AllowConnection(id string) error {
id = strings.TrimSpace(id)
if id == "" {
return fmt.Errorf("connection_id required")
}
if len(p.AllowedConnectionIDs) == 0 {
return fmt.Errorf("connection not allowed: whitelist empty (configure allowed_connection_ids in ~/.opsxdb/mcp.json)")
}
for _, a := range p.AllowedConnectionIDs {
if a == id {
return nil
}
}
return fmt.Errorf("connection not allowed: %s not in allowed_connection_ids", id)
}
func (p Policy) ClampLimit(n int) int {
if n <= 0 {
n = p.DefaultLimit
}
if n > p.MaxLimit {
n = p.MaxLimit
}
return n
}