Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bonusly_thingie.json
T

1 line
17 KiB
JSON

{"repo": "bonusly/thingie", "files": {"lib/thingie/llm_client.rb": {"43dd134bfc49aaa9dd4e4d2aff129c9b2c9d1e7c": {"t": 1785745747.387854, "n": 5897, "h": "a5c1ecec1caa4692", "c": "# frozen_string_literal: true\n\nrequire 'logger'\nrequire 'ruby_llm'\nrequire_relative 'errors'\n\nmodule Thingie\n # Thin wrapper around ruby_llm for code review prompts.\n class LlmClient\n # Maps Thingie provider names to the RubyLLM config attribute names.\n PROVIDER_CONFIG = {\n 'openai' => { key: :openai_api_key, base: :openai_api_base },\n 'anthropic' => { key: :anthropic_api_key, base: :anthropic_api_base },\n 'gemini' => { key: :gemini_api_key, base: :gemini_api_base },\n 'ollama' => { key: :ollama_api_key, base: :ollama_api_base },\n 'deepseek' => { key: :deepseek_api_key, base: :deepseek_api_base },\n 'openrouter' => { key: :openrouter_api_key, base: :openrouter_api_base },\n 'mistral' => { key: :mistral_api_key, base: :mistral_api_base },\n 'perplexity' => { key: :perplexity_api_key, base: :perplexity_api_base },\n 'xai' => { key: :xai_api_key, base: :xai_api_base },\n 'azure' => { key: :azure_api_key, base: :azure_api_base },\n 'bedrock' => { key: :bedrock_api_key, base: :bedrock_api_base },\n 'vertexai' => { key: :vertexai_service_account_key, base: :vertexai_api_base },\n 'gpustack' => { key: :gpustack_api_key, base: :gpustack_api_base }\n }.freeze\n\n LOG_LEVELS = {\n 'debug' => Logger::DEBUG, 'info' => Logger::INFO, 'warn' => Logger::WARN,\n 'error' => Logger::ERROR, 'fatal' => Logger::FATAL\n }.freeze\n\n # `model` overrides config['model'] (same provider/keys) — used to run the\n # critic pass on a stronger model than the review.\n #\n # @param config [Thingie::Configuration] the resolved run configuration\n # @param model [String, nil] optional model override (falls back to `config['model']`)\n # @raise [Thingie::ConfigurationError] if `llm_api_key` is missing from config\n def initialize(config, model: nil)\n @config = config\n @model = model || config['model']\n validate!\n load_local_models_registry\n @llm_context = build_context\n end\n\n # Per-instance RubyLLM context; configuration never mutates global state, so\n # multiple clients with different providers/keys can coexist safely.\n attr_reader :llm_context\n\n # Sends a prompt to the LLM and parses the response against a structured\n # output schema.\n #\n # @param prompt [String] the prompt text to send\n # @param schema [Object] the structured output schema the response must conform to\n # @param tools [Array<Object>] optional `ruby_llm` tools to make available for tool-use\n # @return [Object] the `ruby_llm` response\n def complete_with_schema(prompt, schema, tools: [])\n c = chat\n c = c.with_tools(*tools) unless tools.empty?\n c.with_schema(schema).ask(prompt)\n end\n\n # Applies the configured provider's API key/base to a RubyLLM config\n # object. Accepts either a per-context config (from `RubyLLM.context`) or\n # the global `RubyLLM.config`, so the `models` command can reuse it to make\n # provider credentials visible to `RubyLLM.models.refresh!`.\n #\n # @param llm_config [Object] a `RubyLLM` config object (per-context or global)\n # @param config [Thingie::Configuration] the resolved run configuration\n # @raise [ArgumentError] if `config['provider']` is not a supported provider\n # @return [void]\n def self.apply_provider_config!(llm_config, config)\n provider = config['provider'].to_s.downcase\n mapping = PROVIDER_CONFIG[provider]\n raise ArgumentError, \"Unsupported LLM provider: #{provider}\" unless mapping\n\n llm_config.public_send(\"#{mapping[:key]}=\", config['llm_api_key'])\n llm_config.public_send(\"#{mapping[:base]}=\", config['llm_api_base']) if config['llm_api_base']\n end\n\n private\n\n def validate!\n return if @config['llm_api_key'] && !@config['llm_api_key'].to_s.strip.empty?\n\n raise ConfigurationError,\n 'Missing LLM_API_KEY. Set it as an environment variable or in ~/.thingie/.env.'\n end\n\n # When `models_file` is configured and exists, point the process-wide\n # RubyLLM model registry at it and (re)load it, so reviews use the local\n # registry instead of the (potentially stale) one shipped with the gem.\n # RubyLLM's model registry is a process-wide singleton resolved from the\n # global RubyLLM.config, so this is the one global mutation the client\n # makes; provider keys/base stay per-context. A missing file is ignored so\n # reviews fall back to the shipped registry until `thingie models` creates it.\n def load_local_models_registry\n path = @config['models_file']\n return unless path && !path.to_s.strip.empty?\n\n expanded = File.expand_path(path)\n return unless File.exist?(expanded)\n\n RubyLLM.config.model_registry_file = expanded\n RubyLLM.models.load_from_json!(expanded)\n end\n\n def build_context\n RubyLLM.context do |llm_config|\n apply_provider_config(llm_config)\n llm_config.request_timeout = @config['request_timeout'] if @config['request_timeout']\n llm_config.max_retries = @config['retries'] if @config['retries']\n apply_logging_config(llm_config)\n end\n end\n\n def apply_logging_config(llm_config)\n log_file = @config['log_file']\n llm_config.log_file = log_file if log_file && !log_file.to_s.strip.empty?\n\n level = parse_log_level(@config['log_level'])\n llm_config.log_level = level if level\n end\n\n def parse_log_level(value)\n return nil unless value && !value.to_s.strip.empty?\n return value if value.is_a?(Integer) # already a Logger level\n\n LOG_LEVELS.fetch(value.to_s.downcase, Logger::INFO)\n end\n\n def chat\n llm_context.chat(model: @model, provider: provider)\n end\n\n def provider\n @config['provider'].to_s.downcase\n end\n\n def apply_provider_config(llm_config)\n self.class.apply_provider_config!(llm_config, @config)\n end\n end\nend\n"}, "0ffbcc5603f8583ba479c71fe55ed212261280c0": {"t": 1785756123.3268347, "n": 5897, "h": "a5c1ecec1caa4692", "c": "# frozen_string_literal: true\n\nrequire 'logger'\nrequire 'ruby_llm'\nrequire_relative 'errors'\n\nmodule Thingie\n # Thin wrapper around ruby_llm for code review prompts.\n class LlmClient\n # Maps Thingie provider names to the RubyLLM config attribute names.\n PROVIDER_CONFIG = {\n 'openai' => { key: :openai_api_key, base: :openai_api_base },\n 'anthropic' => { key: :anthropic_api_key, base: :anthropic_api_base },\n 'gemini' => { key: :gemini_api_key, base: :gemini_api_base },\n 'ollama' => { key: :ollama_api_key, base: :ollama_api_base },\n 'deepseek' => { key: :deepseek_api_key, base: :deepseek_api_base },\n 'openrouter' => { key: :openrouter_api_key, base: :openrouter_api_base },\n 'mistral' => { key: :mistral_api_key, base: :mistral_api_base },\n 'perplexity' => { key: :perplexity_api_key, base: :perplexity_api_base },\n 'xai' => { key: :xai_api_key, base: :xai_api_base },\n 'azure' => { key: :azure_api_key, base: :azure_api_base },\n 'bedrock' => { key: :bedrock_api_key, base: :bedrock_api_base },\n 'vertexai' => { key: :vertexai_service_account_key, base: :vertexai_api_base },\n 'gpustack' => { key: :gpustack_api_key, base: :gpustack_api_base }\n }.freeze\n\n LOG_LEVELS = {\n 'debug' => Logger::DEBUG, 'info' => Logger::INFO, 'warn' => Logger::WARN,\n 'error' => Logger::ERROR, 'fatal' => Logger::FATAL\n }.freeze\n\n # `model` overrides config['model'] (same provider/keys) — used to run the\n # critic pass on a stronger model than the review.\n #\n # @param config [Thingie::Configuration] the resolved run configuration\n # @param model [String, nil] optional model override (falls back to `config['model']`)\n # @raise [Thingie::ConfigurationError] if `llm_api_key` is missing from config\n def initialize(config, model: nil)\n @config = config\n @model = model || config['model']\n validate!\n load_local_models_registry\n @llm_context = build_context\n end\n\n # Per-instance RubyLLM context; configuration never mutates global state, so\n # multiple clients with different providers/keys can coexist safely.\n attr_reader :llm_context\n\n # Sends a prompt to the LLM and parses the response against a structured\n # output schema.\n #\n # @param prompt [String] the prompt text to send\n # @param schema [Object] the structured output schema the response must conform to\n # @param tools [Array<Object>] optional `ruby_llm` tools to make available for tool-use\n # @return [Object] the `ruby_llm` response\n def complete_with_schema(prompt, schema, tools: [])\n c = chat\n c = c.with_tools(*tools) unless tools.empty?\n c.with_schema(schema).ask(prompt)\n end\n\n # Applies the configured provider's API key/base to a RubyLLM config\n # object. Accepts either a per-context config (from `RubyLLM.context`) or\n # the global `RubyLLM.config`, so the `models` command can reuse it to make\n # provider credentials visible to `RubyLLM.models.refresh!`.\n #\n # @param llm_config [Object] a `RubyLLM` config object (per-context or global)\n # @param config [Thingie::Configuration] the resolved run configuration\n # @raise [ArgumentError] if `config['provider']` is not a supported provider\n # @return [void]\n def self.apply_provider_config!(llm_config, config)\n provider = config['provider'].to_s.downcase\n mapping = PROVIDER_CONFIG[provider]\n raise ArgumentError, \"Unsupported LLM provider: #{provider}\" unless mapping\n\n llm_config.public_send(\"#{mapping[:key]}=\", config['llm_api_key'])\n llm_config.public_send(\"#{mapping[:base]}=\", config['llm_api_base']) if config['llm_api_base']\n end\n\n private\n\n def validate!\n return if @config['llm_api_key'] && !@config['llm_api_key'].to_s.strip.empty?\n\n raise ConfigurationError,\n 'Missing LLM_API_KEY. Set it as an environment variable or in ~/.thingie/.env.'\n end\n\n # When `models_file` is configured and exists, point the process-wide\n # RubyLLM model registry at it and (re)load it, so reviews use the local\n # registry instead of the (potentially stale) one shipped with the gem.\n # RubyLLM's model registry is a process-wide singleton resolved from the\n # global RubyLLM.config, so this is the one global mutation the client\n # makes; provider keys/base stay per-context. A missing file is ignored so\n # reviews fall back to the shipped registry until `thingie models` creates it.\n def load_local_models_registry\n path = @config['models_file']\n return unless path && !path.to_s.strip.empty?\n\n expanded = File.expand_path(path)\n return unless File.exist?(expanded)\n\n RubyLLM.config.model_registry_file = expanded\n RubyLLM.models.load_from_json!(expanded)\n end\n\n def build_context\n RubyLLM.context do |llm_config|\n apply_provider_config(llm_config)\n llm_config.request_timeout = @config['request_timeout'] if @config['request_timeout']\n llm_config.max_retries = @config['retries'] if @config['retries']\n apply_logging_config(llm_config)\n end\n end\n\n def apply_logging_config(llm_config)\n log_file = @config['log_file']\n llm_config.log_file = log_file if log_file && !log_file.to_s.strip.empty?\n\n level = parse_log_level(@config['log_level'])\n llm_config.log_level = level if level\n end\n\n def parse_log_level(value)\n return nil unless value && !value.to_s.strip.empty?\n return value if value.is_a?(Integer) # already a Logger level\n\n LOG_LEVELS.fetch(value.to_s.downcase, Logger::INFO)\n end\n\n def chat\n llm_context.chat(model: @model, provider: provider)\n end\n\n def provider\n @config['provider'].to_s.downcase\n end\n\n def apply_provider_config(llm_config)\n self.class.apply_provider_config!(llm_config, @config)\n end\n end\nend\n"}, "94d27b73c817ae461cbb615e7aff3b7491983467": {"t": 1785756124.6781735, "n": 4991, "h": "2b34e24dcb6273fd", "c": "# frozen_string_literal: true\n\nrequire 'logger'\nrequire 'ruby_llm'\nrequire_relative 'errors'\n\nmodule Thingie\n # Thin wrapper around ruby_llm for code review prompts.\n class LlmClient\n # Maps Thingie provider names to the RubyLLM config attribute names.\n PROVIDER_CONFIG = {\n 'openai' => { key: :openai_api_key, base: :openai_api_base },\n 'anthropic' => { key: :anthropic_api_key, base: :anthropic_api_base },\n 'gemini' => { key: :gemini_api_key, base: :gemini_api_base },\n 'ollama' => { key: :ollama_api_key, base: :ollama_api_base },\n 'deepseek' => { key: :deepseek_api_key, base: :deepseek_api_base },\n 'openrouter' => { key: :openrouter_api_key, base: :openrouter_api_base },\n 'mistral' => { key: :mistral_api_key, base: :mistral_api_base },\n 'perplexity' => { key: :perplexity_api_key, base: :perplexity_api_base },\n 'xai' => { key: :xai_api_key, base: :xai_api_base },\n 'azure' => { key: :azure_api_key, base: :azure_api_base },\n 'bedrock' => { key: :bedrock_api_key, base: :bedrock_api_base },\n 'vertexai' => { key: :vertexai_service_account_key, base: :vertexai_api_base },\n 'gpustack' => { key: :gpustack_api_key, base: :gpustack_api_base }\n }.freeze\n\n LOG_LEVELS = {\n 'debug' => Logger::DEBUG, 'info' => Logger::INFO, 'warn' => Logger::WARN,\n 'error' => Logger::ERROR, 'fatal' => Logger::FATAL\n }.freeze\n\n # `model` overrides config['model'] (same provider/keys) — used to run the\n # critic pass on a stronger model than the review.\n def initialize(config, model: nil)\n @config = config\n @model = model || config['model']\n validate!\n load_local_models_registry\n @llm_context = build_context\n end\n\n # Per-instance RubyLLM context; configuration never mutates global state, so\n # multiple clients with different providers/keys can coexist safely.\n attr_reader :llm_context\n\n def complete_with_schema(prompt, schema, tools: [])\n c = chat\n c = c.with_tools(*tools) unless tools.empty?\n c.with_schema(schema).ask(prompt)\n end\n\n # Applies the configured provider's API key/base to a RubyLLM config\n # object. Accepts either a per-context config (from `RubyLLM.context`) or\n # the global `RubyLLM.config`, so the `models` command can reuse it to make\n # provider credentials visible to `RubyLLM.models.refresh!`.\n def self.apply_provider_config!(llm_config, config)\n provider = config['provider'].to_s.downcase\n mapping = PROVIDER_CONFIG[provider]\n raise ArgumentError, \"Unsupported LLM provider: #{provider}\" unless mapping\n\n llm_config.public_send(\"#{mapping[:key]}=\", config['llm_api_key'])\n llm_config.public_send(\"#{mapping[:base]}=\", config['llm_api_base']) if config['llm_api_base']\n end\n\n private\n\n def validate!\n return if @config['llm_api_key'] && !@config['llm_api_key'].to_s.strip.empty?\n\n raise ConfigurationError,\n 'Missing LLM_API_KEY. Set it as an environment variable or in ~/.thingie/.env.'\n end\n\n # When `models_file` is configured and exists, point the process-wide\n # RubyLLM model registry at it and (re)load it, so reviews use the local\n # registry instead of the (potentially stale) one shipped with the gem.\n # RubyLLM's model registry is a process-wide singleton resolved from the\n # global RubyLLM.config, so this is the one global mutation the client\n # makes; provider keys/base stay per-context. A missing file is ignored so\n # reviews fall back to the shipped registry until `thingie models` creates it.\n def load_local_models_registry\n path = @config['models_file']\n return unless path && !path.to_s.strip.empty?\n\n expanded = File.expand_path(path)\n return unless File.exist?(expanded)\n\n RubyLLM.config.model_registry_file = expanded\n RubyLLM.models.load_from_json!(expanded)\n end\n\n def build_context\n RubyLLM.context do |llm_config|\n apply_provider_config(llm_config)\n llm_config.request_timeout = @config['request_timeout'] if @config['request_timeout']\n llm_config.max_retries = @config['retries'] if @config['retries']\n apply_logging_config(llm_config)\n end\n end\n\n def apply_logging_config(llm_config)\n log_file = @config['log_file']\n llm_config.log_file = log_file if log_file && !log_file.to_s.strip.empty?\n\n level = parse_log_level(@config['log_level'])\n llm_config.log_level = level if level\n end\n\n def parse_log_level(value)\n return nil unless value && !value.to_s.strip.empty?\n return value if value.is_a?(Integer) # already a Logger level\n\n LOG_LEVELS.fetch(value.to_s.downcase, Logger::INFO)\n end\n\n def chat\n llm_context.chat(model: @model, provider: provider)\n end\n\n def provider\n @config['provider'].to_s.downcase\n end\n\n def apply_provider_config(llm_config)\n self.class.apply_provider_config!(llm_config, @config)\n end\n end\nend\n"}}}}