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)