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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +31 -0
- data/CONFORMANCE.md +71 -0
- data/LICENSE +202 -0
- data/README.md +135 -0
- data/lib/arcp/auth/auth_scheme.rb +16 -0
- data/lib/arcp/auth/bearer.rb +38 -0
- data/lib/arcp/auth.rb +4 -0
- data/lib/arcp/client.rb +354 -0
- data/lib/arcp/clock.rb +35 -0
- data/lib/arcp/credential.rb +65 -0
- data/lib/arcp/credential_provisioner.rb +132 -0
- data/lib/arcp/envelope.rb +115 -0
- data/lib/arcp/errors.rb +154 -0
- data/lib/arcp/ids.rb +18 -0
- data/lib/arcp/job/accepted.rb +37 -0
- data/lib/arcp/job/agent_ref.rb +18 -0
- data/lib/arcp/job/cancel.rb +18 -0
- data/lib/arcp/job/event.rb +68 -0
- data/lib/arcp/job/event_body/delegate.rb +24 -0
- data/lib/arcp/job/event_body/log.rb +20 -0
- data/lib/arcp/job/event_body/metric.rb +20 -0
- data/lib/arcp/job/event_body/progress.rb +27 -0
- data/lib/arcp/job/event_body/result_chunk.rb +42 -0
- data/lib/arcp/job/event_body/status.rb +25 -0
- data/lib/arcp/job/event_body/thought.rb +12 -0
- data/lib/arcp/job/event_body/tool_call.rb +16 -0
- data/lib/arcp/job/event_body/tool_result.rb +23 -0
- data/lib/arcp/job/event_body/trace_span.rb +30 -0
- data/lib/arcp/job/handle.rb +16 -0
- data/lib/arcp/job/job_error.rb +31 -0
- data/lib/arcp/job/result.rb +30 -0
- data/lib/arcp/job/submit.rb +32 -0
- data/lib/arcp/job/subscribe.rb +22 -0
- data/lib/arcp/job/subscribed.rb +14 -0
- data/lib/arcp/job/summary.rb +27 -0
- data/lib/arcp/job/unsubscribe.rb +10 -0
- data/lib/arcp/job.rb +15 -0
- data/lib/arcp/lease.rb +212 -0
- data/lib/arcp/message_types.rb +35 -0
- data/lib/arcp/runtime/credential_registry.rb +67 -0
- data/lib/arcp/runtime/event_log.rb +62 -0
- data/lib/arcp/runtime/job_context.rb +167 -0
- data/lib/arcp/runtime/job_manager.rb +256 -0
- data/lib/arcp/runtime/lease_manager.rb +88 -0
- data/lib/arcp/runtime/runtime.rb +125 -0
- data/lib/arcp/runtime/session_actor.rb +300 -0
- data/lib/arcp/runtime/subscription_manager.rb +57 -0
- data/lib/arcp/runtime.rb +10 -0
- data/lib/arcp/serializer.rb +43 -0
- data/lib/arcp/session/ack.rb +14 -0
- data/lib/arcp/session/agent_inventory.rb +51 -0
- data/lib/arcp/session/bye.rb +10 -0
- data/lib/arcp/session/capability_set.rb +29 -0
- data/lib/arcp/session/feature.rb +25 -0
- data/lib/arcp/session/hello.rb +34 -0
- data/lib/arcp/session/jobs_response.rb +18 -0
- data/lib/arcp/session/list_jobs.rb +19 -0
- data/lib/arcp/session/ping.rb +14 -0
- data/lib/arcp/session/pong.rb +14 -0
- data/lib/arcp/session/session_error.rb +23 -0
- data/lib/arcp/session/welcome.rb +38 -0
- data/lib/arcp/session.rb +26 -0
- data/lib/arcp/trace.rb +51 -0
- data/lib/arcp/transport/base.rb +29 -0
- data/lib/arcp/transport/memory_transport.rb +54 -0
- data/lib/arcp/transport/stdio_transport.rb +56 -0
- data/lib/arcp/transport/websocket_transport.rb +47 -0
- data/lib/arcp/transport.rb +6 -0
- data/lib/arcp/version.rb +7 -0
- data/lib/arcp.rb +19 -0
- data/sig/arcp/client.rbs +16 -0
- data/sig/arcp/credential.rbs +51 -0
- data/sig/arcp/envelope.rbs +25 -0
- data/sig/arcp/errors.rbs +40 -0
- data/sig/arcp/job.rbs +83 -0
- data/sig/arcp/lease.rbs +41 -0
- data/sig/arcp/runtime.rbs +41 -0
- data/sig/arcp/serializer.rbs +8 -0
- data/sig/arcp/session.rbs +56 -0
- data/sig/arcp/transport.rbs +18 -0
- data/sig/arcp.rbs +5 -0
- metadata +226 -0
data/lib/arcp/errors.rb
ADDED
|
@@ -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,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
|