arcp 1.0.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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +31 -0
  3. data/CONFORMANCE.md +71 -0
  4. data/LICENSE +202 -0
  5. data/README.md +135 -0
  6. data/lib/arcp/auth/auth_scheme.rb +16 -0
  7. data/lib/arcp/auth/bearer.rb +38 -0
  8. data/lib/arcp/auth.rb +4 -0
  9. data/lib/arcp/client.rb +354 -0
  10. data/lib/arcp/clock.rb +35 -0
  11. data/lib/arcp/credential.rb +65 -0
  12. data/lib/arcp/credential_provisioner.rb +132 -0
  13. data/lib/arcp/envelope.rb +115 -0
  14. data/lib/arcp/errors.rb +154 -0
  15. data/lib/arcp/ids.rb +18 -0
  16. data/lib/arcp/job/accepted.rb +37 -0
  17. data/lib/arcp/job/agent_ref.rb +18 -0
  18. data/lib/arcp/job/cancel.rb +18 -0
  19. data/lib/arcp/job/event.rb +68 -0
  20. data/lib/arcp/job/event_body/delegate.rb +24 -0
  21. data/lib/arcp/job/event_body/log.rb +20 -0
  22. data/lib/arcp/job/event_body/metric.rb +20 -0
  23. data/lib/arcp/job/event_body/progress.rb +27 -0
  24. data/lib/arcp/job/event_body/result_chunk.rb +42 -0
  25. data/lib/arcp/job/event_body/status.rb +25 -0
  26. data/lib/arcp/job/event_body/thought.rb +12 -0
  27. data/lib/arcp/job/event_body/tool_call.rb +16 -0
  28. data/lib/arcp/job/event_body/tool_result.rb +23 -0
  29. data/lib/arcp/job/event_body/trace_span.rb +30 -0
  30. data/lib/arcp/job/handle.rb +16 -0
  31. data/lib/arcp/job/job_error.rb +31 -0
  32. data/lib/arcp/job/result.rb +30 -0
  33. data/lib/arcp/job/submit.rb +32 -0
  34. data/lib/arcp/job/subscribe.rb +22 -0
  35. data/lib/arcp/job/subscribed.rb +14 -0
  36. data/lib/arcp/job/summary.rb +27 -0
  37. data/lib/arcp/job/unsubscribe.rb +10 -0
  38. data/lib/arcp/job.rb +15 -0
  39. data/lib/arcp/lease.rb +212 -0
  40. data/lib/arcp/message_types.rb +35 -0
  41. data/lib/arcp/runtime/credential_registry.rb +67 -0
  42. data/lib/arcp/runtime/event_log.rb +62 -0
  43. data/lib/arcp/runtime/job_context.rb +167 -0
  44. data/lib/arcp/runtime/job_manager.rb +256 -0
  45. data/lib/arcp/runtime/lease_manager.rb +88 -0
  46. data/lib/arcp/runtime/runtime.rb +125 -0
  47. data/lib/arcp/runtime/session_actor.rb +300 -0
  48. data/lib/arcp/runtime/subscription_manager.rb +57 -0
  49. data/lib/arcp/runtime.rb +10 -0
  50. data/lib/arcp/serializer.rb +43 -0
  51. data/lib/arcp/session/ack.rb +14 -0
  52. data/lib/arcp/session/agent_inventory.rb +51 -0
  53. data/lib/arcp/session/bye.rb +10 -0
  54. data/lib/arcp/session/capability_set.rb +29 -0
  55. data/lib/arcp/session/feature.rb +25 -0
  56. data/lib/arcp/session/hello.rb +34 -0
  57. data/lib/arcp/session/jobs_response.rb +18 -0
  58. data/lib/arcp/session/list_jobs.rb +19 -0
  59. data/lib/arcp/session/ping.rb +14 -0
  60. data/lib/arcp/session/pong.rb +14 -0
  61. data/lib/arcp/session/session_error.rb +23 -0
  62. data/lib/arcp/session/welcome.rb +38 -0
  63. data/lib/arcp/session.rb +26 -0
  64. data/lib/arcp/trace.rb +51 -0
  65. data/lib/arcp/transport/base.rb +29 -0
  66. data/lib/arcp/transport/memory_transport.rb +54 -0
  67. data/lib/arcp/transport/stdio_transport.rb +56 -0
  68. data/lib/arcp/transport/websocket_transport.rb +47 -0
  69. data/lib/arcp/transport.rb +6 -0
  70. data/lib/arcp/version.rb +7 -0
  71. data/lib/arcp.rb +19 -0
  72. data/sig/arcp/client.rbs +16 -0
  73. data/sig/arcp/credential.rbs +51 -0
  74. data/sig/arcp/envelope.rbs +25 -0
  75. data/sig/arcp/errors.rbs +40 -0
  76. data/sig/arcp/job.rbs +83 -0
  77. data/sig/arcp/lease.rbs +41 -0
  78. data/sig/arcp/runtime.rbs +41 -0
  79. data/sig/arcp/serializer.rbs +8 -0
  80. data/sig/arcp/session.rbs +56 -0
  81. data/sig/arcp/transport.rbs +18 -0
  82. data/sig/arcp.rbs +5 -0
  83. metadata +226 -0
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ class Error < StandardError
5
+ CODE = 'INTERNAL_ERROR'
6
+
7
+ attr_reader :details
8
+
9
+ def initialize(message = nil, details: {})
10
+ @details = details.freeze
11
+ super(message || self.class.default_message)
12
+ end
13
+
14
+ def code = self.class::CODE
15
+ def retryable? = false
16
+
17
+ def to_payload(trace_id: nil)
18
+ payload = { code: code, message: message, retryable: retryable? }
19
+ payload[:details] = details unless details.empty?
20
+ payload[:trace_id] = trace_id if trace_id
21
+ payload
22
+ end
23
+
24
+ def self.default_message = name.split('::').last.gsub(/([a-z])([A-Z])/, '\1 \2').downcase
25
+ end
26
+
27
+ module Errors
28
+ class Cancelled < Arcp::Error
29
+ CODE = 'CANCELLED'
30
+ end
31
+
32
+ class InvalidRequest < Arcp::Error
33
+ CODE = 'INVALID_REQUEST'
34
+ end
35
+
36
+ class Unauthenticated < Arcp::Error
37
+ CODE = 'UNAUTHENTICATED'
38
+ end
39
+
40
+ class PermissionDenied < Arcp::Error
41
+ CODE = 'PERMISSION_DENIED'
42
+ end
43
+
44
+ class JobNotFound < Arcp::Error
45
+ CODE = 'JOB_NOT_FOUND'
46
+ end
47
+
48
+ class AgentNotAvailable < Arcp::Error
49
+ CODE = 'AGENT_NOT_AVAILABLE'
50
+ def retryable? = true
51
+ end
52
+
53
+ class DuplicateKey < Arcp::Error
54
+ CODE = 'DUPLICATE_KEY'
55
+ end
56
+
57
+ class RateLimited < Arcp::Error
58
+ CODE = 'RATE_LIMITED'
59
+ def retryable? = true
60
+ end
61
+
62
+ class Internal < Arcp::Error
63
+ CODE = 'INTERNAL_ERROR'
64
+ def retryable? = true
65
+ end
66
+
67
+ class HeartbeatLost < Arcp::Error
68
+ CODE = 'HEARTBEAT_LOST'
69
+ def retryable? = true
70
+ end
71
+
72
+ class Backpressure < Arcp::Error
73
+ CODE = 'BACKPRESSURE'
74
+ def retryable? = true
75
+ end
76
+
77
+ class ProtocolViolation < Arcp::Error
78
+ CODE = 'PROTOCOL_VIOLATION'
79
+ end
80
+
81
+ class Timeout < Arcp::Error
82
+ CODE = 'TIMEOUT'
83
+ def retryable? = true
84
+ end
85
+
86
+ class ResumeWindowExpired < Arcp::Error
87
+ CODE = 'RESUME_WINDOW_EXPIRED'
88
+ end
89
+
90
+ class LeaseSubsetViolation < Arcp::Error
91
+ CODE = 'LEASE_SUBSET_VIOLATION'
92
+ end
93
+
94
+ class AgentVersionNotAvailable < Arcp::Error
95
+ CODE = 'AGENT_VERSION_NOT_AVAILABLE'
96
+ end
97
+
98
+ class LeaseExpired < Arcp::Error
99
+ CODE = 'LEASE_EXPIRED'
100
+ end
101
+
102
+ class BudgetExhausted < Arcp::Error
103
+ CODE = 'BUDGET_EXHAUSTED'
104
+ end
105
+
106
+ # Library-internal: never appears on the wire.
107
+ class UnnegotiatedFeature < Arcp::Error
108
+ CODE = 'UNNEGOTIATED_FEATURE'
109
+ end
110
+
111
+ ALL = [
112
+ Cancelled, InvalidRequest, Unauthenticated, PermissionDenied,
113
+ JobNotFound, AgentNotAvailable, DuplicateKey, RateLimited,
114
+ Internal, HeartbeatLost, Backpressure, ProtocolViolation, Timeout,
115
+ ResumeWindowExpired, LeaseSubsetViolation,
116
+ AgentVersionNotAvailable, LeaseExpired, BudgetExhausted
117
+ ].freeze
118
+
119
+ WIRE_CODES = ALL.map { |c| c::CODE }.freeze
120
+
121
+ BY_CODE = ALL.to_h { |klass| [klass::CODE, klass] }.freeze
122
+
123
+ RETRYABLE_BY_DEFAULT = ALL.select { |k| k.new.retryable? }.map { |k| k::CODE }.freeze
124
+ NON_RETRYABLE_BY_DEFAULT = (WIRE_CODES - RETRYABLE_BY_DEFAULT).freeze
125
+
126
+ def self.for(code, message: nil, details: {})
127
+ klass = BY_CODE[code] || Arcp::Errors::Internal
128
+ klass.new(message, details: details)
129
+ end
130
+ end
131
+
132
+ module ErrorCode
133
+ CANCELLED = 'CANCELLED'
134
+ INVALID_REQUEST = 'INVALID_REQUEST'
135
+ UNAUTHENTICATED = 'UNAUTHENTICATED'
136
+ PERMISSION_DENIED = 'PERMISSION_DENIED'
137
+ JOB_NOT_FOUND = 'JOB_NOT_FOUND'
138
+ AGENT_NOT_AVAILABLE = 'AGENT_NOT_AVAILABLE'
139
+ DUPLICATE_KEY = 'DUPLICATE_KEY'
140
+ RATE_LIMITED = 'RATE_LIMITED'
141
+ INTERNAL_ERROR = 'INTERNAL_ERROR'
142
+ HEARTBEAT_LOST = 'HEARTBEAT_LOST'
143
+ BACKPRESSURE = 'BACKPRESSURE'
144
+ PROTOCOL_VIOLATION = 'PROTOCOL_VIOLATION'
145
+ TIMEOUT = 'TIMEOUT'
146
+ RESUME_WINDOW_EXPIRED = 'RESUME_WINDOW_EXPIRED'
147
+ LEASE_SUBSET_VIOLATION = 'LEASE_SUBSET_VIOLATION'
148
+ AGENT_VERSION_NOT_AVAILABLE = 'AGENT_VERSION_NOT_AVAILABLE'
149
+ LEASE_EXPIRED = 'LEASE_EXPIRED'
150
+ BUDGET_EXHAUSTED = 'BUDGET_EXHAUSTED'
151
+
152
+ ALL = Arcp::Errors::WIRE_CODES
153
+ end
154
+ end
data/lib/arcp/ids.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Arcp
6
+ module Ids
7
+ module_function
8
+
9
+ def envelope_id = SecureRandom.uuid_v7
10
+ def session_id = "ses_#{SecureRandom.uuid_v7}"
11
+ def job_id = "job_#{SecureRandom.uuid_v7}"
12
+ def result_id = "res_#{SecureRandom.uuid_v7}"
13
+ def call_id = "call_#{SecureRandom.uuid_v7}"
14
+ def resume_token = SecureRandom.urlsafe_base64(24)
15
+ def trace_id = SecureRandom.hex(16)
16
+ def span_id = SecureRandom.hex(8)
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../credential'
4
+
5
+ module Arcp
6
+ module Job
7
+ Accepted = Data.define(:job_id, :agent, :accepted_at, :lease, :credentials) do
8
+ def initialize(job_id:, agent:, accepted_at:, lease: nil, credentials: nil)
9
+ super
10
+ end
11
+
12
+ def self.from_h(h)
13
+ h = h.transform_keys(&:to_s)
14
+ new(
15
+ job_id: h.fetch('job_id'),
16
+ agent: h.fetch('agent'),
17
+ accepted_at: h['accepted_at'],
18
+ lease: h['lease'] ? Arcp::Lease::Lease.from_h(h['lease']) : nil,
19
+ credentials: credentials_from(h)
20
+ )
21
+ end
22
+
23
+ def to_h
24
+ out = { 'job_id' => job_id, 'agent' => agent, 'accepted_at' => accepted_at }
25
+ out['lease'] = lease.to_h if lease
26
+ out['credentials'] = credentials.map(&:to_h) if credentials && !credentials.empty?
27
+ out
28
+ end
29
+
30
+ def self.credentials_from(h)
31
+ return nil unless h['credentials']
32
+
33
+ Array(h['credentials']).map { |credential| Arcp::Credential.from_h(credential) }.freeze
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ AgentRef = Data.define(:name, :version) do
6
+ def self.parse(ref)
7
+ return nil if ref.nil?
8
+
9
+ name, version = ref.to_s.split('@', 2)
10
+ raise Arcp::Errors::InvalidRequest, 'agent name must be non-empty' if name.nil? || name.empty?
11
+
12
+ new(name: name, version: version)
13
+ end
14
+
15
+ def to_s = version ? "#{name}@#{version}" : name
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ Cancel = Data.define(:job_id, :reason) do
6
+ def self.from_h(h)
7
+ h = h.transform_keys(&:to_s)
8
+ new(job_id: h.fetch('job_id'), reason: h['reason'])
9
+ end
10
+
11
+ def to_h
12
+ out = { 'job_id' => job_id }
13
+ out['reason'] = reason if reason
14
+ out
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Body classes live in `Arcp::Job::EventBody`. The `Event` constant
4
+ # below is a `Data.define` value object — it cannot also be a module,
5
+ # so the per-kind body types are namespaced separately.
6
+ require_relative 'event_body/progress'
7
+ require_relative 'event_body/result_chunk'
8
+ require_relative 'event_body/log'
9
+ require_relative 'event_body/thought'
10
+ require_relative 'event_body/tool_call'
11
+ require_relative 'event_body/tool_result'
12
+ require_relative 'event_body/status'
13
+ require_relative 'event_body/metric'
14
+ require_relative 'event_body/trace_span'
15
+ require_relative 'event_body/delegate'
16
+
17
+ module Arcp
18
+ module Job
19
+ module EventKind
20
+ PROGRESS = 'progress'
21
+ RESULT_CHUNK = 'result_chunk'
22
+ LOG = 'log'
23
+ THOUGHT = 'thought'
24
+ TOOL_CALL = 'tool_call'
25
+ TOOL_RESULT = 'tool_result'
26
+ STATUS = 'status'
27
+ METRIC = 'metric'
28
+ TRACE_SPAN = 'trace_span'
29
+ DELEGATE = 'delegate'
30
+
31
+ ALL = [
32
+ PROGRESS, RESULT_CHUNK, LOG, THOUGHT, TOOL_CALL, TOOL_RESULT,
33
+ STATUS, METRIC, TRACE_SPAN, DELEGATE
34
+ ].freeze
35
+ end
36
+
37
+ BODY_CLASSES = {
38
+ EventKind::PROGRESS => EventBody::Progress,
39
+ EventKind::RESULT_CHUNK => EventBody::ResultChunk,
40
+ EventKind::LOG => EventBody::Log,
41
+ EventKind::THOUGHT => EventBody::Thought,
42
+ EventKind::TOOL_CALL => EventBody::ToolCall,
43
+ EventKind::TOOL_RESULT => EventBody::ToolResult,
44
+ EventKind::STATUS => EventBody::Status,
45
+ EventKind::METRIC => EventBody::Metric,
46
+ EventKind::TRACE_SPAN => EventBody::TraceSpan,
47
+ EventKind::DELEGATE => EventBody::Delegate
48
+ }.freeze
49
+
50
+ Event = Data.define(:kind, :body) do
51
+ def self.from_h(h)
52
+ h = h.transform_keys(&:to_s)
53
+ kind = h.fetch('kind')
54
+ body_h = h['body'] || {}
55
+ klass = BODY_CLASSES[kind]
56
+ body = klass ? klass.from_h(body_h) : Arcp::Envelope.deep_freeze(body_h.dup)
57
+ new(kind: kind, body: body)
58
+ end
59
+
60
+ def to_h
61
+ body_h = body.respond_to?(:to_h) ? body.to_h : body
62
+ { 'kind' => kind, 'body' => body_h }
63
+ end
64
+
65
+ def known? = EventKind::ALL.include?(kind)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ Delegate = Data.define(:child_job_id, :agent, :lease) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(
10
+ child_job_id: h.fetch('child_job_id'),
11
+ agent: h.fetch('agent'),
12
+ lease: h['lease'] ? Arcp::Lease::Lease.from_h(h['lease']) : nil
13
+ )
14
+ end
15
+
16
+ def to_h
17
+ out = { 'child_job_id' => child_job_id, 'agent' => agent }
18
+ out['lease'] = lease.to_h if lease
19
+ out
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ Log = Data.define(:level, :message, :fields) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(level: h.fetch('level'), message: h.fetch('message'), fields: h['fields'] || {})
10
+ end
11
+
12
+ def to_h
13
+ out = { 'level' => level, 'message' => message }
14
+ out['fields'] = fields unless fields.empty?
15
+ out
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ Metric = Data.define(:name, :value, :unit) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(name: h.fetch('name'), value: h.fetch('value'), unit: h['unit'])
10
+ end
11
+
12
+ def to_h
13
+ out = { 'name' => name, 'value' => value }
14
+ out['unit'] = unit if unit
15
+ out
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ Progress = Data.define(:current, :total, :units, :message) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(
10
+ current: h.fetch('current'),
11
+ total: h['total'],
12
+ units: h['units'],
13
+ message: h['message']
14
+ )
15
+ end
16
+
17
+ def to_h
18
+ out = { 'current' => current }
19
+ out['total'] = total if total
20
+ out['units'] = units if units
21
+ out['message'] = message if message
22
+ out
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Arcp
6
+ module Job
7
+ module EventBody
8
+ ResultChunk = Data.define(:result_id, :chunk_seq, :data, :encoding, :more) do
9
+ ENCODINGS = %w[utf8 base64].freeze
10
+
11
+ def self.from_h(h)
12
+ h = h.transform_keys(&:to_s)
13
+ encoding = h.fetch('encoding')
14
+ unless ENCODINGS.include?(encoding)
15
+ raise Arcp::Errors::InvalidRequest,
16
+ "unknown encoding: #{encoding.inspect}"
17
+ end
18
+
19
+ new(
20
+ result_id: h.fetch('result_id'),
21
+ chunk_seq: h.fetch('chunk_seq'),
22
+ data: h.fetch('data'),
23
+ encoding: encoding,
24
+ more: h.fetch('more')
25
+ )
26
+ end
27
+
28
+ def to_h
29
+ { 'result_id' => result_id, 'chunk_seq' => chunk_seq, 'data' => data,
30
+ 'encoding' => encoding, 'more' => more }
31
+ end
32
+
33
+ def decoded
34
+ case encoding
35
+ when 'utf8' then data
36
+ when 'base64' then Base64.decode64(data)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ Status = Data.define(:phase, :message, :fields) do
7
+ def initialize(phase:, message: nil, fields: {})
8
+ super(phase: phase, message: message, fields: fields || {})
9
+ end
10
+
11
+ def self.from_h(h)
12
+ h = h.transform_keys(&:to_s)
13
+ new(phase: h.fetch('phase'), message: h['message'], fields: h['fields'] || {})
14
+ end
15
+
16
+ def to_h
17
+ out = { 'phase' => phase }
18
+ out['message'] = message if message
19
+ out['fields'] = fields unless fields.empty?
20
+ out
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ Thought = Data.define(:text) do
7
+ def self.from_h(h) = new(text: h.transform_keys(&:to_s).fetch('text'))
8
+ def to_h = { 'text' => text }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ ToolCall = Data.define(:call_id, :tool, :args) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(call_id: h.fetch('call_id'), tool: h.fetch('tool'), args: h['args'] || {})
10
+ end
11
+
12
+ def to_h = { 'call_id' => call_id, 'tool' => tool, 'args' => args }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ ToolResult = Data.define(:call_id, :result, :error) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(call_id: h.fetch('call_id'), result: h['result'], error: h['error'])
10
+ end
11
+
12
+ def to_h
13
+ out = { 'call_id' => call_id }
14
+ out['result'] = result if result
15
+ out['error'] = error if error
16
+ out
17
+ end
18
+
19
+ def ok? = error.nil?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ module EventBody
6
+ TraceSpan = Data.define(:span_id, :parent_span_id, :name, :start_at, :end_at, :attributes) do
7
+ def self.from_h(h)
8
+ h = h.transform_keys(&:to_s)
9
+ new(
10
+ span_id: h.fetch('span_id'),
11
+ parent_span_id: h['parent_span_id'],
12
+ name: h.fetch('name'),
13
+ start_at: h['start_at'],
14
+ end_at: h['end_at'],
15
+ attributes: h['attributes'] || {}
16
+ )
17
+ end
18
+
19
+ def to_h
20
+ out = { 'span_id' => span_id, 'name' => name }
21
+ out['parent_span_id'] = parent_span_id if parent_span_id
22
+ out['start_at'] = start_at if start_at
23
+ out['end_at'] = end_at if end_at
24
+ out['attributes'] = attributes unless attributes.empty?
25
+ out
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ Handle = Data.define(:job_id, :agent, :submitted_at, :lease, :credentials) do
6
+ def initialize(job_id:, agent:, submitted_at:, lease: nil, credentials: nil)
7
+ super
8
+ end
9
+
10
+ def subscribe(client:, **kw) = client.subscribe_job(job_id: job_id, **kw)
11
+ def cancel(client:, reason: nil) = client.cancel_job(job_id: job_id, reason: reason)
12
+ def get_result(client:) = client.get_result(job_id: job_id)
13
+ def credential_for(endpoint:) = Array(credentials).find { |credential| credential.endpoint == endpoint }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ JobError = Data.define(:job_id, :final_status, :code, :message, :retryable, :details) do
6
+ def self.from_h(h)
7
+ h = h.transform_keys(&:to_s)
8
+ new(
9
+ job_id: h.fetch('job_id'),
10
+ final_status: h.fetch('final_status'),
11
+ code: h.fetch('code'),
12
+ message: h['message'],
13
+ retryable: h.fetch('retryable', false),
14
+ details: h['details'] || {}
15
+ )
16
+ end
17
+
18
+ def to_h
19
+ out = { 'job_id' => job_id, 'final_status' => final_status,
20
+ 'code' => code, 'retryable' => retryable }
21
+ out['message'] = message if message
22
+ out['details'] = details unless details.empty?
23
+ out
24
+ end
25
+
26
+ def to_exception
27
+ Arcp::Errors.for(code, message: message, details: details)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ Result = Data.define(:job_id, :final_status, :result, :result_id, :result_size, :completed_at) do
6
+ def self.from_h(h)
7
+ h = h.transform_keys(&:to_s)
8
+ new(
9
+ job_id: h.fetch('job_id'),
10
+ final_status: h.fetch('final_status'),
11
+ result: h['result'],
12
+ result_id: h['result_id'],
13
+ result_size: h['result_size'],
14
+ completed_at: h['completed_at']
15
+ )
16
+ end
17
+
18
+ def to_h
19
+ out = { 'job_id' => job_id, 'final_status' => final_status }
20
+ out['result'] = result if result
21
+ out['result_id'] = result_id if result_id
22
+ out['result_size'] = result_size if result_size
23
+ out['completed_at'] = completed_at if completed_at
24
+ out
25
+ end
26
+
27
+ def chunked? = !result_id.nil?
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcp
4
+ module Job
5
+ Submit = Data.define(
6
+ :agent, :input, :lease_request, :lease_constraints,
7
+ :idempotency_key, :max_runtime_sec
8
+ ) do
9
+ def self.from_h(h)
10
+ h = h.transform_keys(&:to_s)
11
+ new(
12
+ agent: h.fetch('agent'),
13
+ input: h['input'],
14
+ lease_request: Arcp::Lease::LeaseRequest.from_h(h['lease_request']),
15
+ lease_constraints: Arcp::Lease::LeaseConstraints.from_h(h['lease_constraints']),
16
+ idempotency_key: h['idempotency_key'],
17
+ max_runtime_sec: h['max_runtime_sec']
18
+ )
19
+ end
20
+
21
+ def to_h
22
+ out = { 'agent' => agent }
23
+ out['input'] = input if input
24
+ out['lease_request'] = lease_request.to_h if lease_request
25
+ out['lease_constraints'] = lease_constraints.to_h if lease_constraints
26
+ out['idempotency_key'] = idempotency_key if idempotency_key
27
+ out['max_runtime_sec'] = max_runtime_sec if max_runtime_sec
28
+ out
29
+ end
30
+ end
31
+ end
32
+ end