Skip to content

Add Requesty as an AI integration provider#212

Open
Thibaultjaigu wants to merge 1 commit into
etewiah:masterfrom
Thibaultjaigu:add-requesty-provider
Open

Add Requesty as an AI integration provider#212
Thibaultjaigu wants to merge 1 commit into
etewiah:masterfrom
Thibaultjaigu:add-requesty-provider

Conversation

@Thibaultjaigu

Copy link
Copy Markdown

This adds Requesty as an AI provider, mirroring the existing OpenRouter provider.

Requesty (https://router.requesty.ai/v1) is an OpenAI-compatible LLM router.

Changes:

  • app/services/integrations/providers/requesty.rb: new Requesty < Base provider (auto-registers via the providers-dir glob), API_BASE_URL = https://router.requesty.ai/v1, verified model list, validate_connection hitting /models.
  • app/services/ai/base_service.rb: OpenAI-compatible RubyLLM wiring for requesty (base URL + key), mirroring how OpenRouter is configured.
  • specs + docs mirroring the OpenRouter ones.

Model naming is provider/model. Verified: ruby -c on the new files passes.

I work at Requesty. This mirrors the existing OpenRouter provider as closely as possible. Happy to adjust or close it if it's not a fit.

Mirror the existing OpenRouter provider to add Requesty (https://router.requesty.ai/v1),
an OpenAI-compatible LLM router. Adds a dedicated Requesty provider class (auto-registered
via the providers directory), the OpenAI-compatible wiring in the AI base service, docs,
and specs mirroring the OpenRouter provider.

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
end

# Register with the integrations registry
Integrations::Registry.register(:ai, :requesty, Integrations::Providers::Requesty)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [82/80]

label: 'Max Tokens',
help: 'Maximum number of tokens in generated responses'

def validate_connection

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/AbcSize: Assignment Branch Condition size for validate_connection is too high. [18.92/15]
Metrics/MethodLength: Method has too many lines. [19/10]

class Requesty < Base
self.category = :ai
self.display_name = 'Requesty'
self.description = 'Access 400+ AI models from multiple providers through a single OpenAI-compatible API'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [111/80]

# Requesty exposes an OpenAI-compatible API, so we point RubyLLM's
# OpenAI client at the Requesty router base URL.
config.openai_api_key = @integration.credential(:api_key)
config.openai_api_base = Integrations::Providers::Requesty::API_BASE_URL

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [84/80]

category { 'ai' }
provider { 'requesty' }
credentials { { 'api_key' => 'sk-rq-...2345' } }
settings { { 'default_model' => 'openai/gpt-4o-mini', 'max_tokens' => 4096 } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [84/80]

end

it 'defaults to openai/gpt-4o-mini' do
expect(described_class.default_for(:default_model)).to eq('openai/gpt-4o-mini')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [87/80]

options = field[:options]
options.each do |label, value|
expect(value).to match(%r{^[a-z-]+/[a-z0-9.-]+$}i),
"Expected '#{value}' to be in provider/model format"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Layout/AlignParameters: Align the parameters of a method call if they span more than one line.


it 'has options in provider/model format' do
options = field[:options]
options.each do |label, value|

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lint/UnusedBlockArgument: Unused block argument - label. If it's necessary, use _ or _label as an argument name to indicate that it won't be used.

end
end

describe 'setting fields' do

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/BlockLength: Block has too many lines. [36/25]

end

it 'has help text for api_key' do
expect(described_class.credential_fields[:api_key][:help]).to include('requesty.ai')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [90/80]

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

Copy link
Copy Markdown

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 Requesty integration provider to allow unified access to various AI models through an OpenAI-compatible API. It includes the service configuration, documentation, factory traits, and comprehensive unit tests. The review feedback highlights a critical issue regarding global configuration state pollution in RubyLLM.configure that could leak settings across requests in a multi-tenant environment, and suggests refactoring the connection validation to improve error handling and remove redundant rescue blocks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

# Requesty exposes an OpenAI-compatible API, so we point RubyLLM's
# OpenAI client at the Requesty router base URL.
config.openai_api_key = @integration.credential(:api_key)
config.openai_api_base = Integrations::Providers::Requesty::API_BASE_URL

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-critical critical

Because RubyLLM.configure modifies global configuration state, setting config.openai_api_base here will persist across requests. In a multi-tenant environment, subsequent requests that use the standard openai provider or fall back to environment variables will incorrectly route their requests to Requesty because openai_api_base is never reset.

To prevent this cross-request configuration pollution, config.openai_api_base should be explicitly reset to nil (or its default) when configuring other providers or at the start of the configure_ruby_llm! block, for example:

def configure_ruby_llm!
  RubyLLM.configure do |config|
    # Reset any provider-specific base URLs to prevent cross-request pollution
    config.openai_api_base = nil
    # ...

Comment on lines +58 to +82
def validate_connection
unless credentials_valid?
errors.add(:base, 'API key is required')
return false
end

# Validate by fetching the models list from Requesty
response = Faraday.get("#{API_BASE_URL}/models") do |req|
req.headers['Authorization'] = "Bearer #{credential(:api_key)}"
req.options.timeout = 10
end

if response.status == 401
errors.add(:base, 'Invalid API key')
return false
end

response.success?
rescue Faraday::Error => e
errors.add(:base, "Connection failed: #{e.message}")
false
rescue StandardError => e
errors.add(:base, "Connection failed: #{e.message}")
false
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Improve the connection validation by:

  1. Ensuring that any non-success response (e.g., 403, 429, 500) adds a descriptive error message to errors instead of failing silently with an empty error message.
  2. Removing the redundant rescue Faraday::Error block, as Faraday::Error is a subclass of StandardError and is already caught by the subsequent block.
      def validate_connection
        unless credentials_valid?
          errors.add(:base, 'API key is required')
          return false
        end

        # Validate by fetching the models list from Requesty
        response = Faraday.get("#{API_BASE_URL}/models") do |req|
          req.headers['Authorization'] = "Bearer #{credential(:api_key)}"
          req.options.timeout = 10
        end

        if response.success?
          true
        else
          error_message = response.status == 401 ? 'Invalid API key' : "API returned status #{response.status}"
          errors.add(:base, error_message)
          false
        end
      rescue StandardError => e
        errors.add(:base, "Connection failed: #{e.message}")
        false
      end

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