Skip to content

feat(provider): add OpenRouter provider with HTTP-Referer and X-Title headers#1192

Open
seoeaa wants to merge 10 commits intoagentscope-ai:mainfrom
seoeaa:feat/openrouter-provider-clean
Open

feat(provider): add OpenRouter provider with HTTP-Referer and X-Title headers#1192
seoeaa wants to merge 10 commits intoagentscope-ai:mainfrom
seoeaa:feat/openrouter-provider-clean

Conversation

@seoeaa
Copy link
Contributor

@seoeaa seoeaa commented Mar 10, 2026

Enhanced OpenRouter Model Filtering - Implementation Summary

This PR has been enhanced with model filtering capabilities for OpenRouter. Here's what was implemented:

Backend Changes (Python)

1. Extended Model Metadata

  • Added ExtendedModelInfo class with fields:
    • provider - provider name (openai, google, anthropic)
    • input_modalities - supported input types (text, image, audio, video, file)
    • output_modalities - supported output types (text, image, audio)
    • pricing - pricing information

2. Enhanced OpenRouterProvider (src/copaw/providers/openrouter_provider.py)

  • _extract_provider() - extracts provider from model ID (e.g., 'openai' from 'openai/gpt-4o')
  • _extract_model_name() - extracts model name after slash (e.g., 'gpt-4o')
  • fetch_extended_models() - fetches all models with full metadata
  • get_available_providers() - returns unique provider list

3. New API Endpoints (src/copaw/app/routers/providers.py)

  • GET /api/models/openrouter/series - get available providers
  • POST /api/models/openrouter/discover-extended - discover models with metadata
  • POST /api/models/openrouter/models/filter - filter by provider/modality

Frontend Changes (TypeScript/React)

1. Filter UI (console/src/pages/Settings/Models/components/modals/RemoteModelManageModal.tsx)

  • "Filter Models" button to toggle filter panel
  • Checkboxes for selecting providers (OpenAI, Google, Anthropic, etc.)
  • Filter by input modality (Vision/Image or Text only)
  • "Get Models" button to load filtered models
  • Model list with "Add" button for manual addition
  • "Clear All" button to remove all user-added models

2. i18n Translations
Added translations for all 4 languages (EN, RU, JA, ZH):

  • models.filterModels, models.filterByProvider, models.filterByModality
  • models.getModels, models.discovered, models.add
  • models.clearAll, models.clearAllModels, models.allModelsCleared

Key Features

  1. Manual Model Selection: Instead of auto-adding all discovered models, users can now filter and add models manually
  2. Provider Filtering: Filter by provider/series (OpenAI, Google, Anthropic, etc.)
  3. Modality Filtering: Filter by input modality (Vision/Image or Text only)
  4. Clear All: Quick action to remove all user-added models at once

This enhancement provides users with full control over which OpenRouter models they want to add to their configuration.

@seoeaa seoeaa requested a deployment to maintainer-approved March 10, 2026 17:28 — with GitHub Actions Waiting
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive support for OpenRouter as a new model provider, significantly expanding the range of available language models within the application. It includes the implementation of a dedicated provider class that handles API interactions, model discovery, and connection management, while also ensuring compliance with OpenRouter's specific HTTP header requirements for proper attribution.

Highlights

  • New OpenRouter Provider: A new OpenRouterProvider class has been added to integrate with the OpenRouter API, enabling access to its models.
  • Custom HTTP Headers: The OpenRouterProvider automatically includes HTTP-Referer and X-Title headers in its API requests, as required by OpenRouter.
  • Provider Manager Integration: The OpenRouterProvider has been fully integrated into the ProviderManager, allowing it to be discovered, initialized, and used alongside other existing providers.
  • Connection and Model Management: The new provider includes methods to check API connectivity, fetch available models, and verify specific model usability.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/copaw/providers/openrouter_provider.py
    • Added a new file to define the OpenRouterProvider class.
    • Implemented methods for _client initialization, _normalize_models_payload, check_connection, fetch_models, check_model_connection, and get_chat_model_instance.
  • src/copaw/providers/provider_manager.py
    • Imported the new OpenRouterProvider.
    • Declared OPENROUTER_MODELS as an empty list for dynamic model fetching.
    • Instantiated PROVIDER_OPENROUTER with its ID, name, base URL, API key prefix, and model list.
    • Added PROVIDER_OPENROUTER to the list of built-in providers during initialization.
    • Included logic in _provider_from_data to correctly validate and return an OpenRouterProvider instance based on provided data.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new OpenRouterProvider to connect with OpenRouter's API. The implementation is well-structured, but there are a couple of areas for improvement. I've pointed out a case of code duplication with the request headers that can be refactored for better maintainability. Additionally, I've suggested a more efficient implementation for the model list normalization logic. Overall, a good addition.

from copaw.providers.provider import ModelInfo, Provider


class OpenRouterProvider(Provider):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The default_headers dictionary is hardcoded and duplicated in both the _client method (lines 22-25) and the get_chat_model_instance method (lines 100-103). This violates the DRY (Don't Repeat Yourself) principle and makes the code harder to maintain.

To improve this, you can define the headers as a class-level constant. This ensures that the headers are defined in a single place, making future updates easier.

Example:

class OpenRouterProvider(Provider):
    """OpenRouter provider with required HTTP-Referer and X-Title headers."""

    _DEFAULT_HEADERS = {
        "HTTP-Referer": "https://copaw.ai",
        "X-Title": "CoPaw",
    }

    def _client(self, timeout: float = 30) -> AsyncOpenAI:
        return AsyncOpenAI(
            base_url=self.base_url,
            api_key=self.api_key,
            timeout=timeout,
            default_headers=self._DEFAULT_HEADERS,
        )

    # ... other methods

    def get_chat_model_instance(self, model_id: str) -> ChatModelBase:
        from .openai_chat_model_compat import OpenAIChatModelCompat

        return OpenAIChatModelCompat(
            model_name=model_id,
            stream=True,
            api_key=self.api_key,
            client_kwargs={
                "base_url": self.base_url,
                "default_headers": self._DEFAULT_HEADERS,
            },
        )

Comment on lines +29 to +48
def _normalize_models_payload(payload: Any) -> List[ModelInfo]:
models: List[ModelInfo] = []
rows = getattr(payload, "data", [])
for row in rows or []:
model_id = str(getattr(row, "id", "") or "").strip()
if not model_id:
continue
model_name = (
str(getattr(row, "name", "") or model_id).strip() or model_id
)
models.append(ModelInfo(id=model_id, name=model_name))

deduped: List[ModelInfo] = []
seen: set[str] = set()
for model in models:
if model.id in seen:
continue
seen.add(model.id)
deduped.append(model)
return deduped
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This method can be simplified and made more efficient by using a dictionary for deduplication and combining the two loops into one. This avoids creating an intermediate list and iterating over it a second time. Using a dictionary to store models by their ID is a common and efficient pattern for deduplication while iterating.

Suggested change
def _normalize_models_payload(payload: Any) -> List[ModelInfo]:
models: List[ModelInfo] = []
rows = getattr(payload, "data", [])
for row in rows or []:
model_id = str(getattr(row, "id", "") or "").strip()
if not model_id:
continue
model_name = (
str(getattr(row, "name", "") or model_id).strip() or model_id
)
models.append(ModelInfo(id=model_id, name=model_name))
deduped: List[ModelInfo] = []
seen: set[str] = set()
for model in models:
if model.id in seen:
continue
seen.add(model.id)
deduped.append(model)
return deduped
def _normalize_models_payload(payload: Any) -> List[ModelInfo]:
models: dict[str, ModelInfo] = {}
rows = getattr(payload, "data", [])
for row in rows or []:
model_id = str(getattr(row, "id", "") or "").strip()
if not model_id or model_id in models:
continue
model_name = (
str(getattr(row, "name", "") or model_id).strip() or model_id
)
models[model_id] = ModelInfo(id=model_id, name=model_name)
return list(models.values())

@seoeaa seoeaa force-pushed the feat/openrouter-provider-clean branch from a849a8f to 83ac2fe Compare March 13, 2026 00:13
@seoeaa seoeaa requested a deployment to maintainer-approved March 13, 2026 00:13 — with GitHub Actions Waiting
// Enable discover for providers that support it
// For local providers (ollama, llama.cpp, mlx) - check base_url
// For built-in providers with frozen URL - check api_key
const canDiscover = provider.is_local
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add a support_model_discovery field to the ProviderInfo structure in #1342 , try use it instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants