agent-harness 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8162991fcf80a1cfe21b522abe909f615e87e56942b8261f5ee3cbb9a86f18c2
4
- data.tar.gz: 22f7b57522dc3f38dd73a5200cbf878ca72d0b22d889c29bfa58ca878725a705
3
+ metadata.gz: 3549cf974343fcdb961dea202dc22b84f820671a29f9139f1e36bb9c8a6363d4
4
+ data.tar.gz: a35417365aba964248a50cc44d543fd6e11e18a7c27946d7f7750cdfa6347cf0
5
5
  SHA512:
6
- metadata.gz: 1a9f60dc02f229765786bfe38c5cc84f489f52efb8d146f1a0595f9bc03d930b6e71f2194cfb4c86d24e191e1c5d184e7030addda5f1ab09a4a9098ff3a90a7f
7
- data.tar.gz: 7b4a8d87ceb151d28d867522afe69872f5a983df961b7c2a1dcccf07324c1ca8c6cc2e7ed1eb7b4dd129d7315917084a0e0da7d29c2ad4e032b6be29eda71fa2
6
+ metadata.gz: beb28e93b4f37ad2328f70fa4ec0e88677e0b2bf0bff2c2826fb3086aab4b9c0714692f8b17268ec3ecb58815f19021880237c6fff59821d73942cb18b008e5f
7
+ data.tar.gz: d12b1399447b77bf9b570b6791b7426d92e52d691b094f27329a118f1e1ba472daf794a27bc2150a0d8b9a47d825f668dc875f128ac2986e44236792c6d07d10
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.9.0"
2
+ ".": "0.10.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.10.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.9.0...agent-harness/v0.10.0) (2026-04-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * **codex:** expose JSONL transcript parser ([#148](https://github.com/viamin/agent-harness/issues/148)) ([05312ea](https://github.com/viamin/agent-harness/commit/05312eaf9c11fff50931e511ee6e534838eb8746))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **copilot:** github-copilot-cli does not support the -p flag used by build_command ([#141](https://github.com/viamin/agent-harness/issues/141)) ([d06fbc4](https://github.com/viamin/agent-harness/commit/d06fbc414489d6c3bc93a122d0eb2a5771ddbb26))
14
+
3
15
  ## [0.9.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.8.0...agent-harness/v0.9.0) (2026-04-19)
4
16
 
5
17
 
data/README.md CHANGED
@@ -501,6 +501,29 @@ AgentHarness.auth_status(:claude)
501
501
 
502
502
  For providers without a built-in auth check (including `:api_key` providers), `auth_valid?` returns `false` and `auth_status` returns an error indicating the check is not implemented. Custom providers can implement an `auth_status` instance method to provide their own check.
503
503
 
504
+ ### Auth Flow Capabilities
505
+
506
+ Before rendering provider-specific auth controls, check whether the flow is supported:
507
+
508
+ ```ruby
509
+ AgentHarness.auth_url_supported?(:claude)
510
+ # => true
511
+
512
+ AgentHarness.auth_url_supported?(:codex)
513
+ # => false
514
+
515
+ AgentHarness.refresh_auth_supported?(:claude)
516
+ # => true
517
+
518
+ AgentHarness.refresh_auth_supported?(:codex)
519
+ # => false
520
+
521
+ AgentHarness.auth_capabilities(:codex)
522
+ # => { auth_type: :api_key, auth_url: false, refresh: false }
523
+ ```
524
+
525
+ Provider aliases are resolved the same way as other auth APIs, so `:anthropic` reports the same capabilities as `:claude`. Unknown providers raise `AgentHarness::ProviderNotFoundError`, matching `auth_url` and `refresh_auth` provider lookup behavior.
526
+
504
527
  ### Auth Error Detection
505
528
 
506
529
  When a CLI agent fails due to expired or invalid authentication, `send_message` raises `AuthenticationError` with the provider name. Authentication errors are always surfaced directly to the caller (never auto-switched to another provider) so your application can trigger the appropriate re-auth flow:
@@ -509,9 +532,15 @@ When a CLI agent fails due to expired or invalid authentication, `send_message`
509
532
  begin
510
533
  AgentHarness.send_message("Hello", provider: :claude)
511
534
  rescue AgentHarness::AuthenticationError => e
512
- puts e.provider # => :claude
513
- puts e.message # => "oauth token expired"
514
- # Trigger re-authentication flow for the specific provider
535
+ provider = e.provider
536
+
537
+ if AgentHarness.auth_url_supported?(provider)
538
+ redirect_to AgentHarness.auth_url(provider)
539
+ elsif AgentHarness.refresh_auth_supported?(provider)
540
+ render :reauth_token_form, locals: { provider: provider }
541
+ else
542
+ render :auth_expired_without_refresh, locals: { provider: provider, message: e.message }
543
+ end
515
544
  end
516
545
  ```
517
546
 
@@ -524,7 +553,7 @@ AgentHarness.auth_url(:claude)
524
553
  # => "https://claude.ai/oauth/authorize"
525
554
  ```
526
555
 
527
- This raises `NotImplementedError` for `:api_key` providers.
556
+ This raises `AgentHarness::UnsupportedAuthFlowError` for `:api_key` providers or providers whose OAuth URL flow is not implemented. The exception inherits from `AgentHarness::Error` and `StandardError`, so host applications can rescue it with their normal app-level error handling.
528
557
 
529
558
  ### Credential Refresh
530
559
 
@@ -537,7 +566,9 @@ AgentHarness.refresh_auth(:claude, token: "new-oauth-token")
537
566
 
538
567
  Any existing expiry metadata in the credentials file is cleared on refresh so that `auth_valid?` returns `true` immediately after a successful refresh.
539
568
 
540
- This raises `NotImplementedError` for `:api_key` providers. Credential file paths respect the `CLAUDE_CONFIG_DIR` environment variable.
569
+ This raises `AgentHarness::UnsupportedAuthFlowError` for `:api_key` providers or providers whose credential refresh flow is not implemented. Credential file paths respect the `CLAUDE_CONFIG_DIR` environment variable.
570
+
571
+ If you currently rescue `NotImplementedError` for unsupported auth URL generation or credential refresh, update that code to rescue `AgentHarness::UnsupportedAuthFlowError` or the broader `AgentHarness::Error` instead.
541
572
 
542
573
  ## Provider Health Checks
543
574
 
@@ -35,19 +35,46 @@ module AgentHarness
35
35
  end
36
36
  end
37
37
 
38
+ # Get authentication flow capabilities for a provider.
39
+ #
40
+ # @param provider_name [Symbol] the provider name
41
+ # @return [Hash] capabilities with :auth_type, :auth_url, :refresh keys
42
+ # @raise [ProviderNotFoundError] if provider is unknown
43
+ def auth_capabilities(provider_name)
44
+ provider_name = provider_name.to_sym
45
+ provider = resolve_provider(provider_name)
46
+ canonical_name = Providers::Registry.instance.canonical_name(provider_name)
47
+ flow_supported = claude_oauth_flow_provider?(provider_name, canonical_name)
48
+
49
+ {
50
+ auth_type: provider.auth_type,
51
+ auth_url: flow_supported,
52
+ refresh: flow_supported
53
+ }
54
+ end
55
+
56
+ # Check whether OAuth URL generation is supported for a provider.
57
+ #
58
+ # @param provider_name [Symbol] the provider name
59
+ # @return [Boolean] true if auth_url can be called for the provider
60
+ # @raise [ProviderNotFoundError] if provider is unknown
61
+ def auth_url_supported?(provider_name)
62
+ auth_capabilities(provider_name)[:auth_url]
63
+ end
64
+
38
65
  # Generate an OAuth URL for a provider
39
66
  #
40
67
  # Only supported for :oauth auth type providers.
41
68
  #
42
69
  # @param provider_name [Symbol] the provider name
43
70
  # @return [String] the OAuth authorization URL
44
- # @raise [NotImplementedError] if provider doesn't support OAuth
71
+ # @raise [UnsupportedAuthFlowError] if provider doesn't support OAuth
45
72
  def auth_url(provider_name)
46
73
  provider_name = provider_name.to_sym
47
74
  provider = resolve_provider(provider_name)
48
75
 
49
76
  unless provider.auth_type == :oauth
50
- raise NotImplementedError,
77
+ raise UnsupportedAuthFlowError,
51
78
  "Provider #{provider_name} uses #{provider.auth_type} auth and does not support OAuth URL generation"
52
79
  end
53
80
 
@@ -55,29 +82,38 @@ module AgentHarness
55
82
  when :claude, :anthropic
56
83
  claude_auth_url
57
84
  else
58
- raise NotImplementedError,
85
+ raise UnsupportedAuthFlowError,
59
86
  "OAuth URL generation is not yet implemented for provider #{provider_name}"
60
87
  end
61
88
  end
62
89
 
90
+ # Check whether credential refresh is supported for a provider.
91
+ #
92
+ # @param provider_name [Symbol] the provider name
93
+ # @return [Boolean] true if refresh_auth can be called for the provider
94
+ # @raise [ProviderNotFoundError] if provider is unknown
95
+ def refresh_auth_supported?(provider_name)
96
+ auth_capabilities(provider_name)[:refresh]
97
+ end
98
+
63
99
  # Refresh authentication credentials for a provider
64
100
  #
65
101
  # For OAuth providers, stores a pre-exchanged token directly.
66
102
  # This method accepts a token (not an authorization code) because
67
103
  # the OAuth code-exchange flow is provider-specific and should be
68
104
  # handled by the caller or a CLI login command before calling this.
69
- # For API key providers, raises NotImplementedError.
105
+ # For API key providers, raises UnsupportedAuthFlowError.
70
106
  #
71
107
  # @param provider_name [Symbol] the provider name
72
108
  # @param token [String] OAuth token to store (must be non-blank)
73
109
  # @return [Hash] result with :success key
74
- # @raise [NotImplementedError] if provider doesn't support credential refresh
110
+ # @raise [UnsupportedAuthFlowError] if provider doesn't support credential refresh
75
111
  def refresh_auth(provider_name, token: nil)
76
112
  provider_name = provider_name.to_sym
77
113
  provider = resolve_provider(provider_name)
78
114
 
79
115
  unless provider.auth_type == :oauth
80
- raise NotImplementedError,
116
+ raise UnsupportedAuthFlowError,
81
117
  "Provider #{provider_name} uses #{provider.auth_type} auth and does not support credential refresh"
82
118
  end
83
119
 
@@ -85,13 +121,17 @@ module AgentHarness
85
121
  when :claude, :anthropic
86
122
  refresh_claude_auth(token: token)
87
123
  else
88
- raise NotImplementedError,
124
+ raise UnsupportedAuthFlowError,
89
125
  "Credential refresh is not yet implemented for provider #{provider_name}"
90
126
  end
91
127
  end
92
128
 
93
129
  private
94
130
 
131
+ def claude_oauth_flow_provider?(requested_name, canonical_name)
132
+ [:claude, :anthropic].include?(requested_name) || canonical_name == :claude
133
+ end
134
+
95
135
  def resolve_provider(provider_name)
96
136
  klass = Providers::Registry.instance.get(provider_name)
97
137
  canonical_name = Providers::Registry.instance.canonical_name(provider_name)
@@ -66,6 +66,9 @@ module AgentHarness
66
66
  # subscription to API-metered usage.
67
67
  class AuthMismatchError < AuthenticationError; end
68
68
 
69
+ # Raised when a provider does not support the requested authentication flow.
70
+ class UnsupportedAuthFlowError < Error; end
71
+
69
72
  # Configuration errors
70
73
  class ConfigurationError < Error; end
71
74
 
@@ -139,6 +139,28 @@ module AgentHarness
139
139
  def smoke_test_contract
140
140
  Base::DEFAULT_SMOKE_TEST_CONTRACT
141
141
  end
142
+
143
+ def parse_cli_jsonl_transcript(raw_output, max_events: nil)
144
+ return new.send(:parse_jsonl_output, "") if max_events && max_events <= 0
145
+
146
+ output = max_events ? tail_nonempty_lines(raw_output, limit: max_events).join("\n") : raw_output
147
+
148
+ new.send(:parse_jsonl_output, output)
149
+ end
150
+
151
+ private
152
+
153
+ def tail_nonempty_lines(text, limit:)
154
+ return [] if limit <= 0
155
+
156
+ text.to_s.each_line.each_with_object([]) do |line, lines|
157
+ stripped = line.strip
158
+ next if stripped.empty?
159
+
160
+ lines.shift if lines.size >= limit
161
+ lines << stripped
162
+ end
163
+ end
142
164
  end
143
165
 
144
166
  def name
@@ -603,10 +625,11 @@ module AgentHarness
603
625
  when "turn.completed"
604
626
  turn_usage = build_token_usage(event["usage"])
605
627
  result = event["result"]
628
+ result_parts = result.is_a?(String) ? [result] : extract_task_complete_parts(event)
606
629
  wrapped_completion_without_new_output =
607
630
  pending_turn_usage_source == :wrapped &&
608
631
  pending_turn_usage &&
609
- !result.is_a?(String) &&
632
+ result_parts.nil? &&
610
633
  (turn_usage.nil? || current_turn_parts.empty? || current_turn_parts.equal?(pending_wrapped_output_parts))
611
634
 
612
635
  if wrapped_completion_without_new_output
@@ -663,8 +686,8 @@ module AgentHarness
663
686
  pending_wrapped_same_turn_finalization = false
664
687
  end
665
688
 
666
- if result.is_a?(String)
667
- current_turn_parts = [result]
689
+ if result_parts
690
+ current_turn_parts = result_parts
668
691
  saw_assistant_output = true
669
692
  current_turn_finalized_output = true
670
693
  end
@@ -8,12 +8,12 @@ module AgentHarness
8
8
  class GithubCopilot < Base
9
9
  include TokenUsageParsing
10
10
 
11
- PACKAGE_NAME = "@githubnext/github-copilot-cli"
12
- SUPPORTED_CLI_VERSION = "0.1.36"
13
- SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 0.2.0").freeze
14
-
15
11
  MODEL_PATTERN = /^gpt-[\d.o-]+(?:-turbo)?(?:-mini)?$/i
16
12
  JSON_OUTPUT_MIN_VERSION = Gem::Version.new("0.0.422").freeze
13
+ SUBCOMMAND_CLI_MIN_VERSION = Gem::Version.new("0.1.0").freeze
14
+ UNSUPPORTED_SUBCOMMAND_CLI_MESSAGE =
15
+ "github-copilot-cli 0.1.x does not expose a non-interactive send interface; " \
16
+ "the what-the-shell subcommand is interactive and cannot be used by AgentHarness."
17
17
 
18
18
  SMOKE_TEST_CONTRACT = {
19
19
  prompt: "Reply with exactly OK.",
@@ -34,41 +34,22 @@ module AgentHarness
34
34
 
35
35
  def available?
36
36
  executor = AgentHarness.configuration.command_executor
37
- !!executor.which(binary_name)
38
- end
39
-
40
- def installation_contract(version: SUPPORTED_CLI_VERSION)
41
- version = version.strip if version.respond_to?(:strip)
42
- validate_install_version!(version)
43
- package_spec = "#{PACKAGE_NAME}@#{version}".freeze
44
- install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
45
- install_command = (install_command_prefix + [package_spec]).freeze
46
- version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
47
- .map { |op, ver| "#{op} #{ver}".freeze }
48
- .freeze
49
-
50
- contract = {
51
- source: {
52
- type: :npm,
53
- package: PACKAGE_NAME
54
- }.freeze,
55
- install_command_prefix: install_command_prefix,
56
- install_command: install_command,
57
- binary_name: binary_name,
58
- default_version: SUPPORTED_CLI_VERSION,
59
- version: version,
60
- version_requirement: version_requirement,
61
- supported_version_requirement: SUPPORTED_CLI_REQUIREMENT.to_s
62
- }
37
+ return false unless executor.which(binary_name)
63
38
 
64
- contract.each_value do |value|
65
- value.freeze if value.is_a?(String)
66
- end
67
- contract.freeze
39
+ !subcommand_cli_version?(copilot_cli_version(executor: executor))
40
+ rescue
41
+ false
68
42
  end
69
43
 
70
- def install_command(version: SUPPORTED_CLI_VERSION)
71
- installation_contract(version: version)[:install_command]
44
+ def installation_contract(version: nil)
45
+ # The published @githubnext/github-copilot-cli package only has
46
+ # 0.1.x releases, and those expose an interactive subcommand instead
47
+ # of the non-interactive -p prompt path AgentHarness uses.
48
+ nil
49
+ end
50
+
51
+ def install_command(version: nil)
52
+ installation_contract(version: version)&.fetch(:install_command)
72
53
  end
73
54
 
74
55
  def provider_metadata_overrides
@@ -134,26 +115,26 @@ module AgentHarness
134
115
 
135
116
  private
136
117
 
137
- def validate_install_version!(version)
138
- unless version.is_a?(String) && !version.strip.empty?
139
- raise ArgumentError,
140
- "Unsupported GitHub Copilot CLI version #{version.inspect}; " \
141
- "supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
142
- end
118
+ def copilot_cli_version(executor:)
119
+ result = executor.execute([binary_name, "--version"], timeout: 5, env: {})
120
+ extract_version(result)
121
+ rescue
122
+ nil
123
+ end
143
124
 
144
- parsed_version = begin
145
- Gem::Version.new(version)
146
- rescue ArgumentError
147
- raise ArgumentError,
148
- "Unsupported GitHub Copilot CLI version #{version.inspect}; " \
149
- "supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
150
- end
125
+ def subcommand_cli_version?(version)
126
+ !version.nil? && version >= SUBCOMMAND_CLI_MIN_VERSION
127
+ end
151
128
 
152
- return if SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
129
+ def extract_version(result)
130
+ return nil unless result.success?
153
131
 
154
- raise ArgumentError,
155
- "Unsupported GitHub Copilot CLI version #{version.inspect}; " \
156
- "supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
132
+ version_string = [result.stdout, result.stderr].compact.join("\n")[/\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?/]
133
+ return nil if version_string.nil? || version_string.empty?
134
+
135
+ Gem::Version.new(version_string)
136
+ rescue ArgumentError
137
+ nil
157
138
  end
158
139
  end
159
140
 
@@ -194,18 +175,22 @@ module AgentHarness
194
175
  }
195
176
  end
196
177
 
197
- def dangerous_mode_flags(probe_timeout: nil, env: {})
198
- return [] unless supports_json_output_format?(probe_timeout: probe_timeout, env: env)
178
+ def dangerous_mode_flags(probe_timeout: nil, env: {}, version: nil)
179
+ version ||= copilot_cli_version(probe_timeout: probe_timeout, env: env)
180
+ return [] if subcommand_cli_version?(version)
181
+ return [] unless supports_json_output_format?(version: version)
199
182
 
200
183
  ["--allow-all"]
201
184
  end
202
185
 
203
- def supports_sessions?
204
- true
186
+ def supports_sessions?(probe_timeout: nil, env: {}, version: nil)
187
+ legacy_prompt_cli?(version: version, probe_timeout: probe_timeout, env: env)
205
188
  end
206
189
 
207
- def session_flags(session_id)
190
+ def session_flags(session_id, version: nil, probe_timeout: nil, env: {})
208
191
  return [] unless session_id && !session_id.empty?
192
+ return [] unless legacy_prompt_cli?(version: version, probe_timeout: probe_timeout, env: env)
193
+
209
194
  ["--resume", session_id]
210
195
  end
211
196
 
@@ -221,7 +206,7 @@ module AgentHarness
221
206
  output_format: :text,
222
207
  sandbox_aware: false,
223
208
  uses_subcommand: false,
224
- non_interactive_flag: "-p",
209
+ non_interactive_flag: nil,
225
210
  legitimate_exit_codes: [0],
226
211
  stderr_is_diagnostic: true,
227
212
  parses_rate_limit_reset: false
@@ -324,11 +309,15 @@ module AgentHarness
324
309
  protected
325
310
 
326
311
  def build_command(prompt, options)
327
- cmd = [self.class.binary_name, "-p", prompt]
328
312
  env = options.fetch(:_command_env) { build_env(options) }
329
313
  runtime = options[:provider_runtime]
314
+ version = copilot_cli_version(probe_timeout: options[:_version_probe_timeout], env: env)
330
315
 
331
- if supports_json_output_format?(probe_timeout: options[:_version_probe_timeout], env: env)
316
+ raise unsupported_subcommand_cli_error if subcommand_cli_version?(version)
317
+
318
+ cmd = [self.class.binary_name, "-p", prompt]
319
+
320
+ if supports_json_output_format?(version: version)
332
321
  cmd += ["--output-format", "json"]
333
322
  else
334
323
  # Silent mode suppresses the model/stats decoration older CLIs print in
@@ -340,11 +329,11 @@ module AgentHarness
340
329
  cmd += ["--model", model] if model
341
330
  if options[:dangerous_mode] && supports_dangerous_mode?
342
331
  cmd += programmatic_tool_approval_flags
343
- cmd += dangerous_mode_flags(probe_timeout: options[:_version_probe_timeout], env: env)
332
+ cmd += dangerous_mode_flags(version: version)
344
333
  end
345
334
 
346
335
  if options[:session] && !options[:session].empty?
347
- cmd += session_flags(options[:session])
336
+ cmd += session_flags(options[:session], version: version)
348
337
  end
349
338
 
350
339
  cmd
@@ -385,9 +374,22 @@ module AgentHarness
385
374
  ["--allow-all-tools"]
386
375
  end
387
376
 
388
- def supports_json_output_format?(probe_timeout: nil, env: {})
389
- version = copilot_cli_version(probe_timeout: probe_timeout, env: env)
390
- !version.nil? && version >= JSON_OUTPUT_MIN_VERSION
377
+ def supports_json_output_format?(probe_timeout: nil, env: {}, version: nil)
378
+ version ||= copilot_cli_version(probe_timeout: probe_timeout, env: env)
379
+ !version.nil? && !subcommand_cli_version?(version) && version >= JSON_OUTPUT_MIN_VERSION
380
+ end
381
+
382
+ def legacy_prompt_cli?(probe_timeout: nil, env: {}, version: nil)
383
+ version ||= copilot_cli_version(probe_timeout: probe_timeout, env: env)
384
+ !version.nil? && !subcommand_cli_version?(version)
385
+ end
386
+
387
+ def subcommand_cli_version?(version)
388
+ self.class.send(:subcommand_cli_version?, version)
389
+ end
390
+
391
+ def unsupported_subcommand_cli_error
392
+ ProviderError.new(UNSUPPORTED_SUBCOMMAND_CLI_MESSAGE)
391
393
  end
392
394
 
393
395
  def copilot_cli_version(probe_timeout: nil, env: {})
@@ -443,14 +445,7 @@ module AgentHarness
443
445
  end
444
446
 
445
447
  def extract_version(result)
446
- return nil unless result.success?
447
-
448
- version_string = [result.stdout, result.stderr].compact.join("\n")[/\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?/]
449
- return nil if version_string.nil? || version_string.empty?
450
-
451
- Gem::Version.new(version_string)
452
- rescue ArgumentError
453
- nil
448
+ self.class.send(:extract_version, result)
454
449
  end
455
450
 
456
451
  def parse_jsonl_output(output)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AgentHarness
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  end
data/lib/agent_harness.rb CHANGED
@@ -184,19 +184,43 @@ module AgentHarness
184
184
  Authentication.auth_status(provider_name)
185
185
  end
186
186
 
187
+ # Get authentication flow capabilities for a provider
188
+ # @param provider_name [Symbol] the provider name
189
+ # @return [Hash] capabilities with :auth_type, :auth_url, :refresh keys
190
+ # @raise [ProviderNotFoundError] if provider is unknown
191
+ def auth_capabilities(provider_name)
192
+ Authentication.auth_capabilities(provider_name)
193
+ end
194
+
195
+ # Check whether OAuth URL generation is supported for a provider
196
+ # @param provider_name [Symbol] the provider name
197
+ # @return [Boolean] true if auth_url can be called for the provider
198
+ # @raise [ProviderNotFoundError] if provider is unknown
199
+ def auth_url_supported?(provider_name)
200
+ Authentication.auth_url_supported?(provider_name)
201
+ end
202
+
187
203
  # Generate an OAuth URL for a provider
188
204
  # @param provider_name [Symbol] the provider name
189
205
  # @return [String] the OAuth authorization URL
190
- # @raise [NotImplementedError] if provider doesn't support OAuth
206
+ # @raise [UnsupportedAuthFlowError] if provider doesn't support OAuth
191
207
  def auth_url(provider_name)
192
208
  Authentication.auth_url(provider_name)
193
209
  end
194
210
 
211
+ # Check whether credential refresh is supported for a provider
212
+ # @param provider_name [Symbol] the provider name
213
+ # @return [Boolean] true if refresh_auth can be called for the provider
214
+ # @raise [ProviderNotFoundError] if provider is unknown
215
+ def refresh_auth_supported?(provider_name)
216
+ Authentication.refresh_auth_supported?(provider_name)
217
+ end
218
+
195
219
  # Refresh authentication credentials for a provider
196
220
  # @param provider_name [Symbol] the provider name
197
221
  # @param token [String, nil] OAuth token to store
198
222
  # @return [Hash] result with :success key
199
- # @raise [NotImplementedError] if provider doesn't support credential refresh
223
+ # @raise [UnsupportedAuthFlowError] if provider doesn't support credential refresh
200
224
  def refresh_auth(provider_name, token: nil)
201
225
  Authentication.refresh_auth(provider_name, token: token)
202
226
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agent-harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan