Created
February 24, 2026 10:21
-
-
Save ferraridavide/07e1e20ae4a644814997af41d6596816 to your computer and use it in GitHub Desktop.
Parse chat export from GitHub Copilot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import json | |
| from typing import Dict, Any, List, Optional | |
| def get_invocation_message(item: Dict[str, Any]) -> Optional[str]: | |
| """Safely extracts invocation message whether it's a string or dict.""" | |
| invocation_msg_data = item.get("invocationMessage") | |
| if not invocation_msg_data: | |
| return None | |
| if isinstance(invocation_msg_data, str): | |
| return invocation_msg_data | |
| return invocation_msg_data.get("value") | |
| def process_chat_data(filepath: str) -> None: | |
| """Loads and processes chat response data from a JSON file.""" | |
| try: | |
| with open(filepath, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| except (FileNotFoundError, json.JSONDecodeError) as e: | |
| print(f"Error loading '{filepath}': {e}") | |
| return | |
| # Safely navigate the JSON tree | |
| requests = data.get("requests", []) | |
| if not requests: | |
| return | |
| response = requests[0].get("response", []) | |
| for item in response: | |
| kind = item.get("kind") | |
| if not kind: | |
| handle_raw_text(item) | |
| continue | |
| match kind: | |
| case "thinking": | |
| handle_thinking(item) | |
| case "toolInvocationSerialized": | |
| handle_tool_invocation(item, response) | |
| case "mcpServersStarting" | "undoStop" | "progressTaskSerialized" | "codeblockUri" | "textEditGroup" | "inlineReference": | |
| continue | |
| case _: | |
| print(item) | |
| def handle_raw_text(item: Dict[str, Any]) -> None: | |
| """Handles items that do not have a specific 'kind' categorization.""" | |
| value = item.get("value") | |
| if value and value.strip() not in ("", "```"): | |
| print(value) | |
| def handle_thinking(item: Dict[str, Any]) -> None: | |
| """Formats and prints the thinking block.""" | |
| value = item.get("value") | |
| if value and value.strip(): | |
| print(f"<thinking>\n{value}\n</thinking>") | |
| def handle_tool_invocation(item: Dict[str, Any], full_response: List[Dict[str, Any]]) -> None: | |
| """Routes different tool invocations to their specific handlers.""" | |
| tool_data = item.get("toolSpecificData") | |
| if not tool_data: | |
| invocation_msg = get_invocation_message(item) | |
| if invocation_msg: | |
| print(f"<toolCall>\n{invocation_msg}\n</toolCall>") | |
| return | |
| tool_kind = tool_data.get("kind") | |
| match tool_kind: | |
| case "todoList": | |
| handle_todo_list(tool_data.get("todoList")) | |
| case "subagent": | |
| handle_subagent(item, full_response) | |
| case "input": | |
| if "subAgentInvocationId" not in item: | |
| invocation_msg = get_invocation_message(item) | |
| if invocation_msg: | |
| print(f"<toolCall>\n{invocation_msg}\n</toolCall>") | |
| case "terminal": | |
| if "subAgentInvocationId" not in item: | |
| cmd = tool_data.get("commandLine", {}).get("original") | |
| if cmd: | |
| print(f"<terminal>\n{cmd}\n</terminal>") | |
| case _: | |
| print(item) | |
| def handle_todo_list(todo_list: Any) -> None: | |
| """Formats and prints the todo list.""" | |
| if not isinstance(todo_list, list): | |
| print(json.dumps(todo_list, indent=2, ensure_ascii=False)) | |
| return | |
| status_icon = { | |
| "not-started": "[ ]", | |
| "in-progress": "[~]", | |
| "completed": "[x]", | |
| } | |
| print("<todoList>") | |
| for todo in todo_list: | |
| title = todo.get("title", "").strip() | |
| status = todo.get("status", "unknown") | |
| icon = status_icon.get(status, "•") | |
| print(f"{icon} {title}") | |
| print("</todoList>") | |
| def handle_subagent(item: Dict[str, Any], full_response: List[Dict[str, Any]]) -> None: | |
| """Processes subagent operations and groups related tool calls.""" | |
| tool_call_id = item.get("toolCallId", "") | |
| tool_data = item.get("toolSpecificData", {}) | |
| print("<subagent>") | |
| prompt = tool_data.get("prompt", "") | |
| if prompt: | |
| print(f"<prompt>\n{prompt}\n</prompt>") | |
| print("<items>") | |
| for related in full_response: | |
| # Only process items linked to this specific subagent | |
| if related.get("subAgentInvocationId") != tool_call_id: | |
| continue | |
| invocation_msg = get_invocation_message(related) | |
| if invocation_msg: | |
| print(f"<toolCall>{invocation_msg}</toolCall>") | |
| related_tool_data = related.get("toolSpecificData", {}) | |
| if related_tool_data.get("kind") == "terminal": | |
| cmd = related_tool_data.get("commandLine", {}).get("original") | |
| if cmd: | |
| print(f"<terminal>\n{cmd}\n</terminal>") | |
| print("</items>") | |
| result = tool_data.get("result", "") | |
| if result: | |
| print(f"<result>\n{result}\n</result>") | |
| print("</subagent>") | |
| process_chat_data("./chat.json") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
copilot has new format for chatsession
jsonltry out this extension https://marketplace.visualstudio.com/manage/publishers/imperium-dev