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.
-
#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.
-
#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_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).
1162 1163 1164 |
# File 'lib/agent_harness/providers/adapter.rb', line 1162 def auth_lock_config nil end |
#auth_type ⇒ Symbol
Authentication type for this provider
859 860 861 |
# File 'lib/agent_harness/providers/adapter.rb', line 859 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
892 893 894 |
# File 'lib/agent_harness/providers/adapter.rb', line 892 def build_mcp_flags(mcp_servers, working_dir: nil) [] end |
#capabilities ⇒ Hash
Provider capabilities
792 793 794 795 796 797 798 799 800 801 802 |
# File 'lib/agent_harness/providers/adapter.rb', line 792 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).
966 967 968 |
# File 'lib/agent_harness/providers/adapter.rb', line 966 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.
975 976 977 |
# File 'lib/agent_harness/providers/adapter.rb', line 975 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.
1144 1145 1146 |
# File 'lib/agent_harness/providers/adapter.rb', line 1144 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.
781 782 783 784 785 786 787 |
# File 'lib/agent_harness/providers/adapter.rb', line 781 def configuration_schema { fields: [], auth_modes: [auth_type], openai_compatible: false } end |
#dangerous_mode_flags ⇒ Array<String>
Get dangerous mode flags
989 990 991 |
# File 'lib/agent_harness/providers/adapter.rb', line 989 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.
819 820 821 822 823 824 825 826 827 828 829 830 831 832 |
# File 'lib/agent_harness/providers/adapter.rb', line 819 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
807 808 809 |
# File 'lib/agent_harness/providers/adapter.rb', line 807 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.
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 |
# File 'lib/agent_harness/providers/adapter.rb', line 1112 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
873 874 875 |
# File 'lib/agent_harness/providers/adapter.rb', line 873 def fetch_mcp_servers [] end |
#health_status ⇒ Hash
Health check
1036 1037 1038 |
# File 'lib/agent_harness/providers/adapter.rb', line 1036 def health_status {healthy: true, message: "OK"} 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.
840 841 842 |
# File 'lib/agent_harness/providers/adapter.rb', line 840 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.
1154 1155 1156 |
# File 'lib/agent_harness/providers/adapter.rb', line 1154 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.
1133 1134 1135 |
# File 'lib/agent_harness/providers/adapter.rb', line 1133 def parse_rate_limit_reset(output) nil end |
#send_message(prompt:, **options) ⇒ Response
Send a message/prompt to the provider
769 770 771 |
# File 'lib/agent_harness/providers/adapter.rb', line 769 def (prompt:, **) raise NotImplementedError, "#{self.class} must implement #send_message" end |
#session_flags(session_id) ⇒ Array<String>
Get session flags for continuation
1004 1005 1006 |
# File 'lib/agent_harness/providers/adapter.rb', line 1004 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.
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 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 |
# File 'lib/agent_harness/providers/adapter.rb', line 1052 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 ) 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.
1043 1044 1045 |
# File 'lib/agent_harness/providers/adapter.rb', line 1043 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.
883 884 885 |
# File 'lib/agent_harness/providers/adapter.rb', line 883 def supported_mcp_transports %w[stdio] 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.
956 957 958 |
# File 'lib/agent_harness/providers/adapter.rb', line 956 def supports_chat? false end |
#supports_dangerous_mode? ⇒ Boolean
Check if provider supports dangerous mode
982 983 984 |
# File 'lib/agent_harness/providers/adapter.rb', line 982 def supports_dangerous_mode? capabilities[:dangerous_mode] end |
#supports_mcp? ⇒ Boolean
Check if provider supports MCP
866 867 868 |
# File 'lib/agent_harness/providers/adapter.rb', line 866 def supports_mcp? capabilities[:mcp] end |
#supports_sessions? ⇒ Boolean
Check if provider supports session continuation
996 997 998 |
# File 'lib/agent_harness/providers/adapter.rb', line 996 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.
946 947 948 |
# File 'lib/agent_harness/providers/adapter.rb', line 946 def supports_text_mode? false end |
#supports_token_counting? ⇒ Boolean
Whether this provider can extract token usage from CLI output
1022 1023 1024 |
# File 'lib/agent_harness/providers/adapter.rb', line 1022 def supports_token_counting? false end |
#supports_tool_control? ⇒ Boolean
Check if provider supports tool access control (disabling tools)
935 936 937 |
# File 'lib/agent_harness/providers/adapter.rb', line 935 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.
1015 1016 1017 |
# File 'lib/agent_harness/providers/adapter.rb', line 1015 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.
851 852 853 |
# File 'lib/agent_harness/providers/adapter.rb', line 851 def translate_error() end |
#validate_config ⇒ Hash
Validate provider configuration
1029 1030 1031 |
# File 'lib/agent_harness/providers/adapter.rb', line 1029 def validate_config {valid: true, errors: []} end |
#validate_mcp_servers!(mcp_servers) ⇒ Object
Validate that this provider can handle the given MCP servers
901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 |
# File 'lib/agent_harness/providers/adapter.rb', line 901 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 |