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.

Examples:

Implementing a provider

class MyProvider < AgentHarness::Providers::Base
  include AgentHarness::Providers::Adapter

  def self.provider_name
    :my_provider
  end
end

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

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_typeSymbol

Authentication type for this provider

Returns:

  • (Symbol)

    :oauth for token-based auth that can expire, :api_key for static API key auth



772
773
774
# File 'lib/agent_harness/providers/adapter.rb', line 772

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

Parameters:

  • mcp_servers (Array<McpServer>)

    MCP server definitions

  • working_dir (String, nil) (defaults to: nil)

    working directory for temp files

Returns:

  • (Array<String>)

    CLI flags to append to the command



802
803
804
# File 'lib/agent_harness/providers/adapter.rb', line 802

def build_mcp_flags(mcp_servers, working_dir: nil)
  []
end

#capabilitiesHash

Provider capabilities

Returns:

  • (Hash)

    capability flags



749
750
751
752
753
754
755
756
757
758
759
# File 'lib/agent_harness/providers/adapter.rb', line 749

def capabilities
  {
    streaming: false,
    file_upload: false,
    vision: false,
    tool_use: false,
    json_mode: false,
    mcp: false,
    dangerous_mode: false
  }
end

#configuration_schemaHash

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.

Returns:

  • (Hash)

    with :fields, :auth_modes, :openai_compatible keys



738
739
740
741
742
743
744
# File 'lib/agent_harness/providers/adapter.rb', line 738

def configuration_schema
  {
    fields: [],
    auth_modes: [auth_type],
    openai_compatible: false
  }
end

#dangerous_mode_flagsArray<String>

Get dangerous mode flags

Returns:

  • (Array<String>)

    CLI flags for dangerous mode



852
853
854
# File 'lib/agent_harness/providers/adapter.rb', line 852

def dangerous_mode_flags
  []
end

#error_patternsHash<Symbol, Array<Regexp>>

Error patterns for classification

Returns:

  • (Hash<Symbol, Array<Regexp>>)

    error patterns by category



764
765
766
# File 'lib/agent_harness/providers/adapter.rb', line 764

def error_patterns
  {}
end

#execution_semanticsHash

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.

Returns:

  • (Hash)

    execution semantics



957
958
959
960
961
962
963
964
965
966
967
968
# File 'lib/agent_harness/providers/adapter.rb', line 957

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_serversArray<Hash>

Fetch configured MCP servers

Returns:

  • (Array<Hash>)

    MCP server configurations



786
787
788
# File 'lib/agent_harness/providers/adapter.rb', line 786

def fetch_mcp_servers
  []
end

#health_statusHash

Health check

Returns:

  • (Hash)

    with :healthy, :message keys



881
882
883
# File 'lib/agent_harness/providers/adapter.rb', line 881

def health_status
  {healthy: true, message: "OK"}
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.

Parameters:

  • output (String)

    combined stdout+stderr from the CLI

Returns:

  • (Time, nil)

    when the rate limit resets, or nil if unknown



978
979
980
# File 'lib/agent_harness/providers/adapter.rb', line 978

def parse_rate_limit_reset(output)
  nil
end

#send_message(prompt:, **options) ⇒ Response

Send a message/prompt to the provider

Parameters:

  • prompt (String)

    the prompt to send

  • options (Hash)

    provider-specific options

Options Hash (**options):

  • :model (String)

    model to use

  • :timeout (Integer)

    timeout in seconds

  • :session (String)

    session identifier

  • :dangerous_mode (Boolean)

    skip permission checks

  • :provider_runtime (ProviderRuntime, Hash, nil)

    per-request runtime overrides (model, base_url, api_provider, env, flags, metadata). For providers that delegate to Providers::Base#send_message, a plain Hash is automatically coerced into a ProviderRuntime. Providers that override #send_message directly are responsible for handling this option.

Returns:

  • (Response)

    response object with output and metadata

Raises:

  • (NotImplementedError)


726
727
728
# File 'lib/agent_harness/providers/adapter.rb', line 726

def send_message(prompt:, **options)
  raise NotImplementedError, "#{self.class} must implement #send_message"
end

#session_flags(session_id) ⇒ Array<String>

Get session flags for continuation

Parameters:

  • session_id (String)

    the session ID

Returns:

  • (Array<String>)

    CLI flags for session continuation



867
868
869
# File 'lib/agent_harness/providers/adapter.rb', line 867

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.

Parameters:

  • timeout (Integer, nil) (defaults to: nil)

    timeout override in seconds

  • provider_runtime (ProviderRuntime, Hash, nil) (defaults to: nil)

    runtime overrides

Returns:

  • (Hash)

    normalized smoke-test result



897
898
899
900
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
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'lib/agent_harness/providers/adapter.rb', line 897

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 = send_message(
    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

  message = response.error.to_s.strip
  message = output if message.empty?
  message = "Smoke test failed with exit code #{response.exit_code}" if message.empty?

  {
    ok: false,
    status: "error",
    message: message,
    error_category: classify_smoke_test_message(message),
    output: output,
    exit_code: response.exit_code
  }
rescue TimeoutError => e
  failure_smoke_test_result(e.message, :timeout)
rescue AuthenticationError => e
  failure_smoke_test_result(e.message, :auth_expired)
rescue RateLimitError => e
  failure_smoke_test_result(e.message, :rate_limited)
rescue ProviderError => e
  failure_smoke_test_result(e.message, classify_smoke_test_message(e.message))
end

#smoke_test_contractHash?

Canonical smoke-test contract for this provider instance.

Returns:

  • (Hash, nil)

    smoke-test metadata



888
889
890
# File 'lib/agent_harness/providers/adapter.rb', line 888

def smoke_test_contract
  self.class.smoke_test_contract if self.class.respond_to?(:smoke_test_contract)
end

#supported_mcp_transportsArray<String>

Supported MCP transport types for this provider

Returns:

  • (Array<String>)

    supported transports (e.g. [“stdio”, “http”])



793
794
795
# File 'lib/agent_harness/providers/adapter.rb', line 793

def supported_mcp_transports
  []
end

#supports_dangerous_mode?Boolean

Check if provider supports dangerous mode

Returns:

  • (Boolean)

    true if dangerous mode is supported



845
846
847
# File 'lib/agent_harness/providers/adapter.rb', line 845

def supports_dangerous_mode?
  capabilities[:dangerous_mode]
end

#supports_mcp?Boolean

Check if provider supports MCP

Returns:

  • (Boolean)

    true if MCP is supported



779
780
781
# File 'lib/agent_harness/providers/adapter.rb', line 779

def supports_mcp?
  capabilities[:mcp]
end

#supports_sessions?Boolean

Check if provider supports session continuation

Returns:

  • (Boolean)

    true if sessions are supported



859
860
861
# File 'lib/agent_harness/providers/adapter.rb', line 859

def supports_sessions?
  false
end

#validate_configHash

Validate provider configuration

Returns:

  • (Hash)

    with :valid, :errors keys



874
875
876
# File 'lib/agent_harness/providers/adapter.rb', line 874

def validate_config
  {valid: true, errors: []}
end

#validate_mcp_servers!(mcp_servers) ⇒ Object

Validate that this provider can handle the given MCP servers

Parameters:

  • mcp_servers (Array<McpServer>)

    MCP server definitions

Raises:



811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
# File 'lib/agent_harness/providers/adapter.rb', line 811

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