Module: Rubino::LLM::CredentialCheck
- Defined in:
- lib/rubino/llm/credential_check.rb
Overview
Single source of truth for “does the configured model have a usable credential?” — shared by the chat boot preflight (fail-fast before the ~80s retry storm, #93), the onboarding wizard, and ‘doctor`.
It answers two questions the same way the adapter resolves them at call time (ProviderResolver + RubyLLMAdapter#*_compatible_api_key!), so a preflight “no key” verdict matches what the model call would actually hit:
* resolved_provider(config) — the concrete provider the model id /
model.provider resolves to (interprets "auto").
* usable?(config) — true when a key for that provider is
resolvable from config (providers.<name>.api_key) or the native ENV
var, false otherwise.
Class Method Summary collapse
-
.missing_key_message(config = Rubino.configuration) ⇒ Object
A clear, actionable message for an unconfigured provider/model — the text surfaced on the fail-fast path and in non-interactive contexts.
-
.native_ruby_llm_provider?(provider) ⇒ Boolean
True when ruby_llm exposes a ‘<provider>_api_key` config setter for this provider (deepseek, mistral, perplexity, xai, …) — the adapter wires it generically.
- .present?(value) ⇒ Boolean
-
.provider_env_key(provider) ⇒ Object
The native ENV credential a provider reads when no config key is set.
-
.provider_env_var_name(provider) ⇒ Object
The ENV var NAME we’d suggest the user set for a given provider — the single source of truth shared by the credential check, the actionable error message, and the wizard.
-
.resolved_provider(config = Rubino.configuration) ⇒ Object
The concrete provider the configured model will be routed to.
-
.unreachable_provider_message(provider) ⇒ Object
Actionable guidance for a resolvable-but-unreachable provider (#482): ruby_llm has no native client for it, so it must be reached through an OpenAI-/Anthropic-compatible endpoint with an explicit base_url.
-
.usable?(config = Rubino.configuration) ⇒ Boolean
True when a credential for the resolved provider is available.
-
.wireable_provider?(provider) ⇒ Boolean
Providers the adapter wires from the native ENV var: the four with dedicated wiring (openai/anthropic/google/bedrock) plus any provider ruby_llm supports natively (deepseek, mistral, …).
Class Method Details
.missing_key_message(config = Rubino.configuration) ⇒ Object
A clear, actionable message for an unconfigured provider/model — the text surfaced on the fail-fast path and in non-interactive contexts.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/rubino/llm/credential_check.rb', line 119 def (config = Rubino.configuration) provider = resolved_provider(config) # The provider IS resolvable but ruby_llm can't reach it natively and no # *_compatible base_url is configured: the user may even have set its # <PROVIDER>_API_KEY, yet the call would still die. Tell them the real # fix (an OpenAI-compatible base_url) instead of "set the key" (#482). unless wireable_provider?(provider) || present?(config.provider_config(provider)["api_key"]) || config.provider_config(provider)["openai_compatible"] == true || config.provider_config(provider)["anthropic_compatible"] == true return (provider) end env_var = provider_env_var_name(provider) loader = Config::Loader.new <<~MSG.strip No API key configured for provider '#{provider}' (model #{config.dig("model", "default")}). Set it up one of these ways: • run `rubino setup` for a guided first-run setup (creates the files below), or • add #{env_var}=<your-key> to #{loader.env_path} (or run `rubino setup` to create them), or • set providers.#{provider}.api_key in #{loader.config_path} (or run `rubino setup` to create them). MSG end |
.native_ruby_llm_provider?(provider) ⇒ Boolean
True when ruby_llm exposes a ‘<provider>_api_key` config setter for this provider (deepseek, mistral, perplexity, xai, …) — the adapter wires it generically. Excludes the four we special-case so callers can branch on “additional native provider”. Single source of truth shared with RubyLLMAdapter#native_ruby_llm_provider?.
72 73 74 75 76 |
# File 'lib/rubino/llm/credential_check.rb', line 72 def native_ruby_llm_provider?(provider) return false if %w[openai anthropic google bedrock].include?(provider.to_s) RubyLLM.config.respond_to?(:"#{provider}_api_key=") end |
.present?(value) ⇒ Boolean
144 145 146 |
# File 'lib/rubino/llm/credential_check.rb', line 144 def present?(value) !value.nil? && !value.to_s.empty? end |
.provider_env_key(provider) ⇒ Object
The native ENV credential a provider reads when no config key is set. Reads the SAME env var the guidance/wizard tells the user to set (provider_env_var_name) — single source of truth, so a non-native provider (deepseek, mistral, qwen, …) consults its own <PROVIDER>_API_KEY rather than silently falling back to OPENAI_API_KEY.
97 98 99 100 101 |
# File 'lib/rubino/llm/credential_check.rb', line 97 def provider_env_key(provider) value = ENV.fetch(provider_env_var_name(provider), nil) # Google historically also accepted the legacy GOOGLE_API_KEY alias. value || (provider == "google" ? ENV.fetch("GOOGLE_API_KEY", nil) : nil) end |
.provider_env_var_name(provider) ⇒ Object
The ENV var NAME we’d suggest the user set for a given provider — the single source of truth shared by the credential check, the actionable error message, and the wizard. Native providers map to their canonical var; any other provider follows the <PROVIDER>_API_KEY convention.
107 108 109 110 111 112 113 114 115 |
# File 'lib/rubino/llm/credential_check.rb', line 107 def provider_env_var_name(provider) { "openai" => "OPENAI_API_KEY", "anthropic" => "ANTHROPIC_API_KEY", "google" => "GEMINI_API_KEY", "bedrock" => "BEDROCK_API_KEY", "minimax" => "MINIMAX_API_KEY" }.fetch(provider, "#{provider.to_s.upcase}_API_KEY") end |
.resolved_provider(config = Rubino.configuration) ⇒ Object
The concrete provider the configured model will be routed to. Mirrors DoctorCommand#resolved_provider and the adapter’s resolution: an explicit model.provider (not “auto”) wins; otherwise derive from the model id.
26 27 28 29 30 31 |
# File 'lib/rubino/llm/credential_check.rb', line 26 def resolved_provider(config = Rubino.configuration) configured = config.dig("model", "provider") return configured if configured && configured != "auto" ProviderResolver.resolve(config.dig("model", "default").to_s) end |
.unreachable_provider_message(provider) ⇒ Object
Actionable guidance for a resolvable-but-unreachable provider (#482): ruby_llm has no native client for it, so it must be reached through an OpenAI-/Anthropic-compatible endpoint with an explicit base_url.
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/rubino/llm/credential_check.rb', line 81 def (provider) <<~MSG.strip Provider '#{provider}' has no native ruby_llm support, so a #{provider_env_var_name(provider)} alone is not enough — the model call would fail with "Missing configuration". Configure it as an OpenAI-compatible endpoint in ~/.rubino/config.yml: providers.#{provider}.openai_compatible: true providers.#{provider}.base_url: <the provider's OpenAI-compatible endpoint, e.g. https://host/v1> providers.#{provider}.api_key: ${#{provider_env_var_name(provider)}} MSG end |
.usable?(config = Rubino.configuration) ⇒ Boolean
True when a credential for the resolved provider is available. The “fake” provider needs no upstream key. Honours providers.<name>.api_key first (custom / openai-compatible / anthropic-compatible gateways), then the provider’s native ENV var — the same order RubyLLMAdapter uses.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/rubino/llm/credential_check.rb', line 37 def usable?(config = Rubino.configuration) provider = resolved_provider(config) return true if provider == "fake" prov_cfg = config.provider_config(provider) return true if present?(prov_cfg["api_key"]) return present?(ENV.fetch("OPENAI_API_KEY", nil)) if prov_cfg["openai_compatible"] == true return present?(ENV.fetch("ANTHROPIC_API_KEY", nil)) if prov_cfg["anthropic_compatible"] == true # The native ENV var only counts as "usable" when the adapter can # actually reach the provider: a natively-wired provider (openai / # anthropic / google / bedrock) or one ruby_llm supports out of the box # (deepseek, mistral, …). For a resolvable-but-unreachable provider like # qwen — in PROVIDER_PATTERNS yet with no native ruby_llm support and no # *_compatible base_url — the key is present but the call would die with # "Missing configuration". Don't lie: report not-usable so the preflight # matches call time (#482). #unreachable_provider_message explains the fix. return false unless wireable_provider?(provider) present?(provider_env_key(provider)) end |
.wireable_provider?(provider) ⇒ Boolean
Providers the adapter wires from the native ENV var: the four with dedicated wiring (openai/anthropic/google/bedrock) plus any provider ruby_llm supports natively (deepseek, mistral, …).
62 63 64 65 |
# File 'lib/rubino/llm/credential_check.rb', line 62 def wireable_provider?(provider) %w[openai anthropic google bedrock].include?(provider) || native_ruby_llm_provider?(provider) end |