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_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
80 81 82 |
# File 'lib/agent_harness/providers/adapter.rb', line 80 def self.included(base) base.extend(ClassMethods) end |
.metadata_package_name(contract, source) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/agent_harness/providers/adapter.rb', line 51 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 |
# 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) normalized end |
.normalize_metadata_source_type(source) ⇒ Object
45 46 47 48 49 |
# File 'lib/agent_harness/providers/adapter.rb', line 45 def self.(source) return source[:type]&.to_sym if source.is_a?(Hash) source&.to_sym end |
.normalize_metadata_version_requirement(requirement) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/agent_harness/providers/adapter.rb', line 65 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).
1202 1203 1204 |
# File 'lib/agent_harness/providers/adapter.rb', line 1202 def auth_lock_config nil end |
#auth_type ⇒ Symbol
Authentication type for this provider
880 881 882 |
# File 'lib/agent_harness/providers/adapter.rb', line 880 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
913 914 915 |
# File 'lib/agent_harness/providers/adapter.rb', line 913 def build_mcp_flags(mcp_servers, working_dir: nil) [] end |
#capabilities ⇒ Hash
Provider capabilities
811 812 813 814 815 816 817 818 819 820 821 |
# File 'lib/agent_harness/providers/adapter.rb', line 811 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).
996 997 998 |
# File 'lib/agent_harness/providers/adapter.rb', line 996 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.
1005 1006 1007 |
# File 'lib/agent_harness/providers/adapter.rb', line 1005 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.
1184 1185 1186 |
# File 'lib/agent_harness/providers/adapter.rb', line 1184 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.
800 801 802 803 804 805 806 |
# File 'lib/agent_harness/providers/adapter.rb', line 800 def configuration_schema { fields: [], auth_modes: [auth_type], openai_compatible: false } end |
#dangerous_mode_flags ⇒ Array<String>
Get dangerous mode flags
1019 1020 1021 |
# File 'lib/agent_harness/providers/adapter.rb', line 1019 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.
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 |
# File 'lib/agent_harness/providers/adapter.rb', line 838 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
826 827 828 |
# File 'lib/agent_harness/providers/adapter.rb', line 826 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.
1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 |
# File 'lib/agent_harness/providers/adapter.rb', line 1152 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
894 895 896 |
# File 'lib/agent_harness/providers/adapter.rb', line 894 def fetch_mcp_servers [] end |
#health_status ⇒ Hash
Health check
1066 1067 1068 |
# File 'lib/agent_harness/providers/adapter.rb', line 1066 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.
1235 1236 1237 |
# File 'lib/agent_harness/providers/adapter.rb', line 1235 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.
861 862 863 |
# File 'lib/agent_harness/providers/adapter.rb', line 861 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.
1194 1195 1196 |
# File 'lib/agent_harness/providers/adapter.rb', line 1194 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.
1173 1174 1175 |
# File 'lib/agent_harness/providers/adapter.rb', line 1173 def parse_rate_limit_reset(output) nil end |
#plan_execution(prompt:, **options) ⇒ Hash
Return the provider CLI execution plan without executing the command.
788 789 790 |
# File 'lib/agent_harness/providers/adapter.rb', line 788 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.
1075 1076 1077 |
# File 'lib/agent_harness/providers/adapter.rb', line 1075 def preflight_check(env:, timeout: 10) {healthy: true} end |
#send_message(prompt:, **options) ⇒ Response
Send a message/prompt to the provider
779 780 781 |
# File 'lib/agent_harness/providers/adapter.rb', line 779 def (prompt:, **) raise NotImplementedError, "#{self.class} must implement #send_message" end |
#session_flags(session_id) ⇒ Array<String>
Get session flags for continuation
1034 1035 1036 |
# File 'lib/agent_harness/providers/adapter.rb', line 1034 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.
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 |
# File 'lib/agent_harness/providers/adapter.rb', line 1091 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.
1082 1083 1084 |
# File 'lib/agent_harness/providers/adapter.rb', line 1082 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.
904 905 906 |
# File 'lib/agent_harness/providers/adapter.rb', line 904 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.
1214 1215 1216 |
# File 'lib/agent_harness/providers/adapter.rb', line 1214 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.
986 987 988 |
# File 'lib/agent_harness/providers/adapter.rb', line 986 def supports_chat? false end |
#supports_dangerous_mode? ⇒ Boolean
Check if provider supports dangerous mode
1012 1013 1014 |
# File 'lib/agent_harness/providers/adapter.rb', line 1012 def supports_dangerous_mode? capabilities[:dangerous_mode] end |
#supports_mcp? ⇒ Boolean
Check if provider supports MCP
887 888 889 |
# File 'lib/agent_harness/providers/adapter.rb', line 887 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.
965 966 967 |
# File 'lib/agent_harness/providers/adapter.rb', line 965 def false end |
#supports_sessions? ⇒ Boolean
Check if provider supports session continuation
1026 1027 1028 |
# File 'lib/agent_harness/providers/adapter.rb', line 1026 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.
976 977 978 |
# File 'lib/agent_harness/providers/adapter.rb', line 976 def supports_text_mode? false end |
#supports_token_counting? ⇒ Boolean
Whether this provider can extract token usage from CLI output
1052 1053 1054 |
# File 'lib/agent_harness/providers/adapter.rb', line 1052 def supports_token_counting? false end |
#supports_tool_control? ⇒ Boolean
Check if provider supports tool access control (disabling tools)
956 957 958 |
# File 'lib/agent_harness/providers/adapter.rb', line 956 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.
1045 1046 1047 |
# File 'lib/agent_harness/providers/adapter.rb', line 1045 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.
872 873 874 |
# File 'lib/agent_harness/providers/adapter.rb', line 872 def translate_error() end |
#validate_config ⇒ Hash
Validate provider configuration
1059 1060 1061 |
# File 'lib/agent_harness/providers/adapter.rb', line 1059 def validate_config {valid: true, errors: []} end |
#validate_mcp_servers!(mcp_servers) ⇒ Object
Validate that this provider can handle the given MCP servers
922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 |
# File 'lib/agent_harness/providers/adapter.rb', line 922 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 |