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
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).
1197 1198 1199 |
# File 'lib/agent_harness/providers/adapter.rb', line 1197 def auth_lock_config nil end |
#auth_type ⇒ Symbol
Authentication type for this provider
875 876 877 |
# File 'lib/agent_harness/providers/adapter.rb', line 875 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
908 909 910 |
# File 'lib/agent_harness/providers/adapter.rb', line 908 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).
991 992 993 |
# File 'lib/agent_harness/providers/adapter.rb', line 991 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.
1000 1001 1002 |
# File 'lib/agent_harness/providers/adapter.rb', line 1000 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.
1179 1180 1181 |
# File 'lib/agent_harness/providers/adapter.rb', line 1179 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
1014 1015 1016 |
# File 'lib/agent_harness/providers/adapter.rb', line 1014 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 847 848 |
# 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, /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
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.
1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 |
# File 'lib/agent_harness/providers/adapter.rb', line 1147 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
889 890 891 |
# File 'lib/agent_harness/providers/adapter.rb', line 889 def fetch_mcp_servers [] end |
#health_status ⇒ Hash
Health check
1061 1062 1063 |
# File 'lib/agent_harness/providers/adapter.rb', line 1061 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.
1230 1231 1232 |
# File 'lib/agent_harness/providers/adapter.rb', line 1230 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.
856 857 858 |
# File 'lib/agent_harness/providers/adapter.rb', line 856 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.
1189 1190 1191 |
# File 'lib/agent_harness/providers/adapter.rb', line 1189 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.
1168 1169 1170 |
# File 'lib/agent_harness/providers/adapter.rb', line 1168 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.
1070 1071 1072 |
# File 'lib/agent_harness/providers/adapter.rb', line 1070 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
1029 1030 1031 |
# File 'lib/agent_harness/providers/adapter.rb', line 1029 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.
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 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 |
# File 'lib/agent_harness/providers/adapter.rb', line 1086 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.
1077 1078 1079 |
# File 'lib/agent_harness/providers/adapter.rb', line 1077 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.
899 900 901 |
# File 'lib/agent_harness/providers/adapter.rb', line 899 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.
1209 1210 1211 |
# File 'lib/agent_harness/providers/adapter.rb', line 1209 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.
981 982 983 |
# File 'lib/agent_harness/providers/adapter.rb', line 981 def supports_chat? false end |
#supports_dangerous_mode? ⇒ Boolean
Check if provider supports dangerous mode
1007 1008 1009 |
# File 'lib/agent_harness/providers/adapter.rb', line 1007 def supports_dangerous_mode? capabilities[:dangerous_mode] end |
#supports_mcp? ⇒ Boolean
Check if provider supports MCP
882 883 884 |
# File 'lib/agent_harness/providers/adapter.rb', line 882 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.
960 961 962 |
# File 'lib/agent_harness/providers/adapter.rb', line 960 def false end |
#supports_sessions? ⇒ Boolean
Check if provider supports session continuation
1021 1022 1023 |
# File 'lib/agent_harness/providers/adapter.rb', line 1021 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.
971 972 973 |
# File 'lib/agent_harness/providers/adapter.rb', line 971 def supports_text_mode? false end |
#supports_token_counting? ⇒ Boolean
Whether this provider can extract token usage from CLI output
1047 1048 1049 |
# File 'lib/agent_harness/providers/adapter.rb', line 1047 def supports_token_counting? false end |
#supports_tool_control? ⇒ Boolean
Check if provider supports tool access control (disabling tools)
951 952 953 |
# File 'lib/agent_harness/providers/adapter.rb', line 951 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.
1040 1041 1042 |
# File 'lib/agent_harness/providers/adapter.rb', line 1040 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.
867 868 869 |
# File 'lib/agent_harness/providers/adapter.rb', line 867 def translate_error() end |
#validate_config ⇒ Hash
Validate provider configuration
1054 1055 1056 |
# File 'lib/agent_harness/providers/adapter.rb', line 1054 def validate_config {valid: true, errors: []} end |
#validate_mcp_servers!(mcp_servers) ⇒ Object
Validate that this provider can handle the given MCP servers
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 945 946 |
# File 'lib/agent_harness/providers/adapter.rb', line 917 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 |