simple_acp 0.0.1
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/.envrc +1 -0
- data/CHANGELOG.md +5 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +385 -0
- data/Rakefile +13 -0
- data/docs/api/client-base.md +383 -0
- data/docs/api/index.md +159 -0
- data/docs/api/models.md +286 -0
- data/docs/api/server-base.md +379 -0
- data/docs/api/storage.md +347 -0
- data/docs/assets/images/simple_acp.jpg +0 -0
- data/docs/client/index.md +279 -0
- data/docs/client/sessions.md +324 -0
- data/docs/client/streaming.md +345 -0
- data/docs/client/sync-async.md +308 -0
- data/docs/core-concepts/agents.md +253 -0
- data/docs/core-concepts/events.md +337 -0
- data/docs/core-concepts/index.md +147 -0
- data/docs/core-concepts/messages.md +211 -0
- data/docs/core-concepts/runs.md +278 -0
- data/docs/core-concepts/sessions.md +281 -0
- data/docs/examples.md +659 -0
- data/docs/getting-started/configuration.md +166 -0
- data/docs/getting-started/index.md +62 -0
- data/docs/getting-started/installation.md +95 -0
- data/docs/getting-started/quick-start.md +189 -0
- data/docs/index.md +119 -0
- data/docs/server/creating-agents.md +360 -0
- data/docs/server/http-endpoints.md +411 -0
- data/docs/server/index.md +218 -0
- data/docs/server/multi-turn.md +329 -0
- data/docs/server/streaming.md +315 -0
- data/docs/storage/custom.md +414 -0
- data/docs/storage/index.md +176 -0
- data/docs/storage/memory.md +198 -0
- data/docs/storage/postgresql.md +350 -0
- data/docs/storage/redis.md +287 -0
- data/examples/01_basic/client.rb +88 -0
- data/examples/01_basic/server.rb +100 -0
- data/examples/02_async_execution/client.rb +107 -0
- data/examples/02_async_execution/server.rb +56 -0
- data/examples/03_run_management/client.rb +115 -0
- data/examples/03_run_management/server.rb +84 -0
- data/examples/04_rich_messages/client.rb +160 -0
- data/examples/04_rich_messages/server.rb +180 -0
- data/examples/05_await_resume/client.rb +164 -0
- data/examples/05_await_resume/server.rb +114 -0
- data/examples/06_agent_metadata/client.rb +188 -0
- data/examples/06_agent_metadata/server.rb +192 -0
- data/examples/README.md +252 -0
- data/examples/run_demo.sh +137 -0
- data/lib/simple_acp/client/base.rb +448 -0
- data/lib/simple_acp/client/sse.rb +141 -0
- data/lib/simple_acp/models/agent_manifest.rb +129 -0
- data/lib/simple_acp/models/await.rb +123 -0
- data/lib/simple_acp/models/base.rb +147 -0
- data/lib/simple_acp/models/errors.rb +102 -0
- data/lib/simple_acp/models/events.rb +256 -0
- data/lib/simple_acp/models/message.rb +235 -0
- data/lib/simple_acp/models/message_part.rb +225 -0
- data/lib/simple_acp/models/metadata.rb +161 -0
- data/lib/simple_acp/models/run.rb +298 -0
- data/lib/simple_acp/models/session.rb +137 -0
- data/lib/simple_acp/models/types.rb +210 -0
- data/lib/simple_acp/server/agent.rb +116 -0
- data/lib/simple_acp/server/app.rb +264 -0
- data/lib/simple_acp/server/base.rb +510 -0
- data/lib/simple_acp/server/context.rb +210 -0
- data/lib/simple_acp/server/falcon_runner.rb +61 -0
- data/lib/simple_acp/storage/base.rb +129 -0
- data/lib/simple_acp/storage/memory.rb +108 -0
- data/lib/simple_acp/storage/postgresql.rb +233 -0
- data/lib/simple_acp/storage/redis.rb +178 -0
- data/lib/simple_acp/version.rb +5 -0
- data/lib/simple_acp.rb +91 -0
- data/mkdocs.yml +152 -0
- data/sig/simple_acp.rbs +4 -0
- metadata +418 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Models
|
|
5
|
+
# Represents a single agent execution.
|
|
6
|
+
#
|
|
7
|
+
# Tracks the lifecycle of an agent run from creation through completion,
|
|
8
|
+
# including status, output messages, errors, and timing.
|
|
9
|
+
#
|
|
10
|
+
# == Status Lifecycle
|
|
11
|
+
#
|
|
12
|
+
# - created -> in_progress -> completed | failed | cancelled | awaiting
|
|
13
|
+
# - awaiting -> in_progress (on resume)
|
|
14
|
+
# - cancelling -> cancelled
|
|
15
|
+
class Run < Base
|
|
16
|
+
# @!attribute [r] run_id
|
|
17
|
+
# @return [String] unique UUID for this run
|
|
18
|
+
attribute :run_id, required: true
|
|
19
|
+
|
|
20
|
+
# @!attribute [r] agent_name
|
|
21
|
+
# @return [String] name of the agent being executed
|
|
22
|
+
attribute :agent_name, required: true
|
|
23
|
+
|
|
24
|
+
# @!attribute [r] session_id
|
|
25
|
+
# @return [String, nil] optional session ID
|
|
26
|
+
attribute :session_id
|
|
27
|
+
|
|
28
|
+
# @!attribute [r] status
|
|
29
|
+
# @return [String] current status (created, in-progress, completed, failed, cancelled, awaiting)
|
|
30
|
+
attribute :status, default: Types::RunStatus::CREATED
|
|
31
|
+
|
|
32
|
+
# @!attribute [r] await_request
|
|
33
|
+
# @return [AwaitRequest, nil] request for client input (when awaiting)
|
|
34
|
+
attribute :await_request
|
|
35
|
+
|
|
36
|
+
# @!attribute [r] output
|
|
37
|
+
# @return [Array<Message>] output messages from the agent
|
|
38
|
+
attribute :output, default: -> { [] }
|
|
39
|
+
|
|
40
|
+
# @!attribute [r] error
|
|
41
|
+
# @return [Error, nil] error details if failed
|
|
42
|
+
attribute :error
|
|
43
|
+
|
|
44
|
+
# @!attribute [r] created_at
|
|
45
|
+
# @return [Time, nil] when the run was created
|
|
46
|
+
attribute :created_at
|
|
47
|
+
|
|
48
|
+
# @!attribute [r] finished_at
|
|
49
|
+
# @return [Time, nil] when the run finished (completed, failed, or cancelled)
|
|
50
|
+
attribute :finished_at
|
|
51
|
+
|
|
52
|
+
def initialize(**kwargs)
|
|
53
|
+
super
|
|
54
|
+
@run_id ||= Types.generate_uuid
|
|
55
|
+
@output ||= []
|
|
56
|
+
@created_at ||= Time.now
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Create from a hash (JSON deserialization).
|
|
60
|
+
#
|
|
61
|
+
# @param hash [Hash, nil] run data
|
|
62
|
+
# @return [Run, nil] the run or nil
|
|
63
|
+
def self.from_hash(hash)
|
|
64
|
+
return nil if hash.nil?
|
|
65
|
+
|
|
66
|
+
instance = allocate
|
|
67
|
+
instance.send(:initialize_from_hash, hash)
|
|
68
|
+
instance
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if the run is in a terminal state.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true if completed, failed, or cancelled
|
|
74
|
+
def terminal?
|
|
75
|
+
Types::RunStatus.terminal?(@status)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if the run is currently executing.
|
|
79
|
+
#
|
|
80
|
+
# @return [Boolean] true if in_progress
|
|
81
|
+
def in_progress?
|
|
82
|
+
@status == Types::RunStatus::IN_PROGRESS
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Check if the run is waiting for client input.
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean] true if awaiting
|
|
88
|
+
def awaiting?
|
|
89
|
+
@status == Types::RunStatus::AWAITING
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Check if the run completed successfully.
|
|
93
|
+
#
|
|
94
|
+
# @return [Boolean] true if completed
|
|
95
|
+
def completed?
|
|
96
|
+
@status == Types::RunStatus::COMPLETED
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Check if the run failed.
|
|
100
|
+
#
|
|
101
|
+
# @return [Boolean] true if failed
|
|
102
|
+
def failed?
|
|
103
|
+
@status == Types::RunStatus::FAILED
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Check if the run was cancelled.
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean] true if cancelled
|
|
109
|
+
def cancelled?
|
|
110
|
+
@status == Types::RunStatus::CANCELLED
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Check if the run is being cancelled.
|
|
114
|
+
#
|
|
115
|
+
# @return [Boolean] true if cancelling
|
|
116
|
+
def cancelling?
|
|
117
|
+
@status == Types::RunStatus::CANCELLING
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Transition to in_progress status.
|
|
121
|
+
#
|
|
122
|
+
# @return [self] for chaining
|
|
123
|
+
def start!
|
|
124
|
+
@status = Types::RunStatus::IN_PROGRESS
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Transition to awaiting status.
|
|
129
|
+
#
|
|
130
|
+
# @param request [AwaitRequest] the request for client input
|
|
131
|
+
# @return [self] for chaining
|
|
132
|
+
def await!(request)
|
|
133
|
+
@status = Types::RunStatus::AWAITING
|
|
134
|
+
@await_request = request
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Transition to completed status.
|
|
139
|
+
#
|
|
140
|
+
# @param output [Array<Message>, nil] optional output messages
|
|
141
|
+
# @return [self] for chaining
|
|
142
|
+
def complete!(output = nil)
|
|
143
|
+
@status = Types::RunStatus::COMPLETED
|
|
144
|
+
@output = output if output
|
|
145
|
+
@finished_at = Time.now
|
|
146
|
+
self
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Transition to failed status.
|
|
150
|
+
#
|
|
151
|
+
# @param error [Error, String] the error or error message
|
|
152
|
+
# @return [self] for chaining
|
|
153
|
+
def fail!(error)
|
|
154
|
+
@status = Types::RunStatus::FAILED
|
|
155
|
+
@error = error.is_a?(Error) ? error : Error.server_error(error.to_s)
|
|
156
|
+
@finished_at = Time.now
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Transition to cancelling status.
|
|
161
|
+
#
|
|
162
|
+
# @return [self] for chaining
|
|
163
|
+
def cancel!
|
|
164
|
+
@status = Types::RunStatus::CANCELLING
|
|
165
|
+
self
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Transition to cancelled status.
|
|
169
|
+
#
|
|
170
|
+
# @return [self] for chaining
|
|
171
|
+
def cancelled!
|
|
172
|
+
@status = Types::RunStatus::CANCELLED
|
|
173
|
+
@finished_at = Time.now
|
|
174
|
+
self
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Add a message to the output.
|
|
178
|
+
#
|
|
179
|
+
# @param message [Message, Hash] the message to add
|
|
180
|
+
# @return [self] for chaining
|
|
181
|
+
def add_output(message)
|
|
182
|
+
@output << (message.is_a?(Message) ? message : Message.from_hash(message))
|
|
183
|
+
self
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Raise an exception if the run failed.
|
|
187
|
+
#
|
|
188
|
+
# @return [self] if not failed
|
|
189
|
+
# @raise [RunError] if failed
|
|
190
|
+
def raise_for_status!
|
|
191
|
+
return self unless failed?
|
|
192
|
+
|
|
193
|
+
raise SimpleAcp::RunError, @error&.message || "Run failed"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Validate the run.
|
|
197
|
+
#
|
|
198
|
+
# @return [Boolean] true if run_id, agent_name, and status are valid
|
|
199
|
+
def valid?
|
|
200
|
+
return false unless Types.valid_uuid?(@run_id)
|
|
201
|
+
return false unless Types.valid_agent_name?(@agent_name)
|
|
202
|
+
return false unless Types::RunStatus.valid?(@status)
|
|
203
|
+
|
|
204
|
+
true
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
def initialize_from_hash(hash)
|
|
210
|
+
@run_id = hash["run_id"] || hash[:run_id]
|
|
211
|
+
@agent_name = hash["agent_name"] || hash[:agent_name]
|
|
212
|
+
@session_id = hash["session_id"] || hash[:session_id]
|
|
213
|
+
@status = hash["status"] || hash[:status] || Types::RunStatus::CREATED
|
|
214
|
+
@created_at = parse_time(hash["created_at"] || hash[:created_at])
|
|
215
|
+
@finished_at = parse_time(hash["finished_at"] || hash[:finished_at])
|
|
216
|
+
|
|
217
|
+
output_data = hash["output"] || hash[:output] || []
|
|
218
|
+
@output = output_data.map { |m| Message.from_hash(m) }
|
|
219
|
+
|
|
220
|
+
if hash["error"] || hash[:error]
|
|
221
|
+
@error = Error.from_hash(hash["error"] || hash[:error])
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if hash["await_request"] || hash[:await_request]
|
|
225
|
+
@await_request = AwaitRequest.from_hash(hash["await_request"] || hash[:await_request])
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def parse_time(value)
|
|
230
|
+
return nil if value.nil?
|
|
231
|
+
return value if value.is_a?(Time)
|
|
232
|
+
|
|
233
|
+
Time.parse(value.to_s)
|
|
234
|
+
rescue ArgumentError
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Request body for creating a run
|
|
240
|
+
class RunCreateRequest < Base
|
|
241
|
+
attribute :agent_name, required: true
|
|
242
|
+
attribute :input, required: true
|
|
243
|
+
attribute :mode
|
|
244
|
+
attribute :session_id
|
|
245
|
+
attribute :session
|
|
246
|
+
|
|
247
|
+
def self.from_hash(hash)
|
|
248
|
+
return nil if hash.nil?
|
|
249
|
+
|
|
250
|
+
instance = super
|
|
251
|
+
|
|
252
|
+
input_data = hash["input"] || hash[:input] || []
|
|
253
|
+
instance.input = input_data.map { |m| Message.from_hash(m) }
|
|
254
|
+
|
|
255
|
+
if hash["session"] || hash[:session]
|
|
256
|
+
instance.session = Session.from_hash(hash["session"] || hash[:session])
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
instance
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def valid?
|
|
263
|
+
return false unless Types.valid_agent_name?(@agent_name)
|
|
264
|
+
return false if @input.nil? || @input.empty?
|
|
265
|
+
return false if @mode && !Types::RunMode.valid?(@mode)
|
|
266
|
+
|
|
267
|
+
true
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Request body for resuming a run
|
|
272
|
+
class RunResumeRequest < Base
|
|
273
|
+
attribute :run_id, required: true
|
|
274
|
+
attribute :await_resume, required: true
|
|
275
|
+
attribute :mode, required: true
|
|
276
|
+
|
|
277
|
+
def self.from_hash(hash)
|
|
278
|
+
return nil if hash.nil?
|
|
279
|
+
|
|
280
|
+
instance = super
|
|
281
|
+
|
|
282
|
+
if hash["await_resume"] || hash[:await_resume]
|
|
283
|
+
instance.await_resume = AwaitResume.from_hash(hash["await_resume"] || hash[:await_resume])
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
instance
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def valid?
|
|
290
|
+
return false unless Types.valid_uuid?(@run_id)
|
|
291
|
+
return false if @await_resume.nil?
|
|
292
|
+
return false unless Types::RunMode.valid?(@mode)
|
|
293
|
+
|
|
294
|
+
true
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Models
|
|
5
|
+
# Maintains state and conversation history across interactions.
|
|
6
|
+
#
|
|
7
|
+
# Sessions allow agents to maintain context between requests,
|
|
8
|
+
# storing both conversation history and arbitrary state data.
|
|
9
|
+
#
|
|
10
|
+
# @example Using sessions with the client
|
|
11
|
+
# client.use_session("my-session")
|
|
12
|
+
# client.run_sync(agent: "counter", input: "increment") # Count: 1
|
|
13
|
+
# client.run_sync(agent: "counter", input: "increment") # Count: 2
|
|
14
|
+
class Session < Base
|
|
15
|
+
# @!attribute [r] id
|
|
16
|
+
# @return [String] unique session ID
|
|
17
|
+
attribute :id, required: true
|
|
18
|
+
|
|
19
|
+
# @!attribute [r] history
|
|
20
|
+
# @return [Array<Message>] conversation history
|
|
21
|
+
attribute :history, default: -> { [] }
|
|
22
|
+
|
|
23
|
+
# @!attribute [r] state
|
|
24
|
+
# @return [Object, nil] arbitrary state data
|
|
25
|
+
attribute :state
|
|
26
|
+
|
|
27
|
+
def initialize(**kwargs)
|
|
28
|
+
super
|
|
29
|
+
@id ||= Types.generate_uuid
|
|
30
|
+
@history ||= []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Create from a hash (JSON deserialization).
|
|
34
|
+
#
|
|
35
|
+
# @param hash [Hash, nil] session data
|
|
36
|
+
# @return [Session, nil] the session or nil
|
|
37
|
+
def self.from_hash(hash)
|
|
38
|
+
return nil if hash.nil?
|
|
39
|
+
|
|
40
|
+
instance = allocate
|
|
41
|
+
instance.send(:initialize_from_hash, hash)
|
|
42
|
+
instance
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Create a new session with a generated ID.
|
|
46
|
+
#
|
|
47
|
+
# @return [Session] new session
|
|
48
|
+
def self.create
|
|
49
|
+
new(id: Types.generate_uuid)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Add a message to the conversation history.
|
|
53
|
+
#
|
|
54
|
+
# @param message [Message, Hash] the message to add
|
|
55
|
+
# @return [self] for chaining
|
|
56
|
+
def add_to_history(message)
|
|
57
|
+
@history << (message.is_a?(Message) ? message : Message.from_hash(message))
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Clear all conversation history.
|
|
62
|
+
#
|
|
63
|
+
# @return [self] for chaining
|
|
64
|
+
def clear_history!
|
|
65
|
+
@history = []
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Set the session state.
|
|
70
|
+
#
|
|
71
|
+
# @param state_data [Object] arbitrary state data
|
|
72
|
+
# @return [self] for chaining
|
|
73
|
+
def set_state(state_data)
|
|
74
|
+
@state = state_data
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Clear the session state.
|
|
79
|
+
#
|
|
80
|
+
# @return [self] for chaining
|
|
81
|
+
def clear_state!
|
|
82
|
+
@state = nil
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get the number of messages in history.
|
|
87
|
+
#
|
|
88
|
+
# @return [Integer] message count
|
|
89
|
+
def message_count
|
|
90
|
+
@history.length
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check if the session has no history or state.
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean] true if no history and no state
|
|
96
|
+
def empty?
|
|
97
|
+
@history.empty? && @state.nil?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Validate the session.
|
|
101
|
+
#
|
|
102
|
+
# @return [Boolean] true if ID is a valid UUID
|
|
103
|
+
def valid?
|
|
104
|
+
Types.valid_uuid?(@id)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def initialize_from_hash(hash)
|
|
110
|
+
@id = hash["id"] || hash[:id]
|
|
111
|
+
@state = hash["state"] || hash[:state]
|
|
112
|
+
|
|
113
|
+
history_data = hash["history"] || hash[:history] || []
|
|
114
|
+
@history = history_data.map do |item|
|
|
115
|
+
item.is_a?(Message) ? item : Message.from_hash(item)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Response for getting session details
|
|
121
|
+
class SessionResponse < Base
|
|
122
|
+
attribute :id
|
|
123
|
+
attribute :history_count
|
|
124
|
+
attribute :has_state
|
|
125
|
+
attribute :created_at
|
|
126
|
+
|
|
127
|
+
def self.from_session(session)
|
|
128
|
+
new(
|
|
129
|
+
id: session.id,
|
|
130
|
+
history_count: session.history.length,
|
|
131
|
+
has_state: !session.state.nil?,
|
|
132
|
+
created_at: Time.now
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Models
|
|
5
|
+
# Type definitions, constants, and validations for ACP.
|
|
6
|
+
#
|
|
7
|
+
# Contains all the enumerated types and validation helpers
|
|
8
|
+
# used throughout the protocol.
|
|
9
|
+
module Types
|
|
10
|
+
# Agent name must follow RFC 1123 DNS label format
|
|
11
|
+
AGENT_NAME_PATTERN = /\A[a-z0-9]([-a-z0-9]*[a-z0-9])?\z/
|
|
12
|
+
|
|
13
|
+
# Maximum length for agent names
|
|
14
|
+
AGENT_NAME_MAX_LENGTH = 63
|
|
15
|
+
|
|
16
|
+
# Run execution modes.
|
|
17
|
+
module RunMode
|
|
18
|
+
# Synchronous execution (blocks until complete)
|
|
19
|
+
SYNC = "sync"
|
|
20
|
+
# Asynchronous execution (returns immediately)
|
|
21
|
+
ASYNC = "async"
|
|
22
|
+
# Streaming execution (SSE events)
|
|
23
|
+
STREAM = "stream"
|
|
24
|
+
|
|
25
|
+
# All valid modes
|
|
26
|
+
ALL = [SYNC, ASYNC, STREAM].freeze
|
|
27
|
+
|
|
28
|
+
# Check if a mode is valid.
|
|
29
|
+
#
|
|
30
|
+
# @param mode [String] mode to check
|
|
31
|
+
# @return [Boolean] true if valid
|
|
32
|
+
def self.valid?(mode)
|
|
33
|
+
ALL.include?(mode)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Run status values.
|
|
38
|
+
module RunStatus
|
|
39
|
+
# Run created but not started
|
|
40
|
+
CREATED = "created"
|
|
41
|
+
# Run is executing
|
|
42
|
+
IN_PROGRESS = "in-progress"
|
|
43
|
+
# Run is waiting for client input
|
|
44
|
+
AWAITING = "awaiting"
|
|
45
|
+
# Run is being cancelled
|
|
46
|
+
CANCELLING = "cancelling"
|
|
47
|
+
# Run was cancelled
|
|
48
|
+
CANCELLED = "cancelled"
|
|
49
|
+
# Run completed successfully
|
|
50
|
+
COMPLETED = "completed"
|
|
51
|
+
# Run failed with error
|
|
52
|
+
FAILED = "failed"
|
|
53
|
+
|
|
54
|
+
# All valid statuses
|
|
55
|
+
ALL = [CREATED, IN_PROGRESS, AWAITING, CANCELLING, CANCELLED, COMPLETED, FAILED].freeze
|
|
56
|
+
|
|
57
|
+
# Terminal (final) statuses
|
|
58
|
+
TERMINAL = [COMPLETED, FAILED, CANCELLED].freeze
|
|
59
|
+
|
|
60
|
+
# Check if a status is valid.
|
|
61
|
+
#
|
|
62
|
+
# @param status [String] status to check
|
|
63
|
+
# @return [Boolean] true if valid
|
|
64
|
+
def self.valid?(status)
|
|
65
|
+
ALL.include?(status)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if a status is terminal (run is finished).
|
|
69
|
+
#
|
|
70
|
+
# @param status [String] status to check
|
|
71
|
+
# @return [Boolean] true if terminal
|
|
72
|
+
def self.terminal?(status)
|
|
73
|
+
TERMINAL.include?(status)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Link types for agent metadata.
|
|
78
|
+
module LinkType
|
|
79
|
+
# Link to source code repository
|
|
80
|
+
SOURCE_CODE = "source-code"
|
|
81
|
+
# Link to container image
|
|
82
|
+
CONTAINER_IMAGE = "container-image"
|
|
83
|
+
# Link to homepage
|
|
84
|
+
HOMEPAGE = "homepage"
|
|
85
|
+
# Link to documentation
|
|
86
|
+
DOCUMENTATION = "documentation"
|
|
87
|
+
|
|
88
|
+
# All valid link types
|
|
89
|
+
ALL = [SOURCE_CODE, CONTAINER_IMAGE, HOMEPAGE, DOCUMENTATION].freeze
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Dependency types for agent metadata.
|
|
93
|
+
module DependencyType
|
|
94
|
+
# Depends on another agent
|
|
95
|
+
AGENT = "agent"
|
|
96
|
+
# Depends on a tool
|
|
97
|
+
TOOL = "tool"
|
|
98
|
+
# Depends on a model
|
|
99
|
+
MODEL = "model"
|
|
100
|
+
|
|
101
|
+
# All valid dependency types
|
|
102
|
+
ALL = [AGENT, TOOL, MODEL].freeze
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Content encoding types.
|
|
106
|
+
module ContentEncoding
|
|
107
|
+
# Plain text (no encoding)
|
|
108
|
+
PLAIN = "plain"
|
|
109
|
+
# Base64 encoded
|
|
110
|
+
BASE64 = "base64"
|
|
111
|
+
|
|
112
|
+
# All valid encodings
|
|
113
|
+
ALL = [PLAIN, BASE64].freeze
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Event types for SSE streaming.
|
|
117
|
+
module EventType
|
|
118
|
+
# New message created
|
|
119
|
+
MESSAGE_CREATED = "message.created"
|
|
120
|
+
# Message part received
|
|
121
|
+
MESSAGE_PART = "message.part"
|
|
122
|
+
# Message completed
|
|
123
|
+
MESSAGE_COMPLETED = "message.completed"
|
|
124
|
+
# Run created
|
|
125
|
+
RUN_CREATED = "run.created"
|
|
126
|
+
# Run started executing
|
|
127
|
+
RUN_IN_PROGRESS = "run.in-progress"
|
|
128
|
+
# Run awaiting client input
|
|
129
|
+
RUN_AWAITING = "run.awaiting"
|
|
130
|
+
# Run completed successfully
|
|
131
|
+
RUN_COMPLETED = "run.completed"
|
|
132
|
+
# Run was cancelled
|
|
133
|
+
RUN_CANCELLED = "run.cancelled"
|
|
134
|
+
# Run failed
|
|
135
|
+
RUN_FAILED = "run.failed"
|
|
136
|
+
# Generic event
|
|
137
|
+
GENERIC = "generic"
|
|
138
|
+
# Error event
|
|
139
|
+
ERROR = "error"
|
|
140
|
+
|
|
141
|
+
# All valid event types
|
|
142
|
+
ALL = [
|
|
143
|
+
MESSAGE_CREATED, MESSAGE_PART, MESSAGE_COMPLETED,
|
|
144
|
+
RUN_CREATED, RUN_IN_PROGRESS, RUN_AWAITING,
|
|
145
|
+
RUN_COMPLETED, RUN_CANCELLED, RUN_FAILED,
|
|
146
|
+
GENERIC, ERROR
|
|
147
|
+
].freeze
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Message role types.
|
|
151
|
+
module Role
|
|
152
|
+
# Message from user
|
|
153
|
+
USER = "user"
|
|
154
|
+
# Message from agent
|
|
155
|
+
AGENT = "agent"
|
|
156
|
+
|
|
157
|
+
# Check if a role is valid.
|
|
158
|
+
#
|
|
159
|
+
# @param role [String] role to check
|
|
160
|
+
# @return [Boolean] true if "user", "agent", or "agent/name"
|
|
161
|
+
def self.valid?(role)
|
|
162
|
+
role == USER || role == AGENT || role.to_s.start_with?("agent/")
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
class << self
|
|
167
|
+
# Validate an agent name.
|
|
168
|
+
#
|
|
169
|
+
# @param name [String, nil] name to validate
|
|
170
|
+
# @return [Boolean] true if valid RFC 1123 DNS label
|
|
171
|
+
def valid_agent_name?(name)
|
|
172
|
+
return false if name.nil? || name.empty?
|
|
173
|
+
return false if name.length > AGENT_NAME_MAX_LENGTH
|
|
174
|
+
|
|
175
|
+
AGENT_NAME_PATTERN.match?(name)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Validate a UUID.
|
|
179
|
+
#
|
|
180
|
+
# @param value [String, nil] value to validate
|
|
181
|
+
# @return [Boolean] true if valid UUID format
|
|
182
|
+
def valid_uuid?(value)
|
|
183
|
+
return false if value.nil?
|
|
184
|
+
|
|
185
|
+
/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.match?(value.to_s)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Validate a URL.
|
|
189
|
+
#
|
|
190
|
+
# @param value [String, nil] value to validate
|
|
191
|
+
# @return [Boolean] true if valid HTTP/HTTPS URL
|
|
192
|
+
def valid_url?(value)
|
|
193
|
+
return false if value.nil?
|
|
194
|
+
|
|
195
|
+
uri = URI.parse(value.to_s)
|
|
196
|
+
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
197
|
+
rescue URI::InvalidURIError
|
|
198
|
+
false
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Generate a new UUID.
|
|
202
|
+
#
|
|
203
|
+
# @return [String] UUID string
|
|
204
|
+
def generate_uuid
|
|
205
|
+
SecureRandom.uuid
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|