Module: AgentHarness::Providers::Adapter
- Included in:
- Base
- Defined in:
- lib/agent_harness/providers/adapter.rb
Overview
Interface that all providers must implement
This module defines the contract that provider implementations must follow. Include this module in provider classes to ensure they implement the required interface.
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
- .included(base) ⇒ Object
- .metadata_package_name(contract, source) ⇒ Object
- .normalize_metadata_installation(contract, provider_name:, binary_name:) ⇒ Object
-
.normalize_metadata_runtime_requirements(requirements) ⇒ Array<Hash>?
Normalize an installation contract's
runtime_requirementsso the stable provider-metadata path exposes the same runtime prerequisites (e.g. Bun for the :omp provider) as the raw installation contract. - .normalize_metadata_source_type(source) ⇒ Object
- .normalize_metadata_version_requirement(requirement) ⇒ Object
Instance Method Summary collapse
-
#auth_lock_config ⇒ Hash?
Auth lock configuration for providers that need file-based lock serialization (e.g. OAuth refresh token coordination).
-
#auth_type ⇒ Symbol
Authentication type for this provider.
-
#build_mcp_flags(mcp_servers, working_dir: nil) ⇒ Array<String>
Build provider-specific MCP flags/arguments for CLI invocation.
-
#capabilities ⇒ Hash
Provider capabilities.
-
#chat_transport ⇒ Object?
Returns the transport instance used for chat mode.
-
#chat_transport_type ⇒ Symbol?
Returns the symbolic transport type for chat without instantiating the transport object.
-
#config_file_content(options = {}) ⇒ String?
Generate provider-specific config file content.
-
#configuration_schema ⇒ Hash
Provider configuration schema for app-driven setup UIs.
-
#dangerous_mode_flags ⇒ Array<String>
Get dangerous mode flags.
-
#error_classification_patterns ⇒ Hash<Symbol, Array<Regexp>>
Error classification patterns for downstream consumers.
-
#error_patterns ⇒ Hash<Symbol, Array<Regexp>>
Error patterns for classification.
-
#execution_semantics ⇒ Hash
Execution semantics for this provider.
-
#fetch_mcp_servers ⇒ Array<Hash>
Fetch configured MCP servers.
-
#health_status ⇒ Hash
Health check.
-
#heartbeat_integration(heartbeat_file_path:) ⇒ Hash
Return structured heartbeat integration wiring for this provider.
-
#noisy_error_patterns ⇒ Array<Regexp>
Patterns matching noisy/non-actionable error output.
-
#notify_hook_content ⇒ String?
Generate provider-specific notification hook content.
-
#parse_rate_limit_reset(output) ⇒ Time?
Parse a rate-limit reset time from provider output.
-
#plan_execution(prompt:, **options) ⇒ Hash
Return the provider CLI execution plan without executing the command.
-
#preflight_check(env:, timeout: 10) ⇒ Hash
Lightweight provider-owned preflight check executed before smoke tests.
-
#send_message(prompt:, **options) ⇒ Response
Send a message/prompt to the provider.
-
#session_flags(session_id) ⇒ Array<String>
Get session flags for continuation.
-
#smoke_test(timeout: nil, provider_runtime: nil) ⇒ Hash
Execute a minimal provider-owned smoke test via the configured executor.
-
#smoke_test_contract ⇒ Hash?
Canonical smoke-test contract for this provider instance.
-
#supported_mcp_transports ⇒ Array<String>
Supported MCP transport types for this provider.
-
#supports_activity_heartbeat? ⇒ Boolean
Whether this provider supports activity heartbeat signaling.
-
#supports_chat? ⇒ Boolean
Check if provider supports multi-turn chat mode.
-
#supports_dangerous_mode? ⇒ Boolean
Check if provider supports dangerous mode.
-
#supports_mcp? ⇒ Boolean
Check if provider supports MCP.
-
#supports_message_tool_injection? ⇒ Boolean
Check if provider message mode can inject available tool definitions through the
tools:option. -
#supports_sessions? ⇒ Boolean
Check if provider supports session continuation.
-
#supports_text_mode? ⇒ Boolean
Check if provider supports text-only mode via direct HTTP transport.
-
#supports_token_counting? ⇒ Boolean
Whether this provider can extract token usage from CLI output.
-
#supports_tool_control? ⇒ Boolean
Check if provider supports tool access control (disabling tools).
-
#token_usage_from_api_response(body) ⇒ Hash
Extract token usage from an API response body.
-
#translate_error(message) ⇒ String
Translate a raw error message into a user-friendly string.
-
#validate_config ⇒ Hash
Validate provider configuration.
-
#validate_mcp_servers!(mcp_servers) ⇒ Object
Validate that this provider can handle the given MCP servers.
Class Method Details
.included(base) ⇒ Object
106 107 108 |
# File 'lib/agent_harness/providers/adapter.rb', line 106 def self.included(base) base.extend(ClassMethods) end |
.metadata_package_name(contract, source) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/agent_harness/providers/adapter.rb', line 77 def self.(contract, source) return contract[:package_name] if contract[:package_name] return source[:package] if source.is_a?(Hash) package = contract[:package] return package unless package.is_a?(String) if package.split("@").first == "" package.split("@", 3).first(2).join("@") else package.split("@", 2).first end end |
.normalize_metadata_installation(contract, provider_name:, binary_name:) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/agent_harness/providers/adapter.rb', line 19 def self.(contract, provider_name:, binary_name:) return nil unless contract.is_a?(Hash) source = contract[:source] install_command = contract[:install_command]&.dup normalized = { provider: provider_name.to_sym, source_type: (contract[:source_type] || source), package_name: (contract, source), default_version: contract[:default_version] || contract[:version] || contract[:resolved_version], resolved_version: contract[:resolved_version] || contract[:version] || contract[:default_version], supported_version_requirement: ( contract[:supported_version_requirement] || contract[:version_requirement] ), binary_name: contract[:binary_name] || binary_name, install_command: install_command, install_command_string: contract[:install_command_string] || install_command&.join(" ") } normalized[:requires_postinstall] = contract[:requires_postinstall] if contract.key?(:requires_postinstall) normalized[:postinstall_command] = contract[:postinstall_command] if contract.key?(:postinstall_command) if contract.key?(:runtime_requirements) normalized[:runtime_requirements] = (contract[:runtime_requirements]) end normalized end |
.normalize_metadata_runtime_requirements(requirements) ⇒ Array<Hash>?
Normalize an installation contract's runtime_requirements so the
stable provider-metadata path exposes the same runtime prerequisites
(e.g. Bun for the :omp provider) as the raw installation contract.
Each entry is duplicated so consumers receive a self-contained hash they can mutate top-level keys on without aliasing the provider-owned contract, and the resulting array is frozen to signal that the normalized metadata is read-only.
61 62 63 64 65 66 67 68 69 |
# File 'lib/agent_harness/providers/adapter.rb', line 61 def self.(requirements) return requirements unless requirements.is_a?(Array) requirements.map do |requirement| next requirement.dup if requirement.is_a?(Hash) requirement end.freeze end |
.normalize_metadata_source_type(source) ⇒ Object
71 72 73 74 75 |
# File 'lib/agent_harness/providers/adapter.rb', line 71 def self.(source) return source[:type]&.to_sym if source.is_a?(Hash) source&.to_sym end |
.normalize_metadata_version_requirement(requirement) ⇒ Object
91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/agent_harness/providers/adapter.rb', line 91 def self.(requirement) case requirement when nil nil when Array if requirement.all? { |entry| entry.is_a?(Array) && entry.length == 2 } requirement.map { |operator, version| "#{operator} #{version}" }.join(", ") else requirement.join(", ") end else requirement.to_s end end |
Instance Method Details
#auth_lock_config ⇒ Hash?
Auth lock configuration for providers that need file-based lock serialization (e.g. OAuth refresh token coordination).
1285 1286 1287 |
# File 'lib/agent_harness/providers/adapter.rb', line 1285 def auth_lock_config nil end |
#auth_type ⇒ Symbol
Authentication type for this provider
963 964 965 |
# File 'lib/agent_harness/providers/adapter.rb', line 963 def auth_type :api_key end |
#build_mcp_flags(mcp_servers, working_dir: nil) ⇒ Array<String>
Build provider-specific MCP flags/arguments for CLI invocation
996 997 998 |
# File 'lib/agent_harness/providers/adapter.rb', line 996 def build_mcp_flags(mcp_servers, working_dir: nil) [] end |
#capabilities ⇒ Hash
Provider capabilities
894 895 896 897 898 899 900 901 902 903 904 |
# File 'lib/agent_harness/providers/adapter.rb', line 894 def capabilities { streaming: false, file_upload: false, vision: false, tool_use: false, json_mode: false, mcp: false, dangerous_mode: false } end |
#chat_transport ⇒ Object?
Returns the transport instance used for chat mode.
Providers that support chat override this to return an appropriate transport (e.g. OpenAICompatibleTransport or TextTransport).
1079 1080 1081 |
# File 'lib/agent_harness/providers/adapter.rb', line 1079 def chat_transport nil end |
#chat_transport_type ⇒ Symbol?
Returns the symbolic transport type for chat without instantiating the transport object. This avoids triggering API key resolution or other authentication side effects during metadata collection.
1088 1089 1090 |
# File 'lib/agent_harness/providers/adapter.rb', line 1088 def chat_transport_type nil end |
#config_file_content(options = {}) ⇒ String?
Generate provider-specific config file content.
Providers that require a config file written before CLI execution (e.g. Codex TOML, Kilocode JSON) should override this method.
1267 1268 1269 |
# File 'lib/agent_harness/providers/adapter.rb', line 1267 def config_file_content( = {}) nil end |
#configuration_schema ⇒ Hash
Provider configuration schema for app-driven setup UIs
Returns metadata describing the configurable fields, supported authentication modes, and backend compatibility for this provider. Applications use this to build generic provider-entry forms without hardcoding provider-specific knowledge.
883 884 885 886 887 888 889 |
# File 'lib/agent_harness/providers/adapter.rb', line 883 def configuration_schema { fields: [], auth_modes: [auth_type], openai_compatible: false } end |
#dangerous_mode_flags ⇒ Array<String>
Get dangerous mode flags
1102 1103 1104 |
# File 'lib/agent_harness/providers/adapter.rb', line 1102 def dangerous_mode_flags [] end |
#error_classification_patterns ⇒ Hash<Symbol, Array<Regexp>>
Error classification patterns for downstream consumers
Returns patterns grouped by classification category. These patterns encode provider-specific knowledge about how each CLI reports errors and are intended for use by callers outside agent-harness.
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 |
# File 'lib/agent_harness/providers/adapter.rb', line 921 def error_classification_patterns { auth_expired: [], abort: [], authentication: [], quota: [ /requires more credits/i, /insufficient credits/i, /insufficient balance/i, /credit.*exceeded/i, /spend limit.*reached/i, /billing.*limit/i, /(?:weekly|monthly)(?:\/(?:weekly|monthly))?\s+limit\s+exhausted/i ] } end |
#error_patterns ⇒ Hash<Symbol, Array<Regexp>>
Error patterns for classification
909 910 911 |
# File 'lib/agent_harness/providers/adapter.rb', line 909 def error_patterns {} end |
#execution_semantics ⇒ Hash
Execution semantics for this provider
Returns a hash describing provider-specific execution behavior so downstream apps do not need to hardcode CLI quirks. This metadata can be used to select the right flags and interpret output.
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 |
# File 'lib/agent_harness/providers/adapter.rb', line 1235 def execution_semantics { prompt_delivery: :arg, # :arg, :stdin, or :flag output_format: :text, # :text or :json sandbox_aware: false, # adjusts behavior inside containers uses_subcommand: false, # e.g. "codex exec", "opencode run" non_interactive_flag: nil, # flag to suppress interactive prompts legitimate_exit_codes: [0], # exit codes that are NOT errors stderr_is_diagnostic: true, # stderr may contain non-error output parses_rate_limit_reset: false # can extract Retry-After from output } end |
#fetch_mcp_servers ⇒ Array<Hash>
Fetch configured MCP servers
977 978 979 |
# File 'lib/agent_harness/providers/adapter.rb', line 977 def fetch_mcp_servers [] end |
#health_status ⇒ Hash
Health check
1149 1150 1151 |
# File 'lib/agent_harness/providers/adapter.rb', line 1149 def health_status {healthy: true, message: "OK"} end |
#heartbeat_integration(heartbeat_file_path:) ⇒ Hash
Return structured heartbeat integration wiring for this provider.
Providers that support activity heartbeat return a Hash describing how to wire heartbeat file touches into the CLI execution. The returned contract includes environment variables, execution preparation (config/plugin file writes), and the heartbeat granularity so downstream callers can enable heartbeat-backed idle timeout without hardcoding provider-specific config logic.
1318 1319 1320 |
# File 'lib/agent_harness/providers/adapter.rb', line 1318 def heartbeat_integration(heartbeat_file_path:) {supported: false, env: {}, preparation: nil, granularity: nil} end |
#noisy_error_patterns ⇒ Array<Regexp>
Patterns matching noisy/non-actionable error output
Downstream consumers can use these to filter out log noise from provider stderr/stdout that is not meaningful for users.
944 945 946 |
# File 'lib/agent_harness/providers/adapter.rb', line 944 def noisy_error_patterns [] end |
#notify_hook_content ⇒ String?
Generate provider-specific notification hook content.
Providers that support notification hooks appended to their config file should override this method.
1277 1278 1279 |
# File 'lib/agent_harness/providers/adapter.rb', line 1277 def notify_hook_content nil end |
#parse_rate_limit_reset(output) ⇒ Time?
Parse a rate-limit reset time from provider output
Providers that include rate-limit reset information in their error output can override this to extract it, so the orchestration layer can schedule retries accurately.
1256 1257 1258 |
# File 'lib/agent_harness/providers/adapter.rb', line 1256 def parse_rate_limit_reset(output) nil end |
#plan_execution(prompt:, **options) ⇒ Hash
Return the provider CLI execution plan without executing the command.
871 872 873 |
# File 'lib/agent_harness/providers/adapter.rb', line 871 def plan_execution(prompt:, **) raise NotImplementedError, "#{self.class} must implement #plan_execution" end |
#preflight_check(env:, timeout: 10) ⇒ Hash
Lightweight provider-owned preflight check executed before smoke tests.
1158 1159 1160 |
# File 'lib/agent_harness/providers/adapter.rb', line 1158 def preflight_check(env:, timeout: 10) {healthy: true} end |
#send_message(prompt:, **options) ⇒ Response
Send a message/prompt to the provider
862 863 864 |
# File 'lib/agent_harness/providers/adapter.rb', line 862 def (prompt:, **) raise NotImplementedError, "#{self.class} must implement #send_message" end |
#session_flags(session_id) ⇒ Array<String>
Get session flags for continuation
1117 1118 1119 |
# File 'lib/agent_harness/providers/adapter.rb', line 1117 def session_flags(session_id) [] end |
#smoke_test(timeout: nil, provider_runtime: nil) ⇒ Hash
Execute a minimal provider-owned smoke test via the configured executor.
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 |
# File 'lib/agent_harness/providers/adapter.rb', line 1174 def smoke_test(timeout: nil, provider_runtime: nil) contract = smoke_test_contract raise NotImplementedError, "#{self.class} does not implement #smoke_test_contract" unless contract prompt = contract[:prompt] if !prompt.is_a?(String) || prompt.strip.empty? raise ConfigurationError, "#{self.class}.smoke_test_contract must define a non-empty :prompt" end response = ( prompt: prompt, timeout: timeout || contract[:timeout], provider_runtime: provider_runtime, smoke_test: true ) output = response.output.to_s.strip expected_output = contract[:expected_output]&.strip success = response.success? && (!contract.fetch(:require_output, true) || !output.empty?) success &&= expected_output.nil? || output == expected_output if success return { ok: true, status: "ok", message: contract[:success_message] || "Smoke test passed", error_category: nil, output: output, exit_code: response.exit_code } end = response.error.to_s.strip = output if .empty? = "Smoke test failed with exit code #{response.exit_code}" if .empty? { ok: false, status: "error", message: , error_category: (), output: output, exit_code: response.exit_code } rescue TimeoutError => e failure_smoke_test_result(e., :timeout) rescue AuthenticationError => e failure_smoke_test_result(e., :auth_expired) rescue RateLimitError => e failure_smoke_test_result(e., e.error_category || :rate_limited) rescue ProviderError => e failure_smoke_test_result(e., (e.)) end |
#smoke_test_contract ⇒ Hash?
Canonical smoke-test contract for this provider instance.
1165 1166 1167 |
# File 'lib/agent_harness/providers/adapter.rb', line 1165 def smoke_test_contract self.class.smoke_test_contract if self.class.respond_to?(:smoke_test_contract) end |
#supported_mcp_transports ⇒ Array<String>
Supported MCP transport types for this provider
Defaults to ["stdio"]. Providers that support HTTP/SSE transports should override this to include those transports.
987 988 989 |
# File 'lib/agent_harness/providers/adapter.rb', line 987 def supported_mcp_transports %w[stdio] end |
#supports_activity_heartbeat? ⇒ Boolean
Whether this provider supports activity heartbeat signaling.
Providers that can emit a container-visible liveness signal during
long-running CLI execution return true. Downstream callers use
this to decide whether heartbeat-backed idle timeout is viable
without maintaining provider-name allowlists.
1297 1298 1299 |
# File 'lib/agent_harness/providers/adapter.rb', line 1297 def supports_activity_heartbeat? false end |
#supports_chat? ⇒ Boolean
Check if provider supports multi-turn chat mode.
Providers that return true can accept conversation history
and return streaming multi-turn responses via send_chat_message.
1069 1070 1071 |
# File 'lib/agent_harness/providers/adapter.rb', line 1069 def supports_chat? false end |
#supports_dangerous_mode? ⇒ Boolean
Check if provider supports dangerous mode
1095 1096 1097 |
# File 'lib/agent_harness/providers/adapter.rb', line 1095 def supports_dangerous_mode? capabilities[:dangerous_mode] end |
#supports_mcp? ⇒ Boolean
Check if provider supports MCP
970 971 972 |
# File 'lib/agent_harness/providers/adapter.rb', line 970 def supports_mcp? capabilities[:mcp] end |
#supports_message_tool_injection? ⇒ Boolean
Check if provider message mode can inject available tool definitions
through the tools: option. Providers that use tools: strictly for
disallow lists should leave this as false.
1048 1049 1050 |
# File 'lib/agent_harness/providers/adapter.rb', line 1048 def false end |
#supports_sessions? ⇒ Boolean
Check if provider supports session continuation
1109 1110 1111 |
# File 'lib/agent_harness/providers/adapter.rb', line 1109 def supports_sessions? false end |
#supports_text_mode? ⇒ Boolean
Check if provider supports text-only mode via direct HTTP transport.
Providers that return true will route mode: :text requests
through their REST API instead of the CLI. Providers that return
false fall back to the CLI path with tools forcibly disabled.
1059 1060 1061 |
# File 'lib/agent_harness/providers/adapter.rb', line 1059 def supports_text_mode? false end |
#supports_token_counting? ⇒ Boolean
Whether this provider can extract token usage from CLI output
1135 1136 1137 |
# File 'lib/agent_harness/providers/adapter.rb', line 1135 def supports_token_counting? false end |
#supports_tool_control? ⇒ Boolean
Check if provider supports tool access control (disabling tools)
1039 1040 1041 |
# File 'lib/agent_harness/providers/adapter.rb', line 1039 def supports_tool_control? false end |
#token_usage_from_api_response(body) ⇒ Hash
Extract token usage from an API response body
Parses the provider-specific API response shape and returns normalized token counts.
1128 1129 1130 |
# File 'lib/agent_harness/providers/adapter.rb', line 1128 def token_usage_from_api_response(body) {} end |
#translate_error(message) ⇒ String
Translate a raw error message into a user-friendly string
Providers override this to map CLI-specific error output into concise, actionable messages.
955 956 957 |
# File 'lib/agent_harness/providers/adapter.rb', line 955 def translate_error() end |
#validate_config ⇒ Hash
Validate provider configuration
1142 1143 1144 |
# File 'lib/agent_harness/providers/adapter.rb', line 1142 def validate_config {valid: true, errors: []} end |
#validate_mcp_servers!(mcp_servers) ⇒ Object
Validate that this provider can handle the given MCP servers
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 |
# File 'lib/agent_harness/providers/adapter.rb', line 1005 def validate_mcp_servers!(mcp_servers) return if mcp_servers.nil? || mcp_servers.empty? unless supports_mcp? raise McpUnsupportedError.new( "Provider '#{self.class.provider_name}' does not support MCP servers", provider: self.class.provider_name ) end supported = supported_mcp_transports if supported.empty? raise McpUnsupportedError.new( "Provider '#{self.class.provider_name}' does not support request-time MCP servers", provider: self.class.provider_name ) end mcp_servers.each do |server| next if supported.include?(server.transport) raise McpTransportUnsupportedError.new( "Provider '#{self.class.provider_name}' does not support MCP transport " \ "'#{server.transport}' (server: '#{server.name}'). " \ "Supported transports: #{supported.join(", ")}", provider: self.class.provider_name ) end end |