Class: AgentHarness::Providers::Base
- Inherits:
-
Object
- Object
- AgentHarness::Providers::Base
- Includes:
- Adapter
- Defined in:
- lib/agent_harness/providers/base.rb
Overview
Base class for all providers
Provides common functionality for provider implementations including command execution, error handling, and response parsing.
Direct Known Subclasses
Aider, Anthropic, Codex, Cursor, Gemini, GithubCopilot, Kilocode, MistralVibe, Opencode
Constant Summary collapse
- DEFAULT_SMOKE_TEST_CONTRACT =
{ prompt: "Reply with exactly OK.", expected_output: "OK", timeout: 30, require_output: true, success_message: "Smoke test passed" }.freeze
- COMMON_ERROR_PATTERNS =
Common error patterns shared across providers that use standard HTTP-style error responses. Providers with unique patterns (e.g. Anthropic, GitHub Copilot) override error_patterns entirely.
{ rate_limited: [ /rate.?limit/i, /too.?many.?requests/i, /\b429\b/ ], auth_expired: [ /invalid.*api.*key/i, /unauthorized/i, /authentication/i ], quota_exceeded: [ /quota.*exceeded/i, /insufficient.*quota/i, /billing/i ], transient: [ /timeout/i, /connection.*error/i, /service.*unavailable/i, /\b503\b/, /\b502\b/ ] }.tap { |patterns| patterns.each_value(&:freeze) }.freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#executor ⇒ Object
Returns the value of attribute executor.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
Class Method Summary collapse
Instance Method Summary collapse
-
#api_key_env_var_names ⇒ Array<String>
Environment variable names that the provider’s CLI reads for API key authentication.
-
#api_key_unset_vars ⇒ Array<String>
Environment variable names to unset when the caller supplies its own API key, preventing the CLI from reading stale or conflicting proxy/header variables.
-
#cli_env_overrides ⇒ Hash{String => String}
Provider-specific environment variable overrides that the caller should set when invoking the CLI (e.g. feature flags or sandbox controls).
-
#configure(options = {}) ⇒ self
Configure the provider instance.
-
#display_name ⇒ String
Human-friendly display name.
-
#initialize(config: nil, executor: nil, logger: nil) ⇒ Base
constructor
Initialize the provider.
-
#name ⇒ String
Provider name for display.
-
#parse_rate_limit_reset(text) ⇒ Time?
Parse rate-limit reset time from provider error output.
-
#parse_test_error(output:, files: {}) ⇒ Hash?
Parse provider-specific error information from test output.
-
#sandboxed_environment? ⇒ Boolean
Whether the provider is running inside a sandboxed (Docker) environment.
-
#send_chat_message(conversation: nil, messages: nil, tools: nil, stream: false, on_chat_chunk: nil, observer: nil, **options) {|Hash| ... } ⇒ Response
Send a multi-turn chat message via the provider’s chat transport.
-
#send_message(prompt:, **options) ⇒ Response
Main send_message implementation.
-
#subscription_unset_vars ⇒ Array<String>
Environment variable names to unset when the caller uses subscription-based auth, ensuring the CLI does not pick up API-key or proxy variables that would conflict.
-
#test_command_overrides ⇒ Array<String>
Additional CLI flags for health-check/test invocations.
Methods included from Adapter
#auth_lock_config, #auth_type, #build_mcp_flags, #capabilities, #chat_transport, #chat_transport_type, #config_file_content, #configuration_schema, #dangerous_mode_flags, #error_classification_patterns, #error_patterns, #execution_semantics, #fetch_mcp_servers, #health_status, included, metadata_package_name, #noisy_error_patterns, normalize_metadata_installation, normalize_metadata_source_type, normalize_metadata_version_requirement, #notify_hook_content, #session_flags, #smoke_test, #smoke_test_contract, #supported_mcp_transports, #supports_chat?, #supports_dangerous_mode?, #supports_mcp?, #supports_sessions?, #supports_text_mode?, #supports_token_counting?, #supports_tool_control?, #token_usage_from_api_response, #translate_error, #validate_config, #validate_mcp_servers!
Constructor Details
#initialize(config: nil, executor: nil, logger: nil) ⇒ Base
Initialize the provider
79 80 81 82 83 |
# File 'lib/agent_harness/providers/base.rb', line 79 def initialize(config: nil, executor: nil, logger: nil) @config = config || ProviderConfig.new(self.class.provider_name) @executor = executor || AgentHarness.configuration.command_executor @logger = logger || AgentHarness.logger end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
65 66 67 |
# File 'lib/agent_harness/providers/base.rb', line 65 def config @config end |
#executor ⇒ Object
Returns the value of attribute executor.
66 67 68 |
# File 'lib/agent_harness/providers/base.rb', line 66 def executor @executor end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
65 66 67 |
# File 'lib/agent_harness/providers/base.rb', line 65 def logger @logger end |
Class Method Details
.smoke_test_contract ⇒ Object
69 70 71 |
# File 'lib/agent_harness/providers/base.rb', line 69 def smoke_test_contract nil end |
Instance Method Details
#api_key_env_var_names ⇒ Array<String>
Environment variable names that the provider’s CLI reads for API key authentication.
270 |
# File 'lib/agent_harness/providers/base.rb', line 270 def api_key_env_var_names = [] |
#api_key_unset_vars ⇒ Array<String>
Environment variable names to unset when the caller supplies its own API key, preventing the CLI from reading stale or conflicting proxy/header variables.
276 |
# File 'lib/agent_harness/providers/base.rb', line 276 def api_key_unset_vars = [] |
#cli_env_overrides ⇒ Hash{String => String}
Provider-specific environment variable overrides that the caller should set when invoking the CLI (e.g. feature flags or sandbox controls).
288 |
# File 'lib/agent_harness/providers/base.rb', line 288 def cli_env_overrides = {} |
#configure(options = {}) ⇒ self
Configure the provider instance
89 90 91 92 |
# File 'lib/agent_harness/providers/base.rb', line 89 def configure( = {}) @config.merge!() self end |
#display_name ⇒ String
Human-friendly display name
253 254 255 |
# File 'lib/agent_harness/providers/base.rb', line 253 def display_name name.capitalize end |
#name ⇒ String
Provider name for display
246 247 248 |
# File 'lib/agent_harness/providers/base.rb', line 246 def name self.class.provider_name.to_s end |
#parse_rate_limit_reset(text) ⇒ Time?
Parse rate-limit reset time from provider error output.
Providers that emit rate-limit reset times should override this method (or include RateLimitResetParsing for the common format).
319 320 321 |
# File 'lib/agent_harness/providers/base.rb', line 319 def parse_rate_limit_reset(text) nil end |
#parse_test_error(output:, files: {}) ⇒ Hash?
Parse provider-specific error information from test output
Providers override this to extract structured error details from CLI output or sidecar files produced during a test invocation.
308 309 310 |
# File 'lib/agent_harness/providers/base.rb', line 308 def parse_test_error(output:, files: {}) nil end |
#sandboxed_environment? ⇒ Boolean
Whether the provider is running inside a sandboxed (Docker) environment
Providers can use this to adjust execution flags, e.g. skipping nested sandboxing when already inside a container.
263 264 265 |
# File 'lib/agent_harness/providers/base.rb', line 263 def sandboxed_environment? @executor.is_a?(DockerCommandExecutor) end |
#send_chat_message(conversation: nil, messages: nil, tools: nil, stream: false, on_chat_chunk: nil, observer: nil, **options) {|Hash| ... } ⇒ Response
Send a multi-turn chat message via the provider’s chat transport.
Providers that support chat mode can accept either conversation: or messages: as the conversation history payload.
Structured streaming events are delivered through three channels:
-
on_chat_chunkproc (keyword argument) -
observerobject responding toon_chat_chunk -
block (yield)
When multiple receivers are provided, all receive every event.
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/agent_harness/providers/base.rb', line 206 def (conversation: nil, messages: nil, tools: nil, stream: false, on_chat_chunk: nil, observer: nil, **, &on_chunk) unless supports_chat? raise ProviderError, "#{name} does not support chat mode" end = normalize_provider_runtime() runtime = [:provider_runtime] conversation ||= raise ArgumentError, "conversation or messages is required" unless conversation tools = runtime.chat_tools if tools.nil? && runtime&.chat_tools transport = resolve_chat_transport() = (conversation, transport) transport_opts = (runtime, ) transport_opts[:on_chat_chunk] = on_chat_chunk if on_chat_chunk transport_opts[:observer] = observer if observer response = transport.chat( messages: , tools: tools, stream: stream, **transport_opts, &on_chunk ) track_tokens(response) if response.tokens log_debug("send_chat_message_complete", duration: response.duration, tokens: response.tokens) response rescue ProviderError, AuthenticationError, RateLimitError, TimeoutError raise rescue => e last_msg = conversation&.last || &.last handle_error(e, prompt: (last_msg&.dig(:content) || last_msg&.dig("content")).to_s, options: ) end |
#send_message(prompt:, **options) ⇒ Response
Main send_message implementation
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/agent_harness/providers/base.rb', line 104 def (prompt:, **) log_debug("send_message_start", prompt_length: prompt.length, options: .keys) # Text mode: fall back to CLI with tools disabled when the provider # does not have an HTTP text transport. Providers that support text # mode (e.g. Anthropic) override send_message to intercept this # before reaching Base. if [:mode] == :text && !supports_text_mode? log_debug("text_mode_cli_fallback", provider: self.class.provider_name) = .except(:mode).merge(tools: :none) end # Warn when tools option is passed to a provider that doesn't support it if [:tools] && !supports_tool_control? log_debug("tools_option_unsupported", provider: self.class.provider_name, tools: [:tools]) @logger&.warn( "[AgentHarness::#{self.class.provider_name}] tools option is not supported " \ "by this provider and will be ignored" ) end # Coerce provider_runtime from Hash if needed = normalize_provider_runtime() # Normalize and validate MCP servers = normalize_mcp_servers() validate_mcp_servers!([:mcp_servers]) if [:mcp_servers]&.any? # Build command command = build_command(prompt, ) preparation = build_execution_preparation() # Calculate timeout timeout = [:timeout] || @config.timeout || default_timeout # Execute command start_time = Time.now result = execute_with_timeout( command, timeout: timeout, env: build_env(), preparation: preparation, **() ) duration = Time.now - start_time # Parse response response = parse_response(result, duration: duration) runtime = [:provider_runtime] # Runtime model is a per-request override and always takes precedence # over both the config-level model and whatever parse_response returned. # This is intentional: callers use runtime overrides to route a single # provider instance through different backends on each request. if runtime&.model response = Response.new( output: response.output, exit_code: response.exit_code, duration: response.duration, provider: response.provider, model: runtime.model, tokens: response.tokens, metadata: response., error: response.error ) end # Track tokens track_tokens(response) if response.tokens log_debug("send_message_complete", duration: duration, tokens: response.tokens) response rescue McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError raise rescue => e handle_error(e, prompt: prompt, options: ) end |
#subscription_unset_vars ⇒ Array<String>
Environment variable names to unset when the caller uses subscription-based auth, ensuring the CLI does not pick up API-key or proxy variables that would conflict.
282 |
# File 'lib/agent_harness/providers/base.rb', line 282 def subscription_unset_vars = [] |
#test_command_overrides ⇒ Array<String>
Additional CLI flags for health-check/test invocations
Providers override this to supply flags that should be appended when the CLI is invoked in a test or smoke-test context.
296 297 298 |
# File 'lib/agent_harness/providers/base.rb', line 296 def test_command_overrides [] end |