-
Notifications
You must be signed in to change notification settings - Fork 62
CM-62381-add-session-start-hook #434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import sys | ||
| from typing import Annotated | ||
|
|
||
| import typer | ||
|
|
||
| from cycode.cli.apps.ai_guardrails.consts import AIIDEType | ||
| from cycode.cli.apps.ai_guardrails.scan.claude_config import get_mcp_servers, get_user_email, load_claude_config | ||
| from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload, _extract_from_claude_transcript | ||
| from cycode.cli.apps.ai_guardrails.scan.utils import safe_json_parse | ||
| from cycode.cli.apps.auth.auth_common import get_authorization_info | ||
| from cycode.cli.apps.auth.auth_manager import AuthManager | ||
| from cycode.cli.exceptions.handle_auth_errors import handle_auth_exception | ||
| from cycode.cli.utils.get_api_client import get_ai_security_manager_client | ||
| from cycode.logger import get_logger | ||
|
|
||
| logger = get_logger('AI Guardrails') | ||
|
|
||
|
|
||
| def _build_session_payload(payload: dict, ide: str) -> AIHookPayload: | ||
| """Build an AIHookPayload from a session-start stdin payload.""" | ||
| if ide == AIIDEType.CLAUDE_CODE: | ||
| ide_version, model, _ = _extract_from_claude_transcript(payload.get('transcript_path')) | ||
| claude_config = load_claude_config() | ||
| ide_user_email = get_user_email(claude_config) if claude_config else None | ||
|
|
||
| return AIHookPayload( | ||
| event_name='session_start', | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need - its not used later when creating conversation |
||
| conversation_id=payload.get('session_id'), | ||
| ide_user_email=ide_user_email, | ||
| model=payload.get('model') or model, | ||
| ide_provider=AIIDEType.CLAUDE_CODE.value, | ||
| ide_version=ide_version, | ||
| ) | ||
|
|
||
| # Cursor | ||
| return AIHookPayload( | ||
| event_name='session_start', | ||
| conversation_id=payload.get('conversation_id'), | ||
| ide_user_email=payload.get('user_email'), | ||
| model=payload.get('model'), | ||
| ide_provider=AIIDEType.CURSOR.value, | ||
| ide_version=payload.get('cursor_version'), | ||
| ) | ||
|
|
||
|
|
||
| def session_start_command( | ||
|
Check failure on line 46 in cycode/cli/apps/ai_guardrails/session_start_command.py
|
||
| ctx: typer.Context, | ||
| ide: Annotated[ | ||
| str, | ||
| typer.Option( | ||
| '--ide', | ||
| help='IDE that triggered the session start.', | ||
| hidden=True, | ||
| ), | ||
| ] = AIIDEType.CURSOR.value, | ||
| ) -> None: | ||
| """Handle session start: ensure auth, create conversation, report data flow.""" | ||
| # Step 1: Ensure authentication | ||
| auth_info = get_authorization_info(ctx) | ||
| if auth_info is None: | ||
| logger.debug('Not authenticated, starting authentication') | ||
| try: | ||
| auth_manager = AuthManager() | ||
| auth_manager.authenticate() | ||
| except Exception as err: | ||
| handle_auth_exception(ctx, err) | ||
| return | ||
| else: | ||
| logger.debug('Already authenticated') | ||
|
|
||
| # Step 2: Read stdin payload (backward compat: old hooks pipe no stdin) | ||
| if sys.stdin.isatty(): | ||
| logger.debug('No stdin payload (TTY), skipping session initialization') | ||
| return | ||
|
|
||
|
Comment on lines
+71
to
+75
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you explain |
||
| stdin_data = sys.stdin.read().strip() | ||
| payload = safe_json_parse(stdin_data) | ||
| if not payload: | ||
| logger.debug('Empty or invalid stdin payload, skipping session initialization') | ||
| return | ||
|
|
||
| # Step 3: Build session payload and initialize API client | ||
| session_payload = _build_session_payload(payload, ide) | ||
|
|
||
| try: | ||
| ai_client = get_ai_security_manager_client(ctx) | ||
| except Exception as e: | ||
| logger.debug('Failed to initialize AI security client', exc_info=e) | ||
| return | ||
|
|
||
| # Step 4: Create conversation | ||
| try: | ||
| ai_client.create_conversation(session_payload) | ||
| except Exception as e: | ||
| logger.debug('Failed to create conversation during session start', exc_info=e) | ||
|
|
||
| # Step 5: Report data flow (MCP servers, Claude Code only) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should handle cursor too, also extract to func |
||
| if ide == AIIDEType.CLAUDE_CODE: | ||
| claude_config = load_claude_config() | ||
| if claude_config: | ||
| mcp_servers = get_mcp_servers(claude_config) | ||
| if mcp_servers: | ||
| try: | ||
| ai_client.report_data_flow(mcp_servers) | ||
| except Exception as e: | ||
| logger.debug('Failed to report MCP servers', exc_info=e) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ class AISecurityManagerClient: | |
|
|
||
| _CONVERSATIONS_PATH = 'v4/ai-security/interactions/conversations' | ||
| _EVENTS_PATH = 'v4/ai-security/interactions/events' | ||
| _DATA_FLOW_PATH = 'v4/ai-security/interactions/data-flow' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont know if i like that route name |
||
|
|
||
| def __init__(self, client: CycodeClientBase, service_config: 'AISecurityManagerServiceConfigBase') -> None: | ||
| self.client = client | ||
|
|
@@ -88,3 +89,15 @@ def create_event( | |
| except Exception as e: | ||
| logger.debug('Failed to create AI hook event', exc_info=e) | ||
| # Don't fail the hook if tracking fails | ||
|
|
||
| def report_data_flow(self, mcp_servers: Optional[dict] = None) -> None: | ||
| """Report session data flow to the backend.""" | ||
| body: dict = { | ||
| 'mcp_servers': mcp_servers, | ||
| } | ||
|
|
||
| try: | ||
| self.client.post(self._build_endpoint_path(self._DATA_FLOW_PATH), body=body) | ||
| except Exception as e: | ||
| logger.debug('Failed to report data flow', exc_info=e) | ||
| # Don't fail the session if reporting fails | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably get rid of _extract_from_claude_transcript now, i created it because other hooks didn't provide the model, as far as i know session start provides the model in stdin
Regarding ide_version, do we have it in stdin / claude.json file?