Merge pull request #697 from tonybaloney/fix/python-cookbook-api

Fix Python cookbook recipes to use correct async SDK API
This commit is contained in:
Aaron Powell
2026-02-12 09:41:13 +11:00
committed by GitHub
10 changed files with 304 additions and 284 deletions

View File

@@ -16,41 +16,36 @@ You need to handle various error conditions like connection failures, timeouts,
## Basic try-except ## Basic try-except
```python ```python
from copilot import CopilotClient import asyncio
from copilot import CopilotClient, SessionConfig, MessageOptions
client = CopilotClient() async def main():
client = CopilotClient()
try: try:
client.start() await client.start()
session = client.create_session(model="gpt-5") session = await client.create_session(SessionConfig(model="gpt-5"))
response = None response = await session.send_and_wait(MessageOptions(prompt="Hello!"))
def handle_message(event):
nonlocal response
if event["type"] == "assistant.message":
response = event["data"]["content"]
session.on(handle_message) if response:
session.send(prompt="Hello!") print(response.data.content)
session.wait_for_idle()
if response: await session.destroy()
print(response) except Exception as e:
print(f"Error: {e}")
finally:
await client.stop()
session.destroy() if __name__ == "__main__":
except Exception as e: asyncio.run(main())
print(f"Error: {e}")
finally:
client.stop()
``` ```
## Handling specific error types ## Handling specific error types
```python ```python
import subprocess
try: try:
client.start() await client.start()
except FileNotFoundError: except FileNotFoundError:
print("Copilot CLI not found. Please install it first.") print("Copilot CLI not found. Please install it first.")
except ConnectionError: except ConnectionError:
@@ -62,31 +57,14 @@ except Exception as e:
## Timeout handling ## Timeout handling
```python ```python
import signal session = await client.create_session(SessionConfig(model="gpt-5"))
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")
try: try:
session.send(prompt="Complex question...") # send_and_wait accepts an optional timeout in seconds
response = await session.send_and_wait(
# Wait with timeout (30 seconds) MessageOptions(prompt="Complex question..."),
with timeout(30): timeout=30.0
session.wait_for_idle() )
print("Response received") print("Response received")
except TimeoutError: except TimeoutError:
print("Request timed out") print("Request timed out")
@@ -95,21 +73,15 @@ except TimeoutError:
## Aborting a request ## Aborting a request
```python ```python
import threading session = await client.create_session(SessionConfig(model="gpt-5"))
session = client.create_session(model="gpt-5") # Start a request (non-blocking send)
await session.send(MessageOptions(prompt="Write a very long story..."))
# Start a request
session.send(prompt="Write a very long story...")
# Abort it after some condition # Abort it after some condition
def abort_later(): await asyncio.sleep(5)
import time await session.abort()
time.sleep(5) print("Request aborted")
session.abort()
print("Request aborted")
threading.Thread(target=abort_later).start()
``` ```
## Graceful shutdown ## Graceful shutdown
@@ -120,31 +92,19 @@ import sys
def signal_handler(sig, frame): def signal_handler(sig, frame):
print("\nShutting down...") print("\nShutting down...")
errors = client.stop() try:
if errors: loop = asyncio.get_running_loop()
print(f"Cleanup errors: {errors}") loop.create_task(client.stop())
except RuntimeError:
asyncio.run(client.stop())
sys.exit(0) sys.exit(0)
signal.signal(signal.SIGINT, signal_handler) 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 ## 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 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 4. **Log errors**: Capture error details for debugging

View File

@@ -16,31 +16,40 @@ You have a folder with many files and want to organize them into subfolders base
## Example code ## Example code
```python ```python
from copilot import CopilotClient import asyncio
import os import os
from copilot import (
CopilotClient, SessionConfig, MessageOptions,
SessionEvent, SessionEventType,
)
# Create and start client async def main():
client = CopilotClient() # Create and start client
client.start() client = CopilotClient()
await client.start()
# Create session # Create session
session = client.create_session(model="gpt-5") session = await client.create_session(SessionConfig(model="gpt-5"))
# Event handler done = asyncio.Event()
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']}")
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 session.on(handle_event)
target_folder = os.path.expanduser("~/Downloads")
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. Analyze the files in "{target_folder}" and organize them into subfolders.
1. First, list all files and their metadata 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 4. Move each file to its appropriate subfolder
Please confirm before moving any files. 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 ## Grouping strategies
@@ -90,10 +103,10 @@ client.stop()
For safety, you can ask Copilot to only preview changes: For safety, you can ask Copilot to only preview changes:
```python ```python
session.send(prompt=f""" await session.send(MessageOptions(prompt=f"""
Analyze files in "{target_folder}" and show me how you would organize them 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. by file type. DO NOT move any files - just show me the plan.
""") """))
``` ```
## Custom grouping with AI analysis ## 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: Let Copilot determine the best grouping based on file content:
```python ```python
session.send(prompt=f""" await session.send(MessageOptions(prompt=f"""
Look at the files in "{target_folder}" and suggest a logical organization. Look at the files in "{target_folder}" and suggest a logical organization.
Consider: Consider:
- File names and what they might contain - File names and what they might contain
@@ -109,7 +122,7 @@ Consider:
- Date patterns that might indicate projects or events - Date patterns that might indicate projects or events
Propose folder names that are descriptive and useful. Propose folder names that are descriptive and useful.
""") """))
``` ```
## Safety considerations ## Safety considerations

View File

@@ -16,31 +16,36 @@ You need to run multiple conversations in parallel, each with its own context an
## Python ## Python
```python ```python
from copilot import CopilotClient import asyncio
from copilot import CopilotClient, SessionConfig, MessageOptions
client = CopilotClient() async def main():
client.start() client = CopilotClient()
await client.start()
# Create multiple independent sessions # Create multiple independent sessions
session1 = client.create_session(model="gpt-5") session1 = await client.create_session(SessionConfig(model="gpt-5"))
session2 = client.create_session(model="gpt-5") session2 = await client.create_session(SessionConfig(model="gpt-5"))
session3 = client.create_session(model="claude-sonnet-4.5") session3 = await client.create_session(SessionConfig(model="claude-sonnet-4.5"))
# Each session maintains its own conversation history # Each session maintains its own conversation history
session1.send(prompt="You are helping with a Python project") await session1.send(MessageOptions(prompt="You are helping with a Python project"))
session2.send(prompt="You are helping with a TypeScript project") await session2.send(MessageOptions(prompt="You are helping with a TypeScript project"))
session3.send(prompt="You are helping with a Go project") await session3.send(MessageOptions(prompt="You are helping with a Go project"))
# Follow-up messages stay in their respective contexts # Follow-up messages stay in their respective contexts
session1.send(prompt="How do I create a virtual environment?") await session1.send(MessageOptions(prompt="How do I create a virtual environment?"))
session2.send(prompt="How do I set up tsconfig?") await session2.send(MessageOptions(prompt="How do I set up tsconfig?"))
session3.send(prompt="How do I initialize a module?") await session3.send(MessageOptions(prompt="How do I initialize a module?"))
# Clean up all sessions # Clean up all sessions
session1.destroy() await session1.destroy()
session2.destroy() await session2.destroy()
session3.destroy() await session3.destroy()
client.stop() await client.stop()
if __name__ == "__main__":
asyncio.run(main())
``` ```
## Custom session IDs ## Custom session IDs
@@ -48,10 +53,10 @@ client.stop()
Use custom IDs for easier tracking: Use custom IDs for easier tracking:
```python ```python
session = client.create_session( session = await client.create_session(SessionConfig(
session_id="user-123-chat", session_id="user-123-chat",
model="gpt-5" model="gpt-5"
) ))
print(session.session_id) # "user-123-chat" print(session.session_id) # "user-123-chat"
``` ```
@@ -59,16 +64,16 @@ print(session.session_id) # "user-123-chat"
## Listing sessions ## Listing sessions
```python ```python
sessions = client.list_sessions() sessions = await client.list_sessions()
for session_info in sessions: for session_info in sessions:
print(f"Session: {session_info['sessionId']}") print(f"Session: {session_info.session_id}")
``` ```
## Deleting sessions ## Deleting sessions
```python ```python
# Delete a specific session # Delete a specific session
client.delete_session("user-123-chat") await client.delete_session("user-123-chat")
``` ```
## Use cases ## Use cases

View File

@@ -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 ### Creating a session with a custom ID
```python ```python
from copilot import CopilotClient import asyncio
from copilot import CopilotClient, SessionConfig, MessageOptions
client = CopilotClient() async def main():
client.start() client = CopilotClient()
await client.start()
# Create session with a memorable ID # Create session with a memorable ID
session = client.create_session( session = await client.create_session(SessionConfig(
session_id="user-123-conversation", session_id="user-123-conversation",
model="gpt-5", 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 # Session ID is preserved
print(session.session_id) # "user-123-conversation" print(session.session_id) # "user-123-conversation"
# Destroy session but keep data on disk # Destroy session but keep data on disk
session.destroy() await session.destroy()
client.stop() await client.stop()
if __name__ == "__main__":
asyncio.run(main())
``` ```
### Resuming a session ### Resuming a session
```python ```python
client = CopilotClient() client = CopilotClient()
client.start() await client.start()
# Resume the previous session # Resume the previous session
session = client.resume_session("user-123-conversation") session = await client.resume_session("user-123-conversation")
# Previous context is restored # Previous context is restored
session.send(prompt="What were we discussing?") await session.send_and_wait(MessageOptions(prompt="What were we discussing?"))
session.destroy() await session.destroy()
client.stop() await client.stop()
``` ```
### Listing available sessions ### Listing available sessions
```python ```python
sessions = client.list_sessions() sessions = await client.list_sessions()
for s in sessions: for s in sessions:
print("Session:", s["sessionId"]) print("Session:", s.session_id)
``` ```
### Deleting a session permanently ### Deleting a session permanently
```python ```python
# Remove session and all its data from disk # Remove session and all its data from disk
client.delete_session("user-123-conversation") await client.delete_session("user-123-conversation")
``` ```
### Getting session history ### Getting session history
```python ```python
messages = session.get_messages() messages = await session.get_messages()
for msg in messages: for msg in messages:
print(f"[{msg['type']}] {msg['data']}") print(f"[{msg.type}] {msg.data.content}")
``` ```
## Best practices ## Best practices

View File

@@ -38,10 +38,15 @@ python pr_visualization.py --repo github/copilot-sdk
```python ```python
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio
import subprocess import subprocess
import sys import sys
import os import os
from copilot import CopilotClient import re
from copilot import (
CopilotClient, SessionConfig, MessageOptions,
SessionEvent, SessionEventType,
)
# ============================================================================ # ============================================================================
# Git & GitHub Detection # Git & GitHub Detection
@@ -69,7 +74,6 @@ def get_github_remote():
remote_url = result.stdout.strip() remote_url = result.stdout.strip()
# Handle SSH: git@github.com:owner/repo.git # Handle SSH: git@github.com:owner/repo.git
import re
ssh_match = re.search(r"git@github\.com:(.+/.+?)(?:\.git)?$", remote_url) ssh_match = re.search(r"git@github\.com:(.+/.+?)(?:\.git)?$", remote_url)
if ssh_match: if ssh_match:
return ssh_match.group(1) return ssh_match.group(1)
@@ -98,7 +102,7 @@ def prompt_for_repo():
# Main Application # Main Application
# ============================================================================ # ============================================================================
def main(): async def main():
print("🔍 PR Age Chart Generator\n") print("🔍 PR Age Chart Generator\n")
# Determine the repository # Determine the repository
@@ -126,11 +130,11 @@ def main():
owner, repo_name = repo.split("/", 1) owner, repo_name = repo.split("/", 1)
# Create Copilot client - no custom tools needed! # Create Copilot client
client = CopilotClient(log_level="error") client = CopilotClient()
client.start() await client.start()
session = client.create_session( session = await client.create_session(SessionConfig(
model="gpt-5", model="gpt-5",
system_message={ system_message={
"content": f""" "content": f"""
@@ -147,30 +151,34 @@ The current working directory is: {os.getcwd()}
</instructions> </instructions>
""" """
} }
) ))
done = asyncio.Event()
# Set up event handling # Set up event handling
def handle_event(event): def handle_event(event: SessionEvent):
if event["type"] == "assistant.message": if event.type == SessionEventType.ASSISTANT_MESSAGE:
print(f"\n🤖 {event['data']['content']}\n") print(f"\n🤖 {event.data.content}\n")
elif event["type"] == "tool.execution_start": elif event.type == SessionEventType.TOOL_EXECUTION_START:
print(f" ⚙️ {event['data']['toolName']}") print(f" ⚙️ {event.data.tool_name}")
elif event.type.value == "session.idle":
done.set()
session.on(handle_event) session.on(handle_event)
# Initial prompt - let Copilot figure out the details # Initial prompt - let Copilot figure out the details
print("\n📊 Starting analysis...\n") 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. Fetch the open pull requests for {owner}/{repo_name} from the last week.
Calculate the age of each PR in days. Calculate the age of each PR in days.
Then generate a bar chart image showing the distribution of PR ages Then generate a bar chart image showing the distribution of PR ages
(group them into sensible buckets like <1 day, 1-3 days, etc.). (group them into sensible buckets like <1 day, 1-3 days, etc.).
Save the chart as "pr-age-chart.png" in the current directory. 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. 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 # Interactive loop
print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n") print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n")
@@ -189,14 +197,15 @@ The current working directory is: {os.getcwd()}
break break
if user_input: if user_input:
session.send(prompt=user_input) done.clear()
session.wait_for_idle() await session.send(MessageOptions(prompt=user_input))
await done.wait()
session.destroy() await session.destroy()
client.stop() await client.stop()
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(main())
``` ```
## How it works ## How it works

View File

@@ -1,28 +1,25 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from copilot import CopilotClient import asyncio
from copilot import CopilotClient, SessionConfig, MessageOptions
client = CopilotClient() async def main():
client = CopilotClient()
try: try:
client.start() await client.start()
session = client.create_session(model="gpt-5") session = await client.create_session(SessionConfig(model="gpt-5"))
response = None response = await session.send_and_wait(MessageOptions(prompt="Hello!"))
def handle_message(event):
nonlocal response
if event["type"] == "assistant.message":
response = event["data"]["content"]
session.on(handle_message) if response:
session.send(prompt="Hello!") print(response.data.content)
session.wait_for_idle()
if response: await session.destroy()
print(response) except Exception as e:
print(f"Error: {e}")
finally:
await client.stop()
session.destroy() if __name__ == "__main__":
except Exception as e: asyncio.run(main())
print(f"Error: {e}")
finally:
client.stop()

View File

@@ -1,31 +1,40 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from copilot import CopilotClient import asyncio
import os import os
from copilot import (
CopilotClient, SessionConfig, MessageOptions,
SessionEvent, SessionEventType,
)
# Create and start client async def main():
client = CopilotClient() # Create and start client
client.start() client = CopilotClient()
await client.start()
# Create session # Create session
session = client.create_session(model="gpt-5") session = await client.create_session(SessionConfig(model="gpt-5"))
# Event handler done = asyncio.Event()
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']}")
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 session.on(handle_event)
# Change this to your target folder
target_folder = os.path.expanduser("~/Downloads")
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. Analyze the files in "{target_folder}" and organize them into subfolders.
1. First, list all files and their metadata 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 4. Move each file to its appropriate subfolder
Please confirm before moving any files. Please confirm before moving any files.
""") """))
session.wait_for_idle() await done.wait()
session.destroy() await session.destroy()
client.stop() await client.stop()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,35 +1,40 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from copilot import CopilotClient import asyncio
from copilot import CopilotClient, SessionConfig, MessageOptions
client = CopilotClient() async def main():
client.start() client = CopilotClient()
await client.start()
# Create multiple independent sessions # Create multiple independent sessions
session1 = client.create_session(model="gpt-5") session1 = await client.create_session(SessionConfig(model="gpt-5"))
session2 = client.create_session(model="gpt-5") session2 = await client.create_session(SessionConfig(model="gpt-5"))
session3 = client.create_session(model="claude-sonnet-4.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 # Each session maintains its own conversation history
session1.send(prompt="You are helping with a Python project") await session1.send(MessageOptions(prompt="You are helping with a Python project"))
session2.send(prompt="You are helping with a TypeScript project") await session2.send(MessageOptions(prompt="You are helping with a TypeScript project"))
session3.send(prompt="You are helping with a Go 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 # Follow-up messages stay in their respective contexts
session1.send(prompt="How do I create a virtual environment?") await session1.send(MessageOptions(prompt="How do I create a virtual environment?"))
session2.send(prompt="How do I set up tsconfig?") await session2.send(MessageOptions(prompt="How do I set up tsconfig?"))
session3.send(prompt="How do I initialize a module?") 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 # Clean up all sessions
session1.destroy() await session1.destroy()
session2.destroy() await session2.destroy()
session3.destroy() await session3.destroy()
client.stop() await client.stop()
print("All sessions destroyed successfully") print("All sessions destroyed successfully")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,36 +1,41 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from copilot import CopilotClient import asyncio
from copilot import CopilotClient, SessionConfig, MessageOptions
client = CopilotClient() async def main():
client.start() client = CopilotClient()
await client.start()
# Create session with a memorable ID # Create session with a memorable ID
session = client.create_session( session = await client.create_session(SessionConfig(
session_id="user-123-conversation", session_id="user-123-conversation",
model="gpt-5", model="gpt-5",
) ))
session.send(prompt="Let's discuss TypeScript generics") await session.send_and_wait(MessageOptions(prompt="Let's discuss TypeScript generics"))
print(f"Session created: {session.session_id}") print(f"Session created: {session.session_id}")
# Destroy session but keep data on disk # Destroy session but keep data on disk
session.destroy() await session.destroy()
print("Session destroyed (state persisted)") print("Session destroyed (state persisted)")
# Resume the previous session # Resume the previous session
resumed = client.resume_session("user-123-conversation") resumed = await client.resume_session("user-123-conversation")
print(f"Resumed: {resumed.session_id}") 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 # List sessions
sessions = client.list_sessions() sessions = await client.list_sessions()
print("Sessions:", [s["sessionId"] for s in sessions]) print("Sessions:", [s.session_id for s in sessions])
# Delete session permanently # Delete session permanently
client.delete_session("user-123-conversation") await client.delete_session("user-123-conversation")
print("Session deleted") print("Session deleted")
resumed.destroy() await resumed.destroy()
client.stop() await client.stop()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,10 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio
import subprocess import subprocess
import sys import sys
import os import os
import re import re
from copilot import CopilotClient from copilot import (
CopilotClient, SessionConfig, MessageOptions,
SessionEvent, SessionEventType,
)
# ============================================================================ # ============================================================================
# Git & GitHub Detection # Git & GitHub Detection
@@ -60,7 +64,7 @@ def prompt_for_repo():
# Main Application # Main Application
# ============================================================================ # ============================================================================
def main(): async def main():
print("🔍 PR Age Chart Generator\n") print("🔍 PR Age Chart Generator\n")
# Determine the repository # Determine the repository
@@ -88,11 +92,11 @@ def main():
owner, repo_name = repo.split("/", 1) owner, repo_name = repo.split("/", 1)
# Create Copilot client - no custom tools needed! # Create Copilot client
client = CopilotClient(log_level="error") client = CopilotClient()
client.start() await client.start()
session = client.create_session( session = await client.create_session(SessionConfig(
model="gpt-5", model="gpt-5",
system_message={ system_message={
"content": f""" "content": f"""
@@ -109,30 +113,34 @@ The current working directory is: {os.getcwd()}
</instructions> </instructions>
""" """
} }
) ))
done = asyncio.Event()
# Set up event handling # Set up event handling
def handle_event(event): def handle_event(event: SessionEvent):
if event["type"] == "assistant.message": if event.type == SessionEventType.ASSISTANT_MESSAGE:
print(f"\n🤖 {event['data']['content']}\n") print(f"\n🤖 {event.data.content}\n")
elif event["type"] == "tool.execution_start": elif event.type == SessionEventType.TOOL_EXECUTION_START:
print(f" ⚙️ {event['data']['toolName']}") print(f" ⚙️ {event.data.tool_name}")
elif event.type.value == "session.idle":
done.set()
session.on(handle_event) session.on(handle_event)
# Initial prompt - let Copilot figure out the details # Initial prompt - let Copilot figure out the details
print("\n📊 Starting analysis...\n") 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. Fetch the open pull requests for {owner}/{repo_name} from the last week.
Calculate the age of each PR in days. Calculate the age of each PR in days.
Then generate a bar chart image showing the distribution of PR ages Then generate a bar chart image showing the distribution of PR ages
(group them into sensible buckets like <1 day, 1-3 days, etc.). (group them into sensible buckets like <1 day, 1-3 days, etc.).
Save the chart as "pr-age-chart.png" in the current directory. 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. 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 # Interactive loop
print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n") print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n")
@@ -151,11 +159,12 @@ The current working directory is: {os.getcwd()}
break break
if user_input: if user_input:
session.send(prompt=user_input) done.clear()
session.wait_for_idle() await session.send(MessageOptions(prompt=user_input))
await done.wait()
session.destroy() await session.destroy()
client.stop() await client.stop()
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(main())