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_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
75 76 77 |
# File 'lib/agent_harness/providers/adapter.rb', line 75 def self.included(base) base.extend(ClassMethods) end |
.metadata_package_name(contract, source) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/agent_harness/providers/adapter.rb', line 46 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 |
# 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 { 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(" ") } end |
.normalize_metadata_source_type(source) ⇒ Object
40 41 42 43 44 |
# File 'lib/agent_harness/providers/adapter.rb', line 40 def self.(source) return source[:type]&.to_sym if source.is_a?(Hash) source&.to_sym end |
.normalize_metadata_version_requirement(requirement) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/agent_harness/providers/adapter.rb', line 60 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).
1186 1187 1188 |
# File 'lib/agent_harness/providers/adapter.rb', line 1186 def auth_lock_config nil end |
#auth_type ⇒ Symbol
Authentication type for this provider
873 874 875 |
# File 'lib/agent_harness/providers/adapter.rb', line 873 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
906 907 908 |
# File 'lib/agent_harness/providers/adapter.rb', line 906 def build_mcp_flags(mcp_servers, working_dir: nil) [] end |
#capabilities ⇒ Hash
Provider capabilities
806 807 808 809 810 811 812 813 814 815 816 |
# File 'lib/agent_harness/providers/adapter.rb', line 806 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).
980 981 982 |
# File 'lib/agent_harness/providers/adapter.rb', line 980 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.
989 990 991 |
# File 'lib/agent_harness/providers/adapter.rb', line 989 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.
1168 1169 1170 |
# File 'lib/agent_harness/providers/adapter.rb', line 1168 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.
795 796 797 798 799 800 801 |
# File 'lib/agent_harness/providers/adapter.rb', line 795 def configuration_schema { fields: [], auth_modes: [auth_type], openai_compatible: false } end |
#dangerous_mode_flags ⇒ Array<String>
Get dangerous mode flags
1003 1004 1005 |
# File 'lib/agent_harness/providers/adapter.rb', line 1003 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.
833 834 835 836 837 838 839 840 841 842 843 844 845 846 |
# File 'lib/agent_harness/providers/adapter.rb', line 833 def error_classification_patterns { auth_expired: [], abort: [], authentication: [], quota: [ /requires more credits/i, /insufficient credits/i, /credit.*exceeded/i, /spend limit.*reached/i, /billing.*limit/i ] } end |
#error_patterns ⇒ Hash<Symbol, Array<Regexp>>
Error patterns for classification
821 822 823 |
# File 'lib/agent_harness/providers/adapter.rb', line 821 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.
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 |
# File 'lib/agent_harness/providers/adapter.rb', line 1136 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
887 888 889 |
# File 'lib/agent_harness/providers/adapter.rb', line 887 def fetch_mcp_servers [] end |
#health_status ⇒ Hash
Health check
1050 1051 1052 |
# File 'lib/agent_harness/providers/adapter.rb', line 1050 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.
1219 1220 1221 |
# File 'lib/agent_harness/providers/adapter.rb', line 1219 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.
854 855 856 |
# File 'lib/agent_harness/providers/adapter.rb', line 854 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.
1178 1179 1180 |
# File 'lib/agent_harness/providers/adapter.rb', line 1178 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.
1157 1158 1159 |
# File 'lib/agent_harness/providers/adapter.rb', line 1157 def parse_rate_limit_reset(output) nil end |
#plan_execution(prompt:, **options) ⇒ Hash
Return the provider CLI execution plan without executing the command.
783 784 785 |
# File 'lib/agent_harness/providers/adapter.rb', line 783 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.
1059 1060 1061 |
# File 'lib/agent_harness/providers/adapter.rb', line 1059 def preflight_check(env:, timeout: 10) {healthy: true} end |
#send_message(prompt:, **options) ⇒ Response
Send a message/prompt to the provider
774 775 776 |
# File 'lib/agent_harness/providers/adapter.rb', line 774 def (prompt:, **) raise NotImplementedError, "#{self.class} must implement #send_message" end |
#session_flags(session_id) ⇒ Array<String>
Get session flags for continuation
1018 1019 1020 |
# File 'lib/agent_harness/providers/adapter.rb', line 1018 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.
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 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 |
# File 'lib/agent_harness/providers/adapter.rb', line 1075 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., :rate_limited) rescue ProviderError => e failure_smoke_test_result(e., (e.)) end |
#smoke_test_contract ⇒ Hash?
Canonical smoke-test contract for this provider instance.
1066 1067 1068 |
# File 'lib/agent_harness/providers/adapter.rb', line 1066 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.
897 898 899 |
# File 'lib/agent_harness/providers/adapter.rb', line 897 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.
1198 1199 1200 |
# File 'lib/agent_harness/providers/adapter.rb', line 1198 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.
970 971 972 |
# File 'lib/agent_harness/providers/adapter.rb', line 970 def supports_chat? false end |
#supports_dangerous_mode? ⇒ Boolean
Check if provider supports dangerous mode
996 997 998 |
# File 'lib/agent_harness/providers/adapter.rb', line 996 def supports_dangerous_mode? capabilities[:dangerous_mode] end |
#supports_mcp? ⇒ Boolean
Check if provider supports MCP
880 881 882 |
# File 'lib/agent_harness/providers/adapter.rb', line 880 def supports_mcp? capabilities[:mcp] end |
#supports_sessions? ⇒ Boolean
Check if provider supports session continuation
1010 1011 1012 |
# File 'lib/agent_harness/providers/adapter.rb', line 1010 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.
960 961 962 |
# File 'lib/agent_harness/providers/adapter.rb', line 960 def supports_text_mode? false end |
#supports_token_counting? ⇒ Boolean
Whether this provider can extract token usage from CLI output
1036 1037 1038 |
# File 'lib/agent_harness/providers/adapter.rb', line 1036 def supports_token_counting? false end |
#supports_tool_control? ⇒ Boolean
Check if provider supports tool access control (disabling tools)
949 950 951 |
# File 'lib/agent_harness/providers/adapter.rb', line 949 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.
1029 1030 1031 |
# File 'lib/agent_harness/providers/adapter.rb', line 1029 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.
865 866 867 |
# File 'lib/agent_harness/providers/adapter.rb', line 865 def translate_error() end |
#validate_config ⇒ Hash
Validate provider configuration
1043 1044 1045 |
# File 'lib/agent_harness/providers/adapter.rb', line 1043 def validate_config {valid: true, errors: []} end |
#validate_mcp_servers!(mcp_servers) ⇒ Object
Validate that this provider can handle the given MCP servers
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 |
# File 'lib/agent_harness/providers/adapter.rb', line 915 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 |