-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathresponse_builders.py
More file actions
135 lines (102 loc) · 4.54 KB
/
response_builders.py
File metadata and controls
135 lines (102 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""
Response builders for different AI IDE hooks.
Each IDE has its own response format for hooks. This module provides
an abstract interface and concrete implementations for each supported IDE.
"""
from abc import ABC, abstractmethod
from cycode.cli.apps.ai_guardrails.consts import AIIDEType
class IDEResponseBuilder(ABC):
"""Abstract base class for IDE-specific response builders."""
@abstractmethod
def allow_permission(self) -> dict:
"""Build response to allow file read or MCP execution."""
@abstractmethod
def deny_permission(self, user_message: str, agent_message: str) -> dict:
"""Build response to deny file read or MCP execution."""
@abstractmethod
def ask_permission(self, user_message: str, agent_message: str) -> dict:
"""Build response to ask user for permission (warn mode)."""
@abstractmethod
def allow_prompt(self) -> dict:
"""Build response to allow prompt submission."""
@abstractmethod
def deny_prompt(self, user_message: str) -> dict:
"""Build response to deny prompt submission."""
class CursorResponseBuilder(IDEResponseBuilder):
"""Response builder for Cursor IDE hooks.
Cursor hook response formats:
- beforeSubmitPrompt: {"continue": bool, "user_message": str}
- beforeReadFile: {"permission": str, "user_message": str, "agent_message": str}
- beforeMCPExecution: {"permission": str, "user_message": str, "agent_message": str}
"""
def allow_permission(self) -> dict:
"""Allow file read or MCP execution."""
return {'permission': 'allow'}
def deny_permission(self, user_message: str, agent_message: str) -> dict:
"""Deny file read or MCP execution."""
return {'permission': 'deny', 'user_message': user_message, 'agent_message': agent_message}
def ask_permission(self, user_message: str, agent_message: str) -> dict:
"""Ask user for permission (warn mode)."""
return {'permission': 'ask', 'user_message': user_message, 'agent_message': agent_message}
def allow_prompt(self) -> dict:
"""Allow prompt submission."""
return {'continue': True}
def deny_prompt(self, user_message: str) -> dict:
"""Deny prompt submission."""
return {'continue': False, 'user_message': user_message}
class ClaudeCodeResponseBuilder(IDEResponseBuilder):
"""Response builder for Claude Code IDE hooks.
Claude Code hook response formats:
- UserPromptSubmit: {} for allow, {"decision": "block", "reason": str} for deny
- PreToolUse: hookSpecificOutput with permissionDecision (allow/deny/ask)
"""
def allow_permission(self) -> dict:
"""Allow file read or MCP execution."""
return {
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'allow',
}
}
def deny_permission(self, user_message: str, agent_message: str) -> dict:
"""Deny file read or MCP execution."""
return {
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'deny',
'permissionDecisionReason': user_message,
}
}
def ask_permission(self, user_message: str, agent_message: str) -> dict:
"""Ask user for permission (warn mode)."""
return {
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'ask',
'permissionDecisionReason': user_message,
}
}
def allow_prompt(self) -> dict:
"""Allow prompt submission (empty response means allow)."""
return {}
def deny_prompt(self, user_message: str) -> dict:
"""Deny prompt submission."""
return {'decision': 'block', 'reason': user_message}
# Registry of response builders by IDE type
_RESPONSE_BUILDERS: dict[str, IDEResponseBuilder] = {
AIIDEType.CURSOR: CursorResponseBuilder(),
AIIDEType.CLAUDE_CODE: ClaudeCodeResponseBuilder(),
}
def get_response_builder(ide: str = AIIDEType.CURSOR.value) -> IDEResponseBuilder:
"""Get the response builder for a specific IDE.
Args:
ide: The IDE name (e.g., 'cursor', 'claude-code') or AIIDEType enum
Returns:
IDEResponseBuilder instance for the specified IDE
Raises:
ValueError: If the IDE is not supported
"""
builder = _RESPONSE_BUILDERS.get(ide.lower())
if not builder:
raise ValueError(f'Unsupported IDE: {ide}. Supported IDEs: {list(_RESPONSE_BUILDERS.keys())}')
return builder