Files
kandev/apps/backend/internal/plugins/conversation_sanitization.go
T
2026-09-17 10:39:30 +01:00

69 lines
2.7 KiB
Go

package plugins
import (
"github.com/kandev/kandev/internal/sysprompt"
)
//nolint:goconst // These keys are an explicit privacy allowlist.
var conversationMessageMetadataKeys = map[string]struct{}{
"action_details": {}, "action_type": {}, "action_visibility": {},
"actions": {}, "agent_disconnected": {},
"attempt": {}, "attachments": {}, "auth_methods": {}, "auto_start": {},
"base_branch": {}, "context": {}, "context_files": {}, "decision_id": {},
"effective_model": {}, "entity_references": {}, "error_output": {},
"failure_code": {}, "failure_details": {}, "failure_kind": {},
"fallback_model": {}, "has_hidden_prompts": {}, "has_resume_token": {},
"has_review_comments": {}, "is_auth_error": {}, "kind": {}, "max_attempts": {},
"message": {}, "missing_branch": {}, "model_id": {}, "new_branch": {},
"original_branch": {}, "options": {}, "pending_id": {}, "plan_mode": {}, "progress": {},
"provider_name": {}, "question": {}, "question_id": {}, "question_index": {},
"question_total": {}, "recovery_actions": {}, "remediation": {},
"remediation_url": {}, "requested_model": {}, "request_id": {},
"requests_input": {}, "response": {}, "reset_at": {}, "retry_at": {},
"retry_in_seconds": {}, "retrying": {}, "sender_session_id": {}, "sender_session_name": {},
"sender_task_id": {}, "sender_task_title": {}, "stage": {}, "status": {},
"script_type": {}, "agent_name": {}, "command": {}, "exit_code": {},
"is_resuming": {}, "started_at": {}, "completed_at": {}, "error": {},
"task_id": {}, "text": {}, "tool_call_id": {}, "variant": {}, "workflow_message": {},
"workflow_step_color": {}, "workflow_step_id": {}, "workflow_step_name": {},
}
// SanitizeConversationMessageMetadata returns the presentation metadata that
// the plugin conversation stream may expose. Internal agent and tool metadata
// is excluded by the allowlist.
func SanitizeConversationMessageMetadata(source map[string]any) map[string]any {
if len(source) == 0 {
return nil
}
target := make(map[string]any)
for key := range conversationMessageMetadataKeys {
value, exists := source[key]
if !exists || value == nil {
continue
}
target[key] = sanitizeConversationMetadataValue(value)
}
return target
}
func sanitizeConversationMetadataValue(value any) any {
switch typed := value.(type) {
case string:
return sysprompt.StripSystemContent(typed)
case map[string]any:
target := make(map[string]any, len(typed))
for key, nested := range typed {
target[key] = sanitizeConversationMetadataValue(nested)
}
return target
case []any:
target := make([]any, len(typed))
for index, nested := range typed {
target[index] = sanitizeConversationMetadataValue(nested)
}
return target
default:
return value
}
}