From c65e8ab0b5245edc498d52d6230db65c2e0c2f10 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 05:56:48 -0800 Subject: [PATCH] Fix Python cookbook recipes to use correct async SDK API All 5 Python recipes and their markdown docs used a synchronous, kwargs-based API that doesn't match the real github-copilot-sdk: - client.start() -> await client.start() (all methods are async) - create_session(model=...) -> create_session(SessionConfig(model=...)) - session.send(prompt=...) -> session.send(MessageOptions(prompt=...)) - session.wait_for_idle() -> session.send_and_wait() (wait_for_idle doesn't exist) - event['type']/event['data']['content'] -> event.type/event.data.content - All code wrapped in async def main() + asyncio.run(main()) Verified all imports resolve against github-copilot-sdk. --- cookbook/copilot-sdk/python/error-handling.md | 114 ++++++------------ .../python/managing-local-files.md | 63 ++++++---- .../copilot-sdk/python/multiple-sessions.md | 55 +++++---- .../copilot-sdk/python/persisting-sessions.md | 53 ++++---- .../copilot-sdk/python/pr-visualization.md | 51 ++++---- .../python/recipe/error_handling.py | 37 +++--- .../python/recipe/managing_local_files.py | 58 +++++---- .../python/recipe/multiple_sessions.py | 53 ++++---- .../python/recipe/persisting_sessions.py | 55 +++++---- .../python/recipe/pr_visualization.py | 49 +++++--- 10 files changed, 304 insertions(+), 284 deletions(-) diff --git a/cookbook/copilot-sdk/python/error-handling.md b/cookbook/copilot-sdk/python/error-handling.md index cdd73cbc..dfdd02b9 100644 --- a/cookbook/copilot-sdk/python/error-handling.md +++ b/cookbook/copilot-sdk/python/error-handling.md @@ -16,41 +16,36 @@ You need to handle various error conditions like connection failures, timeouts, ## Basic try-except ```python -from copilot import CopilotClient +import asyncio +from copilot import CopilotClient, SessionConfig, MessageOptions -client = CopilotClient() +async def main(): + client = CopilotClient() -try: - client.start() - session = client.create_session(model="gpt-5") + try: + await client.start() + session = await client.create_session(SessionConfig(model="gpt-5")) - response = None - def handle_message(event): - nonlocal response - if event["type"] == "assistant.message": - response = event["data"]["content"] + response = await session.send_and_wait(MessageOptions(prompt="Hello!")) - session.on(handle_message) - session.send(prompt="Hello!") - session.wait_for_idle() + if response: + print(response.data.content) - if response: - print(response) + await session.destroy() + except Exception as e: + print(f"Error: {e}") + finally: + await client.stop() - session.destroy() -except Exception as e: - print(f"Error: {e}") -finally: - client.stop() +if __name__ == "__main__": + asyncio.run(main()) ``` ## Handling specific error types ```python -import subprocess - try: - client.start() + await client.start() except FileNotFoundError: print("Copilot CLI not found. Please install it first.") except ConnectionError: @@ -62,31 +57,14 @@ except Exception as e: ## Timeout handling ```python -import signal -from contextlib import contextmanager - -@contextmanager -def timeout(seconds): - def timeout_handler(signum, frame): - raise TimeoutError("Request timed out") - - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(seconds) - try: - yield - finally: - signal.alarm(0) - signal.signal(signal.SIGALRM, old_handler) - -session = client.create_session(model="gpt-5") +session = await client.create_session(SessionConfig(model="gpt-5")) try: - session.send(prompt="Complex question...") - - # Wait with timeout (30 seconds) - with timeout(30): - session.wait_for_idle() - + # send_and_wait accepts an optional timeout in seconds + response = await session.send_and_wait( + MessageOptions(prompt="Complex question..."), + timeout=30.0 + ) print("Response received") except TimeoutError: print("Request timed out") @@ -95,21 +73,15 @@ except TimeoutError: ## Aborting a request ```python -import threading +session = await client.create_session(SessionConfig(model="gpt-5")) -session = client.create_session(model="gpt-5") - -# Start a request -session.send(prompt="Write a very long story...") +# Start a request (non-blocking send) +await session.send(MessageOptions(prompt="Write a very long story...")) # Abort it after some condition -def abort_later(): - import time - time.sleep(5) - session.abort() - print("Request aborted") - -threading.Thread(target=abort_later).start() +await asyncio.sleep(5) +await session.abort() +print("Request aborted") ``` ## Graceful shutdown @@ -120,31 +92,19 @@ import sys def signal_handler(sig, frame): print("\nShutting down...") - errors = client.stop() - if errors: - print(f"Cleanup errors: {errors}") + try: + loop = asyncio.get_running_loop() + loop.create_task(client.stop()) + except RuntimeError: + asyncio.run(client.stop()) sys.exit(0) signal.signal(signal.SIGINT, signal_handler) ``` -## Context manager for automatic cleanup - -```python -from copilot import CopilotClient - -with CopilotClient() as client: - client.start() - session = client.create_session(model="gpt-5") - - # ... do work ... - - # client.stop() is automatically called when exiting context -``` - ## Best practices -1. **Always clean up**: Use try-finally or context managers to ensure `stop()` is called +1. **Always clean up**: Use try-finally to ensure `await client.stop()` is called 2. **Handle connection errors**: The CLI might not be installed or running -3. **Set appropriate timeouts**: Long-running requests should have timeouts +3. **Set appropriate timeouts**: Use the `timeout` parameter on `send_and_wait()` 4. **Log errors**: Capture error details for debugging diff --git a/cookbook/copilot-sdk/python/managing-local-files.md b/cookbook/copilot-sdk/python/managing-local-files.md index c81a831e..a9e4e35f 100644 --- a/cookbook/copilot-sdk/python/managing-local-files.md +++ b/cookbook/copilot-sdk/python/managing-local-files.md @@ -16,31 +16,40 @@ You have a folder with many files and want to organize them into subfolders base ## Example code ```python -from copilot import CopilotClient +import asyncio import os +from copilot import ( + CopilotClient, SessionConfig, MessageOptions, + SessionEvent, SessionEventType, +) -# Create and start client -client = CopilotClient() -client.start() +async def main(): + # Create and start client + client = CopilotClient() + await client.start() -# Create session -session = client.create_session(model="gpt-5") + # Create session + session = await client.create_session(SessionConfig(model="gpt-5")) -# Event handler -def handle_event(event): - if event["type"] == "assistant.message": - print(f"\nCopilot: {event['data']['content']}") - elif event["type"] == "tool.execution_start": - print(f" → Running: {event['data']['toolName']}") - elif event["type"] == "tool.execution_complete": - print(f" āœ“ Completed: {event['data']['toolCallId']}") + done = asyncio.Event() -session.on(handle_event) + # Event handler + def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE: + print(f"\nCopilot: {event.data.content}") + elif event.type == SessionEventType.TOOL_EXECUTION_START: + print(f" → Running: {event.data.tool_name}") + elif event.type == SessionEventType.TOOL_EXECUTION_COMPLETE: + print(f" āœ“ Completed: {event.data.tool_call_id}") + elif event.type.value == "session.idle": + done.set() -# Ask Copilot to organize files -target_folder = os.path.expanduser("~/Downloads") + session.on(handle_event) -session.send(prompt=f""" + # Ask Copilot to organize files + target_folder = os.path.expanduser("~/Downloads") + + await session.send(MessageOptions(prompt=f""" Analyze the files in "{target_folder}" and organize them into subfolders. 1. First, list all files and their metadata @@ -49,11 +58,15 @@ Analyze the files in "{target_folder}" and organize them into subfolders. 4. Move each file to its appropriate subfolder Please confirm before moving any files. -""") +""")) -session.wait_for_idle() + await done.wait() -client.stop() + await session.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main()) ``` ## Grouping strategies @@ -90,10 +103,10 @@ client.stop() For safety, you can ask Copilot to only preview changes: ```python -session.send(prompt=f""" +await session.send(MessageOptions(prompt=f""" Analyze files in "{target_folder}" and show me how you would organize them by file type. DO NOT move any files - just show me the plan. -""") +""")) ``` ## Custom grouping with AI analysis @@ -101,7 +114,7 @@ by file type. DO NOT move any files - just show me the plan. Let Copilot determine the best grouping based on file content: ```python -session.send(prompt=f""" +await session.send(MessageOptions(prompt=f""" Look at the files in "{target_folder}" and suggest a logical organization. Consider: - File names and what they might contain @@ -109,7 +122,7 @@ Consider: - Date patterns that might indicate projects or events Propose folder names that are descriptive and useful. -""") +""")) ``` ## Safety considerations diff --git a/cookbook/copilot-sdk/python/multiple-sessions.md b/cookbook/copilot-sdk/python/multiple-sessions.md index 4baa0f47..0efa3ed8 100644 --- a/cookbook/copilot-sdk/python/multiple-sessions.md +++ b/cookbook/copilot-sdk/python/multiple-sessions.md @@ -16,31 +16,36 @@ You need to run multiple conversations in parallel, each with its own context an ## Python ```python -from copilot import CopilotClient +import asyncio +from copilot import CopilotClient, SessionConfig, MessageOptions -client = CopilotClient() -client.start() +async def main(): + client = CopilotClient() + await client.start() -# Create multiple independent sessions -session1 = client.create_session(model="gpt-5") -session2 = client.create_session(model="gpt-5") -session3 = client.create_session(model="claude-sonnet-4.5") + # Create multiple independent sessions + session1 = await client.create_session(SessionConfig(model="gpt-5")) + session2 = await client.create_session(SessionConfig(model="gpt-5")) + session3 = await client.create_session(SessionConfig(model="claude-sonnet-4.5")) -# Each session maintains its own conversation history -session1.send(prompt="You are helping with a Python project") -session2.send(prompt="You are helping with a TypeScript project") -session3.send(prompt="You are helping with a Go project") + # Each session maintains its own conversation history + await session1.send(MessageOptions(prompt="You are helping with a Python project")) + await session2.send(MessageOptions(prompt="You are helping with a TypeScript project")) + await session3.send(MessageOptions(prompt="You are helping with a Go project")) -# Follow-up messages stay in their respective contexts -session1.send(prompt="How do I create a virtual environment?") -session2.send(prompt="How do I set up tsconfig?") -session3.send(prompt="How do I initialize a module?") + # Follow-up messages stay in their respective contexts + await session1.send(MessageOptions(prompt="How do I create a virtual environment?")) + await session2.send(MessageOptions(prompt="How do I set up tsconfig?")) + await session3.send(MessageOptions(prompt="How do I initialize a module?")) -# Clean up all sessions -session1.destroy() -session2.destroy() -session3.destroy() -client.stop() + # Clean up all sessions + await session1.destroy() + await session2.destroy() + await session3.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main()) ``` ## Custom session IDs @@ -48,10 +53,10 @@ client.stop() Use custom IDs for easier tracking: ```python -session = client.create_session( +session = await client.create_session(SessionConfig( session_id="user-123-chat", model="gpt-5" -) +)) print(session.session_id) # "user-123-chat" ``` @@ -59,16 +64,16 @@ print(session.session_id) # "user-123-chat" ## Listing sessions ```python -sessions = client.list_sessions() +sessions = await client.list_sessions() for session_info in sessions: - print(f"Session: {session_info['sessionId']}") + print(f"Session: {session_info.session_id}") ``` ## Deleting sessions ```python # Delete a specific session -client.delete_session("user-123-chat") +await client.delete_session("user-123-chat") ``` ## Use cases diff --git a/cookbook/copilot-sdk/python/persisting-sessions.md b/cookbook/copilot-sdk/python/persisting-sessions.md index 5d07a469..cc77407c 100644 --- a/cookbook/copilot-sdk/python/persisting-sessions.md +++ b/cookbook/copilot-sdk/python/persisting-sessions.md @@ -16,64 +16,69 @@ You want users to be able to continue a conversation even after closing and reop ### Creating a session with a custom ID ```python -from copilot import CopilotClient +import asyncio +from copilot import CopilotClient, SessionConfig, MessageOptions -client = CopilotClient() -client.start() +async def main(): + client = CopilotClient() + await client.start() -# Create session with a memorable ID -session = client.create_session( - session_id="user-123-conversation", - model="gpt-5", -) + # Create session with a memorable ID + session = await client.create_session(SessionConfig( + session_id="user-123-conversation", + model="gpt-5", + )) -session.send(prompt="Let's discuss TypeScript generics") + await session.send_and_wait(MessageOptions(prompt="Let's discuss TypeScript generics")) -# Session ID is preserved -print(session.session_id) # "user-123-conversation" + # Session ID is preserved + print(session.session_id) # "user-123-conversation" -# Destroy session but keep data on disk -session.destroy() -client.stop() + # Destroy session but keep data on disk + await session.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main()) ``` ### Resuming a session ```python client = CopilotClient() -client.start() +await client.start() # Resume the previous session -session = client.resume_session("user-123-conversation") +session = await client.resume_session("user-123-conversation") # Previous context is restored -session.send(prompt="What were we discussing?") +await session.send_and_wait(MessageOptions(prompt="What were we discussing?")) -session.destroy() -client.stop() +await session.destroy() +await client.stop() ``` ### Listing available sessions ```python -sessions = client.list_sessions() +sessions = await client.list_sessions() for s in sessions: - print("Session:", s["sessionId"]) + print("Session:", s.session_id) ``` ### Deleting a session permanently ```python # Remove session and all its data from disk -client.delete_session("user-123-conversation") +await client.delete_session("user-123-conversation") ``` ### Getting session history ```python -messages = session.get_messages() +messages = await session.get_messages() for msg in messages: - print(f"[{msg['type']}] {msg['data']}") + print(f"[{msg.type}] {msg.data.content}") ``` ## Best practices diff --git a/cookbook/copilot-sdk/python/pr-visualization.md b/cookbook/copilot-sdk/python/pr-visualization.md index 0419aed1..0b158e4a 100644 --- a/cookbook/copilot-sdk/python/pr-visualization.md +++ b/cookbook/copilot-sdk/python/pr-visualization.md @@ -38,10 +38,15 @@ python pr_visualization.py --repo github/copilot-sdk ```python #!/usr/bin/env python3 +import asyncio import subprocess import sys import os -from copilot import CopilotClient +import re +from copilot import ( + CopilotClient, SessionConfig, MessageOptions, + SessionEvent, SessionEventType, +) # ============================================================================ # Git & GitHub Detection @@ -69,7 +74,6 @@ def get_github_remote(): remote_url = result.stdout.strip() # Handle SSH: git@github.com:owner/repo.git - import re ssh_match = re.search(r"git@github\.com:(.+/.+?)(?:\.git)?$", remote_url) if ssh_match: return ssh_match.group(1) @@ -98,7 +102,7 @@ def prompt_for_repo(): # Main Application # ============================================================================ -def main(): +async def main(): print("šŸ” PR Age Chart Generator\n") # Determine the repository @@ -126,11 +130,11 @@ def main(): owner, repo_name = repo.split("/", 1) - # Create Copilot client - no custom tools needed! - client = CopilotClient(log_level="error") - client.start() + # Create Copilot client + client = CopilotClient() + await client.start() - session = client.create_session( + session = await client.create_session(SessionConfig( model="gpt-5", system_message={ "content": f""" @@ -147,30 +151,34 @@ The current working directory is: {os.getcwd()} """ } - ) + )) + + done = asyncio.Event() # Set up event handling - def handle_event(event): - if event["type"] == "assistant.message": - print(f"\nšŸ¤– {event['data']['content']}\n") - elif event["type"] == "tool.execution_start": - print(f" āš™ļø {event['data']['toolName']}") + def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE: + print(f"\nšŸ¤– {event.data.content}\n") + elif event.type == SessionEventType.TOOL_EXECUTION_START: + print(f" āš™ļø {event.data.tool_name}") + elif event.type.value == "session.idle": + done.set() session.on(handle_event) # Initial prompt - let Copilot figure out the details print("\nšŸ“Š Starting analysis...\n") - session.send(prompt=f""" + await session.send(MessageOptions(prompt=f""" Fetch the open pull requests for {owner}/{repo_name} from the last week. Calculate the age of each PR in days. Then generate a bar chart image showing the distribution of PR ages (group them into sensible buckets like <1 day, 1-3 days, etc.). Save the chart as "pr-age-chart.png" in the current directory. Finally, summarize the PR health - average age, oldest PR, and how many might be considered stale. - """) + """)) - session.wait_for_idle() + await done.wait() # Interactive loop print("\nšŸ’” Ask follow-up questions or type \"exit\" to quit.\n") @@ -189,14 +197,15 @@ The current working directory is: {os.getcwd()} break if user_input: - session.send(prompt=user_input) - session.wait_for_idle() + done.clear() + await session.send(MessageOptions(prompt=user_input)) + await done.wait() - session.destroy() - client.stop() + await session.destroy() + await client.stop() if __name__ == "__main__": - main() + asyncio.run(main()) ``` ## How it works diff --git a/cookbook/copilot-sdk/python/recipe/error_handling.py b/cookbook/copilot-sdk/python/recipe/error_handling.py index b76b29ce..7933cbba 100644 --- a/cookbook/copilot-sdk/python/recipe/error_handling.py +++ b/cookbook/copilot-sdk/python/recipe/error_handling.py @@ -1,28 +1,25 @@ #!/usr/bin/env python3 -from copilot import CopilotClient +import asyncio +from copilot import CopilotClient, SessionConfig, MessageOptions -client = CopilotClient() +async def main(): + client = CopilotClient() -try: - client.start() - session = client.create_session(model="gpt-5") + try: + await client.start() + session = await client.create_session(SessionConfig(model="gpt-5")) - response = None - def handle_message(event): - nonlocal response - if event["type"] == "assistant.message": - response = event["data"]["content"] + response = await session.send_and_wait(MessageOptions(prompt="Hello!")) - session.on(handle_message) - session.send(prompt="Hello!") - session.wait_for_idle() + if response: + print(response.data.content) - if response: - print(response) + await session.destroy() + except Exception as e: + print(f"Error: {e}") + finally: + await client.stop() - session.destroy() -except Exception as e: - print(f"Error: {e}") -finally: - client.stop() +if __name__ == "__main__": + asyncio.run(main()) diff --git a/cookbook/copilot-sdk/python/recipe/managing_local_files.py b/cookbook/copilot-sdk/python/recipe/managing_local_files.py index 8b9f94dc..c0381170 100644 --- a/cookbook/copilot-sdk/python/recipe/managing_local_files.py +++ b/cookbook/copilot-sdk/python/recipe/managing_local_files.py @@ -1,31 +1,40 @@ #!/usr/bin/env python3 -from copilot import CopilotClient +import asyncio import os +from copilot import ( + CopilotClient, SessionConfig, MessageOptions, + SessionEvent, SessionEventType, +) -# Create and start client -client = CopilotClient() -client.start() +async def main(): + # Create and start client + client = CopilotClient() + await client.start() -# Create session -session = client.create_session(model="gpt-5") + # Create session + session = await client.create_session(SessionConfig(model="gpt-5")) -# Event handler -def handle_event(event): - if event["type"] == "assistant.message": - print(f"\nCopilot: {event['data']['content']}") - elif event["type"] == "tool.execution_start": - print(f" → Running: {event['data']['toolName']}") - elif event["type"] == "tool.execution_complete": - print(f" āœ“ Completed: {event['data']['toolCallId']}") + done = asyncio.Event() -session.on(handle_event) + # Event handler + def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE: + print(f"\nCopilot: {event.data.content}") + elif event.type == SessionEventType.TOOL_EXECUTION_START: + print(f" → Running: {event.data.tool_name}") + elif event.type == SessionEventType.TOOL_EXECUTION_COMPLETE: + print(f" āœ“ Completed: {event.data.tool_call_id}") + elif event.type.value == "session.idle": + done.set() -# Ask Copilot to organize files -# Change this to your target folder -target_folder = os.path.expanduser("~/Downloads") + session.on(handle_event) -session.send(prompt=f""" + # Ask Copilot to organize files + # Change this to your target folder + target_folder = os.path.expanduser("~/Downloads") + + await session.send(MessageOptions(prompt=f""" Analyze the files in "{target_folder}" and organize them into subfolders. 1. First, list all files and their metadata @@ -34,9 +43,12 @@ Analyze the files in "{target_folder}" and organize them into subfolders. 4. Move each file to its appropriate subfolder Please confirm before moving any files. -""") +""")) -session.wait_for_idle() + await done.wait() -session.destroy() -client.stop() + await session.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/cookbook/copilot-sdk/python/recipe/multiple_sessions.py b/cookbook/copilot-sdk/python/recipe/multiple_sessions.py index dd4f299c..8d7d35d1 100644 --- a/cookbook/copilot-sdk/python/recipe/multiple_sessions.py +++ b/cookbook/copilot-sdk/python/recipe/multiple_sessions.py @@ -1,35 +1,40 @@ #!/usr/bin/env python3 -from copilot import CopilotClient +import asyncio +from copilot import CopilotClient, SessionConfig, MessageOptions -client = CopilotClient() -client.start() +async def main(): + client = CopilotClient() + await client.start() -# Create multiple independent sessions -session1 = client.create_session(model="gpt-5") -session2 = client.create_session(model="gpt-5") -session3 = client.create_session(model="claude-sonnet-4.5") + # Create multiple independent sessions + session1 = await client.create_session(SessionConfig(model="gpt-5")) + session2 = await client.create_session(SessionConfig(model="gpt-5")) + session3 = await client.create_session(SessionConfig(model="claude-sonnet-4.5")) -print("Created 3 independent sessions") + print("Created 3 independent sessions") -# Each session maintains its own conversation history -session1.send(prompt="You are helping with a Python project") -session2.send(prompt="You are helping with a TypeScript project") -session3.send(prompt="You are helping with a Go project") + # Each session maintains its own conversation history + await session1.send(MessageOptions(prompt="You are helping with a Python project")) + await session2.send(MessageOptions(prompt="You are helping with a TypeScript project")) + await session3.send(MessageOptions(prompt="You are helping with a Go project")) -print("Sent initial context to all sessions") + print("Sent initial context to all sessions") -# Follow-up messages stay in their respective contexts -session1.send(prompt="How do I create a virtual environment?") -session2.send(prompt="How do I set up tsconfig?") -session3.send(prompt="How do I initialize a module?") + # Follow-up messages stay in their respective contexts + await session1.send(MessageOptions(prompt="How do I create a virtual environment?")) + await session2.send(MessageOptions(prompt="How do I set up tsconfig?")) + await session3.send(MessageOptions(prompt="How do I initialize a module?")) -print("Sent follow-up questions to each session") + print("Sent follow-up questions to each session") -# Clean up all sessions -session1.destroy() -session2.destroy() -session3.destroy() -client.stop() + # Clean up all sessions + await session1.destroy() + await session2.destroy() + await session3.destroy() + await client.stop() -print("All sessions destroyed successfully") + print("All sessions destroyed successfully") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/cookbook/copilot-sdk/python/recipe/persisting_sessions.py b/cookbook/copilot-sdk/python/recipe/persisting_sessions.py index b3d97f2f..da668070 100644 --- a/cookbook/copilot-sdk/python/recipe/persisting_sessions.py +++ b/cookbook/copilot-sdk/python/recipe/persisting_sessions.py @@ -1,36 +1,41 @@ #!/usr/bin/env python3 -from copilot import CopilotClient +import asyncio +from copilot import CopilotClient, SessionConfig, MessageOptions -client = CopilotClient() -client.start() +async def main(): + client = CopilotClient() + await client.start() -# Create session with a memorable ID -session = client.create_session( - session_id="user-123-conversation", - model="gpt-5", -) + # Create session with a memorable ID + session = await client.create_session(SessionConfig( + session_id="user-123-conversation", + model="gpt-5", + )) -session.send(prompt="Let's discuss TypeScript generics") -print(f"Session created: {session.session_id}") + await session.send_and_wait(MessageOptions(prompt="Let's discuss TypeScript generics")) + print(f"Session created: {session.session_id}") -# Destroy session but keep data on disk -session.destroy() -print("Session destroyed (state persisted)") + # Destroy session but keep data on disk + await session.destroy() + print("Session destroyed (state persisted)") -# Resume the previous session -resumed = client.resume_session("user-123-conversation") -print(f"Resumed: {resumed.session_id}") + # Resume the previous session + resumed = await client.resume_session("user-123-conversation") + print(f"Resumed: {resumed.session_id}") -resumed.send(prompt="What were we discussing?") + await resumed.send_and_wait(MessageOptions(prompt="What were we discussing?")) -# List sessions -sessions = client.list_sessions() -print("Sessions:", [s["sessionId"] for s in sessions]) + # List sessions + sessions = await client.list_sessions() + print("Sessions:", [s.session_id for s in sessions]) -# Delete session permanently -client.delete_session("user-123-conversation") -print("Session deleted") + # Delete session permanently + await client.delete_session("user-123-conversation") + print("Session deleted") -resumed.destroy() -client.stop() + await resumed.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/cookbook/copilot-sdk/python/recipe/pr_visualization.py b/cookbook/copilot-sdk/python/recipe/pr_visualization.py index 6be73dfd..9ece3f93 100644 --- a/cookbook/copilot-sdk/python/recipe/pr_visualization.py +++ b/cookbook/copilot-sdk/python/recipe/pr_visualization.py @@ -1,10 +1,14 @@ #!/usr/bin/env python3 +import asyncio import subprocess import sys import os import re -from copilot import CopilotClient +from copilot import ( + CopilotClient, SessionConfig, MessageOptions, + SessionEvent, SessionEventType, +) # ============================================================================ # Git & GitHub Detection @@ -60,7 +64,7 @@ def prompt_for_repo(): # Main Application # ============================================================================ -def main(): +async def main(): print("šŸ” PR Age Chart Generator\n") # Determine the repository @@ -88,11 +92,11 @@ def main(): owner, repo_name = repo.split("/", 1) - # Create Copilot client - no custom tools needed! - client = CopilotClient(log_level="error") - client.start() + # Create Copilot client + client = CopilotClient() + await client.start() - session = client.create_session( + session = await client.create_session(SessionConfig( model="gpt-5", system_message={ "content": f""" @@ -109,30 +113,34 @@ The current working directory is: {os.getcwd()} """ } - ) + )) + + done = asyncio.Event() # Set up event handling - def handle_event(event): - if event["type"] == "assistant.message": - print(f"\nšŸ¤– {event['data']['content']}\n") - elif event["type"] == "tool.execution_start": - print(f" āš™ļø {event['data']['toolName']}") + def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE: + print(f"\nšŸ¤– {event.data.content}\n") + elif event.type == SessionEventType.TOOL_EXECUTION_START: + print(f" āš™ļø {event.data.tool_name}") + elif event.type.value == "session.idle": + done.set() session.on(handle_event) # Initial prompt - let Copilot figure out the details print("\nšŸ“Š Starting analysis...\n") - session.send(prompt=f""" + await session.send(MessageOptions(prompt=f""" Fetch the open pull requests for {owner}/{repo_name} from the last week. Calculate the age of each PR in days. Then generate a bar chart image showing the distribution of PR ages (group them into sensible buckets like <1 day, 1-3 days, etc.). Save the chart as "pr-age-chart.png" in the current directory. Finally, summarize the PR health - average age, oldest PR, and how many might be considered stale. - """) + """)) - session.wait_for_idle() + await done.wait() # Interactive loop print("\nšŸ’” Ask follow-up questions or type \"exit\" to quit.\n") @@ -151,11 +159,12 @@ The current working directory is: {os.getcwd()} break if user_input: - session.send(prompt=user_input) - session.wait_for_idle() + done.clear() + await session.send(MessageOptions(prompt=user_input)) + await done.wait() - session.destroy() - client.stop() + await session.destroy() + await client.stop() if __name__ == "__main__": - main() + asyncio.run(main())