@@ -459,3 +459,133 @@ def summarize(self, transcript: str, user_notes: str = "") -> MeetingSummary:
459459 logger .error (f"All { max_retries } attempts failed for OpenRouter API call" )
460460 logger .error (error_msg , exc_info = True )
461461 raise
462+
463+
464+ class CopilotSummarizer (BaseSummarizer ):
465+ """Summarizer using GitHub Copilot API (included with Copilot subscription)."""
466+
467+ MODELS = {
468+ "mini" : {
469+ "id" : "gpt-4o-mini" ,
470+ "name" : "GPT-4o Mini (Copilot)" ,
471+ },
472+ "standard" : {
473+ "id" : "gpt-4o" ,
474+ "name" : "GPT-4o (Copilot)" ,
475+ },
476+ }
477+
478+ def __init__ (self , api_key : Optional [str ] = None , model : str = "mini" ):
479+ """
480+ Initialize Copilot summarizer.
481+
482+ Args:
483+ api_key: GitHub OAuth access token (from device flow authentication).
484+ model: Model tier - "mini" (gpt-4o-mini) or "standard" (gpt-4o).
485+ """
486+ if not api_key :
487+ raise ValueError (
488+ "GitHub Copilot token required. "
489+ "Authenticate via Settings > GitHub Copilot > Authenticate with GitHub."
490+ )
491+
492+ if model not in self .MODELS :
493+ raise ValueError (
494+ f"Invalid model: { model } . Choose from: { list (self .MODELS .keys ())} "
495+ )
496+
497+ self .model_config = self .MODELS [model ]
498+ self .model = self .model_config ["id" ]
499+
500+ try :
501+ from .copilot_auth import CopilotTokenManager , COPILOT_API_BASE
502+
503+ self .token_manager = CopilotTokenManager (github_token = api_key )
504+ self .copilot_api_base = COPILOT_API_BASE
505+ except Exception as e :
506+ raise RuntimeError (f"Failed to initialize Copilot auth: { e } " )
507+
508+ try :
509+ from openai import OpenAI
510+
511+ self ._openai_class = OpenAI
512+ except ImportError :
513+ raise ImportError ("openai package not installed. Run: pip install openai" )
514+
515+ # Create initial client (token will be refreshed as needed)
516+ self ._client = None
517+ self ._current_token = None
518+
519+ def _get_client (self ):
520+ """Get an OpenAI client with a valid Copilot session token."""
521+ token = self .token_manager .get_token ()
522+
523+ # Re-create client if token changed
524+ if token != self ._current_token :
525+ self ._client = self ._openai_class (
526+ api_key = token ,
527+ base_url = self .copilot_api_base ,
528+ )
529+ self ._current_token = token
530+
531+ return self ._client
532+
533+ def summarize (self , transcript : str , user_notes : str = "" ) -> MeetingSummary :
534+ """Generate summary using GitHub Copilot with retry logic."""
535+ logger .info (f"Generating AI summary with { self .model_config ['name' ]} ..." )
536+ logger .info (f"Transcript: { len (transcript .split ())} words" )
537+ print (f"Generating AI summary with { self .model_config ['name' ]} ..." )
538+ print (f"Transcript: { len (transcript .split ())} words" )
539+
540+ max_retries = 2
541+ retry_delay = 2 # seconds
542+
543+ for attempt in range (max_retries ):
544+ try :
545+ client = self ._get_client ()
546+
547+ response = client .chat .completions .create (
548+ model = self .model ,
549+ messages = [
550+ {
551+ "role" : "user" ,
552+ "content" : self ._build_prompt (
553+ transcript , user_notes = user_notes
554+ ),
555+ }
556+ ],
557+ temperature = 0.3 ,
558+ )
559+
560+ # Log token usage if available (no cost tracking - included in subscription)
561+ if response .usage :
562+ total_tokens = (
563+ response .usage .prompt_tokens + response .usage .completion_tokens
564+ )
565+ logger .info (
566+ f"Summary generated ({ total_tokens } tokens, included in Copilot subscription)"
567+ )
568+ print (
569+ f"Summary generated ({ total_tokens } tokens, included in Copilot subscription)"
570+ )
571+ else :
572+ logger .info ("Summary generated (Copilot)" )
573+ print ("Summary generated (Copilot)" )
574+
575+ return self ._parse_response (response .choices [0 ].message .content )
576+
577+ except Exception as e :
578+ error_msg = f"Attempt { attempt + 1 } /{ max_retries } failed: { type (e ).__name__ } : { e } "
579+
580+ if attempt < max_retries - 1 :
581+ # If it's a token error, force a refresh before retrying
582+ self ._current_token = None
583+ logger .warning (error_msg + f" - Retrying in { retry_delay } s..." )
584+ print (f"Warning: { error_msg } - Retrying in { retry_delay } s..." )
585+ time .sleep (retry_delay )
586+ else :
587+ logger .error (
588+ f"All { max_retries } attempts failed for Copilot API call"
589+ )
590+ logger .error (error_msg , exc_info = True )
591+ raise
0 commit comments