fix(copilot): fixes from testing and integration#131
fix(copilot): fixes from testing and integration#131theblazehen wants to merge 9 commits intoMirrowel:feature/copilot-providerfrom
Conversation
|
I'm starting my review of the Copilot provider fixes. I'll be looking closely at the new credential setup flow and the streaming response handling. Back soon with the full report! |
There was a problem hiding this comment.
Overall Assessment
This PR provides a solid set of improvements for the Copilot provider, particularly around production stability and credential management. The fixes for streaming response handling (awaiting the handler and handling ResponseNotRead errors) are critical and well-implemented. The expansion of the model catalog and the improved model prefixing logic significantly enhance the provider's usability.
Architectural Feedback
- Credential Storage: The introduction of
setup_credentialand associated management methods is a great step toward standardized credential handling. Using the email for deduplication is a robust approach. - Streaming Compatibility: The switch to plain dictionaries for streaming chunks is an interesting choice to avoid serialization issues. While practical, it's important to ensure this doesn't break expectations for downstream consumers that might rely on LiteLLM's object structure.
Key Suggestions
- Locking Mechanism: The in-memory lock key using
id(creds)might be slightly fragile if objects are cloned. - Persistence Path: Consider using a more global configuration directory for OAuth credentials to avoid "orphaned" credential files when running from different directories.
Verified Fixes
- Streaming Await: Correctly added
awaitto_handle_streaming_responsecall. - Error Handling: Implemented
aread()for error responses in streaming mode, preventingResponseNotReadexceptions. - Email Fallback: Added reliable fallback from
emailtologinusername for GitHub identity.
Questions for the Author
- Was the change from
litellm.Choicesto plain dictionaries tested with all intended downstream consumers? - Is the
oauth_credsdirectory intended to be local to the project root, or would a global config directory be more appropriate for the CLI use case?
This review was generated by an AI assistant.
|
|
||
| def _get_oauth_base_dir(self) -> Path: | ||
| """Return the OAuth credentials base directory.""" | ||
| return Path.cwd() / "oauth_creds" |
There was a problem hiding this comment.
Using Path.cwd() / "oauth_creds" might cause credentials to be scattered if the application is executed from different directories. Consider using a standardized user configuration directory (e.g., Path.home() / ".config/llm-proxy/oauth_creds") to ensure persistence across sessions.
There was a problem hiding this comment.
Good catch — fixed in 229257e. Now uses the centralized get_oauth_dir() from utils/paths.py which handles PyInstaller mode and overrides.
| The Copilot API token (access_token) expires after ~30 minutes. | ||
| """ | ||
| async with await self._get_lock(path): | ||
| lock_key = path or f"in-memory://copilot/{id(creds)}" |
There was a problem hiding this comment.
Using id(creds) for an in-memory lock key is clever, but be aware that it might lead to redundant locks if the creds dictionary is copied or recreated (e.g., during serialization/deserialization cycles). If the credentials have a unique identifier like an email, that might be a more stable key.
There was a problem hiding this comment.
Fair point — id() can be recycled if the dict gets garbage collected and recreated. In practice the in-memory path only triggers for env-based credentials which are long-lived singletons, but using the email from _proxy_metadata would be more robust. Open to changing if preferred.
| } | ||
| ) | ||
|
|
||
| return litellm.ModelResponse( |
There was a problem hiding this comment.
While using plain dictionaries for choices avoids serialization issues, it might break compatibility with LiteLLM utilities or downstream consumers that expect litellm.Choices objects. Have you verified that this change doesn’t affect any of the proxy’s core functionality or existing integrations?
There was a problem hiding this comment.
Yes — the litellm.Choices/litellm.Delta objects were causing serialization issues during streaming. ModelResponse() accepts plain dicts for choices and handles the wrapping internally, so downstream consumers still get proper objects. Tested with both streaming and non-streaming through the full proxy pipeline.
| 4. Parses response into LiteLLM format | ||
| """ | ||
| credential_path = kwargs.get("api_key", "") | ||
| credential_path = kwargs.pop("credential_identifier", kwargs.get("api_key", "")) |
There was a problem hiding this comment.
Good addition of credential_identifier. However, kwargs.get("api_key", "") is used as a fallback. In this provider, api_key is repurposed to store the credential path. It might be worth adding a comment to clarify this for future maintainers.
There was a problem hiding this comment.
Makes sense. For context: all OAuth-based providers in this project pass credential file paths (or env:// virtual paths) through the api_key/credential_identifier field since the rotator assigns credentials per-request. The credential_identifier kwarg was added in a later refactor as the canonical way to pass this, with api_key kept as fallback for backward compat.
…th.cwd() Avoids credentials scattering if run from different directories and respects PyInstaller/override modes via utils.paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Fixes found while testing and integrating the Copilot provider:
setup_credential()method with email-based deduplication and env export supportnullemail — fall back tologinusernameget_models()now returnscopilot/gpt-5instead of baregpt-5so they're directly usable in/v1/modelsDEFAULT_COPILOT_MODELSfrom 8 → 20 models (GPT-5.x, Claude 4.5/4.6, Gemini 3, Grok)acompletion()now readscredential_identifierkwarg (used by the rotator) instead of onlyapi_key_handle_streaming_responsewas not awaited in the completion pathlitellm.Choices/litellm.Deltaobjects for streaming chunks (avoids serialization issues)aread()response body before logging HTTP errors in streaming modeTest plan
🤖 Generated with Claude Code