11#!/usr/bin/env python3
22import asyncio
3+ import json
34import os
45import sys
6+ from datetime import datetime , timezone
57
68from copilot import CopilotClient , RuntimeConnection
79from copilot .session import PermissionHandler
10+ from copilot .session_events import (
11+ AssistantMessageData ,
12+ SessionEventType ,
13+ ToolExecutionCompleteData ,
14+ ToolExecutionStartData ,
15+ )
816
917
1018def read_required_env (name : str ) -> str :
@@ -25,6 +33,16 @@ def extract_assistant_content(message: object) -> str:
2533 return ""
2634
2735
36+ def _format_timestamp (ts : datetime | None = None ) -> str :
37+ now = ts or datetime .now (timezone .utc )
38+ return now .isoformat (timespec = "milliseconds" ).replace ("+00:00" , "Z" )
39+
40+
41+ def write_event (event_type : str , data : dict , timestamp : datetime | None = None ) -> None :
42+ entry = {"type" : event_type , "timestamp" : _format_timestamp (timestamp ), "data" : data }
43+ sys .stderr .write (json .dumps (entry ) + "\n " )
44+
45+
2846async def main () -> int :
2947 prompt_path = read_required_env ("GH_AW_PROMPT" )
3048 sdk_uri = read_required_env ("COPILOT_SDK_URI" )
@@ -43,6 +61,44 @@ async def main() -> int:
4361 session = None
4462 try :
4563 session = await client .create_session (on_permission_request = PermissionHandler .approve_all , model = model )
64+
65+ pending_tool_calls : dict [str , dict [str , str ]] = {}
66+
67+ def handle_event (event ) -> None :
68+ if event .ephemeral :
69+ return
70+
71+ match event .type :
72+ case SessionEventType .USER_MESSAGE :
73+ write_event ("user.message" , {}, event .timestamp )
74+
75+ case SessionEventType .TOOL_EXECUTION_START :
76+ data = event .data
77+ if isinstance (data , ToolExecutionStartData ):
78+ tool_name = data .tool_name or "unknown"
79+ mcp_server_name = data .mcp_server_name or ""
80+ if data .tool_call_id :
81+ pending_tool_calls [data .tool_call_id ] = {"toolName" : tool_name , "mcpServerName" : mcp_server_name }
82+ write_event ("tool.execution_start" , {"toolName" : tool_name , "mcpServerName" : mcp_server_name }, event .timestamp )
83+
84+ case SessionEventType .TOOL_EXECUTION_COMPLETE :
85+ data = event .data
86+ if isinstance (data , ToolExecutionCompleteData ):
87+ pending = pending_tool_calls .pop (data .tool_call_id , None ) if data .tool_call_id else None
88+ tool_name = pending .get ("toolName" ) if pending else None
89+ if not tool_name :
90+ tool_name = data .tool_description .name if data .tool_description else None
91+ tool_name = tool_name or "unknown"
92+ mcp_server_name = pending .get ("mcpServerName" , "" ) if pending else ""
93+ write_event ("tool.execution_complete" , {"toolName" : tool_name , "mcpServerName" : mcp_server_name , "success" : data .success }, event .timestamp )
94+
95+ case SessionEventType .ASSISTANT_MESSAGE :
96+ data = event .data
97+ if isinstance (data , AssistantMessageData ):
98+ write_event ("assistant.message" , {"content" : data .content }, event .timestamp )
99+
100+ session .on (handle_event )
101+
46102 response = await session .send_and_wait (prompt )
47103 content = extract_assistant_content (response )
48104 if content :
0 commit comments