feat: add secure agent and MCP extensions
This commit is contained in:
161
backend/apps/tools/models.py
Normal file
161
backend/apps/tools/models.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
class ToolProviderType(models.TextChoices):
|
||||
NATIVE = "native", "原生工具"
|
||||
MCP_HTTP = "mcp_http", "HTTP MCP"
|
||||
|
||||
|
||||
class ToolRiskLevel(models.TextChoices):
|
||||
READ_ONLY = "read_only", "只读"
|
||||
LOW_WRITE = "low_write", "低风险写"
|
||||
HIGH_RISK = "high_risk", "高风险"
|
||||
|
||||
|
||||
class ToolProvider(models.Model):
|
||||
name = models.CharField(max_length=120, unique=True)
|
||||
provider_type = models.CharField(
|
||||
max_length=24,
|
||||
choices=ToolProviderType.choices,
|
||||
default=ToolProviderType.MCP_HTTP,
|
||||
)
|
||||
base_url = models.URLField(max_length=500, blank=True)
|
||||
token_encrypted = models.TextField(blank=True)
|
||||
enabled = models.BooleanField(default=True)
|
||||
timeout_seconds = models.PositiveIntegerField(default=30)
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="created_tool_providers",
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class UnifiedTool(models.Model):
|
||||
code = models.CharField(max_length=160, unique=True)
|
||||
provider = models.ForeignKey(
|
||||
ToolProvider,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="tools",
|
||||
)
|
||||
external_name = models.CharField(max_length=200, blank=True)
|
||||
name = models.CharField(max_length=160)
|
||||
description = models.TextField(blank=True)
|
||||
input_schema = models.JSONField(default=dict)
|
||||
risk_level = models.CharField(
|
||||
max_length=24,
|
||||
choices=ToolRiskLevel.choices,
|
||||
default=ToolRiskLevel.READ_ONLY,
|
||||
)
|
||||
enabled = models.BooleanField(default=True)
|
||||
synced_at = models.DateTimeField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["code"]
|
||||
|
||||
|
||||
class ToolAuthorization(models.Model):
|
||||
tool = models.ForeignKey(
|
||||
UnifiedTool,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="authorizations",
|
||||
)
|
||||
principal_type = models.CharField(max_length=24)
|
||||
principal_id = models.PositiveBigIntegerField()
|
||||
allowed = models.BooleanField(default=True)
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="created_tool_authorizations",
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["tool", "principal_type", "principal_id"],
|
||||
name="unique_tool_authorization_principal",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class ToolCallStatus(models.TextChoices):
|
||||
PENDING_CONFIRMATION = "pending_confirmation", "待确认"
|
||||
RUNNING = "running", "执行中"
|
||||
SUCCEEDED = "succeeded", "成功"
|
||||
FAILED = "failed", "失败"
|
||||
REJECTED = "rejected", "已拒绝"
|
||||
|
||||
|
||||
class ToolCall(models.Model):
|
||||
public_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
tool = models.ForeignKey(UnifiedTool, on_delete=models.PROTECT, related_name="calls")
|
||||
agent_run = models.ForeignKey(
|
||||
"agents.AgentRun",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="tool_calls",
|
||||
)
|
||||
requested_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="requested_tool_calls",
|
||||
)
|
||||
status = models.CharField(max_length=32, choices=ToolCallStatus.choices)
|
||||
input_payload = models.JSONField(default=dict)
|
||||
output_payload = models.JSONField(null=True, blank=True)
|
||||
authorization_snapshot = models.JSONField(default=dict)
|
||||
risk_snapshot = models.JSONField(default=dict)
|
||||
idempotency_key = models.CharField(max_length=160, blank=True)
|
||||
error_code = models.CharField(max_length=80, blank=True)
|
||||
error_message = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
finished_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["tool", "idempotency_key"],
|
||||
condition=models.Q(idempotency_key__gt=""),
|
||||
name="unique_tool_call_idempotency_key",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class ToolConfirmation(models.Model):
|
||||
call = models.OneToOneField(
|
||||
ToolCall,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="confirmation",
|
||||
)
|
||||
status = models.CharField(max_length=24, default="pending")
|
||||
reason = models.TextField(blank=True)
|
||||
decided_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="decided_tool_confirmations",
|
||||
)
|
||||
decided_at = models.DateTimeField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
Reference in New Issue
Block a user