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,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Server
|
|
5
|
+
# Request context passed to agent handlers during execution.
|
|
6
|
+
#
|
|
7
|
+
# Provides access to input messages, session state, conversation history,
|
|
8
|
+
# and methods for controlling execution flow.
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing input
|
|
11
|
+
# server.agent("echo") do |context|
|
|
12
|
+
# text = context.input.first.text_content
|
|
13
|
+
# Models::Message.agent("You said: #{text}")
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# @example Using session state
|
|
17
|
+
# server.agent("counter") do |context|
|
|
18
|
+
# count = (context.state || 0) + 1
|
|
19
|
+
# context.set_state(count)
|
|
20
|
+
# Models::Message.agent("Count: #{count}")
|
|
21
|
+
# end
|
|
22
|
+
class Context
|
|
23
|
+
# @return [Models::Run] the current run being executed
|
|
24
|
+
attr_reader :run
|
|
25
|
+
|
|
26
|
+
# @return [Models::Session, nil] the session (if any)
|
|
27
|
+
attr_reader :session
|
|
28
|
+
|
|
29
|
+
# @return [Array<Models::Message>] input messages for this run
|
|
30
|
+
attr_reader :input
|
|
31
|
+
|
|
32
|
+
# @return [Server::Base] reference to the server
|
|
33
|
+
attr_reader :server
|
|
34
|
+
|
|
35
|
+
# Initialize a new context.
|
|
36
|
+
#
|
|
37
|
+
# @param run [Models::Run] the run being executed
|
|
38
|
+
# @param session [Models::Session, nil] optional session
|
|
39
|
+
# @param input [Array<Models::Message>] input messages
|
|
40
|
+
# @param server [Server::Base] the server instance
|
|
41
|
+
def initialize(run:, session:, input:, server:)
|
|
42
|
+
@run = run
|
|
43
|
+
@session = session
|
|
44
|
+
@input = input
|
|
45
|
+
@server = server
|
|
46
|
+
@cancelled = false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [String] the name of the agent being executed
|
|
50
|
+
def agent_name
|
|
51
|
+
@run.agent_name
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [String] the unique run ID
|
|
55
|
+
def run_id
|
|
56
|
+
@run.run_id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [String, nil] the session ID if a session is active
|
|
60
|
+
def session_id
|
|
61
|
+
@session&.id
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if the run has been cancelled.
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if cancelled
|
|
67
|
+
def cancelled?
|
|
68
|
+
@cancelled
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Mark the run as cancelled.
|
|
72
|
+
#
|
|
73
|
+
# @return [void]
|
|
74
|
+
def cancel!
|
|
75
|
+
@cancelled = true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get conversation history from the session.
|
|
79
|
+
#
|
|
80
|
+
# @return [Array<Models::Message>] previous messages in the session
|
|
81
|
+
def history
|
|
82
|
+
@session&.history || []
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get arbitrary state data from the session.
|
|
86
|
+
#
|
|
87
|
+
# @return [Object, nil] the stored state data
|
|
88
|
+
def state
|
|
89
|
+
@session&.state
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Update the session state with new data.
|
|
93
|
+
#
|
|
94
|
+
# @param new_state [Object] the state data to store
|
|
95
|
+
# @return [void]
|
|
96
|
+
def set_state(new_state)
|
|
97
|
+
@session&.set_state(new_state)
|
|
98
|
+
@server.storage.save_session(@session) if @session
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Request additional input from the client, pausing execution.
|
|
102
|
+
#
|
|
103
|
+
# This puts the run into an "awaiting" state until the client
|
|
104
|
+
# calls resume with the requested input.
|
|
105
|
+
#
|
|
106
|
+
# @param prompt_message [Models::Message] message to display to the client
|
|
107
|
+
# @return [RunYieldAwait] yield this from your agent to pause
|
|
108
|
+
#
|
|
109
|
+
# @example Multi-turn conversation
|
|
110
|
+
# server.agent("questioner") do |context|
|
|
111
|
+
# Enumerator.new do |yielder|
|
|
112
|
+
# result = context.await_message(Models::Message.agent("What is your name?"))
|
|
113
|
+
# yielder << result
|
|
114
|
+
# name = context.resume_message&.text_content
|
|
115
|
+
# yielder << RunYield.new(Models::Message.agent("Hello, #{name}!"))
|
|
116
|
+
# end
|
|
117
|
+
# end
|
|
118
|
+
def await_message(prompt_message)
|
|
119
|
+
request = Models::MessageAwaitRequest.new(message: prompt_message)
|
|
120
|
+
@run.await!(request)
|
|
121
|
+
@server.storage.save_run(@run)
|
|
122
|
+
|
|
123
|
+
# Return a result that indicates the run is awaiting
|
|
124
|
+
RunYieldAwait.new(request: request)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Log a message for debugging or trajectory tracking.
|
|
128
|
+
#
|
|
129
|
+
# @param message [String] the message to log
|
|
130
|
+
# @return [void]
|
|
131
|
+
def log(message)
|
|
132
|
+
SimpleAcp.logger&.info("[#{agent_name}] #{message}")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Get the resume message (only available during resume).
|
|
136
|
+
#
|
|
137
|
+
# This method returns nil for initial contexts. Override in
|
|
138
|
+
# ResumeContext to return the actual resume message.
|
|
139
|
+
#
|
|
140
|
+
# @return [Models::Message, nil] nil for initial context
|
|
141
|
+
def resume_message
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Wrapper for yielding output messages from an agent.
|
|
147
|
+
#
|
|
148
|
+
# Use this when streaming responses from an Enumerator-based agent handler.
|
|
149
|
+
#
|
|
150
|
+
# @example
|
|
151
|
+
# Enumerator.new do |yielder|
|
|
152
|
+
# yielder << RunYield.new(Models::Message.agent("First message"))
|
|
153
|
+
# yielder << RunYield.new(Models::Message.agent("Second message"))
|
|
154
|
+
# end
|
|
155
|
+
class RunYield
|
|
156
|
+
# @return [Models::Message] the message to output
|
|
157
|
+
attr_reader :message
|
|
158
|
+
|
|
159
|
+
# Create a new message yield.
|
|
160
|
+
#
|
|
161
|
+
# @param message [Models::Message, Hash] the message to yield
|
|
162
|
+
def initialize(message)
|
|
163
|
+
@message = message.is_a?(Models::Message) ? message : Models::Message.from_hash(message)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Wrapper for pausing execution and awaiting client input.
|
|
168
|
+
#
|
|
169
|
+
# Yield this from an agent to pause execution until the client
|
|
170
|
+
# resumes with the requested input.
|
|
171
|
+
class RunYieldAwait
|
|
172
|
+
# @return [Models::AwaitRequest] the await request details
|
|
173
|
+
attr_reader :request
|
|
174
|
+
|
|
175
|
+
# Create a new await yield.
|
|
176
|
+
#
|
|
177
|
+
# @param request [Models::AwaitRequest] the request for client input
|
|
178
|
+
def initialize(request:)
|
|
179
|
+
@request = request
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Extended context for resuming an awaited run.
|
|
184
|
+
#
|
|
185
|
+
# Contains the client's response to the await request.
|
|
186
|
+
class ResumeContext < Context
|
|
187
|
+
# @return [Models::AwaitResume] the client's resume payload
|
|
188
|
+
attr_reader :await_resume
|
|
189
|
+
|
|
190
|
+
# Initialize a resume context.
|
|
191
|
+
#
|
|
192
|
+
# @param run [Models::Run] the run being resumed
|
|
193
|
+
# @param session [Models::Session, nil] optional session
|
|
194
|
+
# @param input [Array<Models::Message>] original input messages
|
|
195
|
+
# @param server [Server::Base] the server instance
|
|
196
|
+
# @param await_resume [Models::AwaitResume] the client's response
|
|
197
|
+
def initialize(run:, session:, input:, server:, await_resume:)
|
|
198
|
+
super(run: run, session: session, input: input, server: server)
|
|
199
|
+
@await_resume = await_resume
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Get the message from the client's resume payload.
|
|
203
|
+
#
|
|
204
|
+
# @return [Models::Message, nil] the client's response message
|
|
205
|
+
def resume_message
|
|
206
|
+
@await_resume&.message
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
require "falcon"
|
|
5
|
+
require "falcon/server"
|
|
6
|
+
require "protocol/rack"
|
|
7
|
+
require "io/endpoint"
|
|
8
|
+
require "io/endpoint/address_endpoint"
|
|
9
|
+
|
|
10
|
+
module SimpleAcp
|
|
11
|
+
module Server
|
|
12
|
+
# Falcon-based server runner using fiber concurrency.
|
|
13
|
+
#
|
|
14
|
+
# Provides efficient handling of SSE streams and long-lived
|
|
15
|
+
# connections through Async's fiber scheduler.
|
|
16
|
+
#
|
|
17
|
+
# @example Starting a server
|
|
18
|
+
# app = server.to_app
|
|
19
|
+
# FalconRunner.run(app, port: 8000)
|
|
20
|
+
module FalconRunner
|
|
21
|
+
# Start the server using Falcon.
|
|
22
|
+
#
|
|
23
|
+
# @param app [#call] the Rack application
|
|
24
|
+
# @param port [Integer] port to bind to (default: 8000)
|
|
25
|
+
# @param host [String] host to bind to (default: "0.0.0.0")
|
|
26
|
+
# @param options [Hash] additional options
|
|
27
|
+
# @option options [Integer] :count number of worker processes
|
|
28
|
+
# @return [void]
|
|
29
|
+
def self.run(app, port: 8000, host: "0.0.0.0", **options)
|
|
30
|
+
endpoint = IO::Endpoint::AddressEndpoint.new(Addrinfo.tcp(host, port))
|
|
31
|
+
|
|
32
|
+
puts "ACP Server (Falcon) running on http://#{host}:#{port}"
|
|
33
|
+
|
|
34
|
+
server_task = nil
|
|
35
|
+
|
|
36
|
+
# Handle interrupt for graceful shutdown
|
|
37
|
+
trap("INT") do
|
|
38
|
+
puts "\nShutting down..."
|
|
39
|
+
Thread.main.raise(Interrupt)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
trap("TERM") do
|
|
43
|
+
puts "\nShutting down..."
|
|
44
|
+
Thread.main.raise(Interrupt)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
Sync do |task|
|
|
49
|
+
rack_app = Protocol::Rack::Adapter.new(app)
|
|
50
|
+
server = Falcon::Server.new(rack_app, endpoint, protocol: Async::HTTP::Protocol::HTTP1, scheme: "http")
|
|
51
|
+
|
|
52
|
+
server_task = server.run
|
|
53
|
+
server_task.wait
|
|
54
|
+
end
|
|
55
|
+
rescue Interrupt
|
|
56
|
+
# Graceful shutdown
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Storage
|
|
5
|
+
# Abstract base class for storage backends.
|
|
6
|
+
#
|
|
7
|
+
# Defines the interface for storing runs, sessions, and events.
|
|
8
|
+
# Implementations include Memory, Redis, and PostgreSQL.
|
|
9
|
+
#
|
|
10
|
+
# @abstract Subclass and implement all methods
|
|
11
|
+
#
|
|
12
|
+
# @example Custom storage backend
|
|
13
|
+
# class MyStorage < SimpleAcp::Storage::Base
|
|
14
|
+
# def get_run(run_id)
|
|
15
|
+
# # ...
|
|
16
|
+
# end
|
|
17
|
+
# # ... implement other methods
|
|
18
|
+
# end
|
|
19
|
+
class Base
|
|
20
|
+
# Initialize the storage backend.
|
|
21
|
+
#
|
|
22
|
+
# @param options [Hash] backend-specific options
|
|
23
|
+
def initialize(options = {})
|
|
24
|
+
@options = options
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Retrieve a run by ID.
|
|
28
|
+
#
|
|
29
|
+
# @param run_id [String] the run ID
|
|
30
|
+
# @return [Models::Run, nil] the run or nil if not found
|
|
31
|
+
# @raise [NotImplementedError] if not implemented
|
|
32
|
+
def get_run(run_id)
|
|
33
|
+
raise NotImplementedError, "#{self.class}#get_run must be implemented"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Save a run.
|
|
37
|
+
#
|
|
38
|
+
# @param run [Models::Run] the run to save
|
|
39
|
+
# @return [Models::Run] the saved run
|
|
40
|
+
# @raise [NotImplementedError] if not implemented
|
|
41
|
+
def save_run(run)
|
|
42
|
+
raise NotImplementedError, "#{self.class}#save_run must be implemented"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Delete a run.
|
|
46
|
+
#
|
|
47
|
+
# @param run_id [String] the run ID to delete
|
|
48
|
+
# @return [void]
|
|
49
|
+
# @raise [NotImplementedError] if not implemented
|
|
50
|
+
def delete_run(run_id)
|
|
51
|
+
raise NotImplementedError, "#{self.class}#delete_run must be implemented"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# List runs with optional filtering.
|
|
55
|
+
#
|
|
56
|
+
# @param agent_name [String, nil] filter by agent name
|
|
57
|
+
# @param session_id [String, nil] filter by session ID
|
|
58
|
+
# @param limit [Integer] maximum number to return (default: 10)
|
|
59
|
+
# @param offset [Integer] number to skip (default: 0)
|
|
60
|
+
# @return [Hash] with :runs and :total keys
|
|
61
|
+
# @raise [NotImplementedError] if not implemented
|
|
62
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
63
|
+
raise NotImplementedError, "#{self.class}#list_runs must be implemented"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Retrieve a session by ID.
|
|
67
|
+
#
|
|
68
|
+
# @param session_id [String] the session ID
|
|
69
|
+
# @return [Models::Session, nil] the session or nil if not found
|
|
70
|
+
# @raise [NotImplementedError] if not implemented
|
|
71
|
+
def get_session(session_id)
|
|
72
|
+
raise NotImplementedError, "#{self.class}#get_session must be implemented"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Save a session.
|
|
76
|
+
#
|
|
77
|
+
# @param session [Models::Session] the session to save
|
|
78
|
+
# @return [Models::Session] the saved session
|
|
79
|
+
# @raise [NotImplementedError] if not implemented
|
|
80
|
+
def save_session(session)
|
|
81
|
+
raise NotImplementedError, "#{self.class}#save_session must be implemented"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Delete a session.
|
|
85
|
+
#
|
|
86
|
+
# @param session_id [String] the session ID to delete
|
|
87
|
+
# @return [void]
|
|
88
|
+
# @raise [NotImplementedError] if not implemented
|
|
89
|
+
def delete_session(session_id)
|
|
90
|
+
raise NotImplementedError, "#{self.class}#delete_session must be implemented"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Add an event to a run.
|
|
94
|
+
#
|
|
95
|
+
# @param run_id [String] the run ID
|
|
96
|
+
# @param event [Models::Event] the event to add
|
|
97
|
+
# @return [Models::Event] the added event
|
|
98
|
+
# @raise [NotImplementedError] if not implemented
|
|
99
|
+
def add_event(run_id, event)
|
|
100
|
+
raise NotImplementedError, "#{self.class}#add_event must be implemented"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get events for a run.
|
|
104
|
+
#
|
|
105
|
+
# @param run_id [String] the run ID
|
|
106
|
+
# @param limit [Integer] maximum number to return (default: 100)
|
|
107
|
+
# @param offset [Integer] number to skip (default: 0)
|
|
108
|
+
# @return [Array<Models::Event>] list of events
|
|
109
|
+
# @raise [NotImplementedError] if not implemented
|
|
110
|
+
def get_events(run_id, limit: 100, offset: 0)
|
|
111
|
+
raise NotImplementedError, "#{self.class}#get_events must be implemented"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Close the storage connection.
|
|
115
|
+
#
|
|
116
|
+
# @return [void]
|
|
117
|
+
def close
|
|
118
|
+
# Override in subclasses if needed
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Check if the storage is accessible.
|
|
122
|
+
#
|
|
123
|
+
# @return [Boolean] true if accessible
|
|
124
|
+
def ping
|
|
125
|
+
true
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
|
|
5
|
+
module SimpleAcp
|
|
6
|
+
module Storage
|
|
7
|
+
# Thread-safe in-memory storage backend.
|
|
8
|
+
#
|
|
9
|
+
# This is the default storage backend. Data is lost when the process exits.
|
|
10
|
+
# Uses concurrent-ruby for thread safety.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# storage = SimpleAcp::Storage::Memory.new
|
|
14
|
+
# server = SimpleAcp::Server::Base.new(storage: storage)
|
|
15
|
+
class Memory < Base
|
|
16
|
+
def initialize(options = {})
|
|
17
|
+
super
|
|
18
|
+
@runs = Concurrent::Map.new
|
|
19
|
+
@sessions = Concurrent::Map.new
|
|
20
|
+
@events = Concurrent::Map.new
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @see Base#get_run
|
|
25
|
+
def get_run(run_id)
|
|
26
|
+
@runs[run_id]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @see Base#save_run
|
|
30
|
+
def save_run(run)
|
|
31
|
+
@runs[run.run_id] = run
|
|
32
|
+
run
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @see Base#delete_run
|
|
36
|
+
def delete_run(run_id)
|
|
37
|
+
@events.delete(run_id)
|
|
38
|
+
@runs.delete(run_id)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @see Base#list_runs
|
|
42
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
43
|
+
runs = @runs.values
|
|
44
|
+
|
|
45
|
+
runs = runs.select { |r| r.agent_name == agent_name } if agent_name
|
|
46
|
+
runs = runs.select { |r| r.session_id == session_id } if session_id
|
|
47
|
+
|
|
48
|
+
runs = runs.sort_by { |r| r.created_at || Time.at(0) }.reverse
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
runs: runs.drop(offset).take(limit),
|
|
52
|
+
total: runs.length
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @see Base#get_session
|
|
57
|
+
def get_session(session_id)
|
|
58
|
+
@sessions[session_id]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @see Base#save_session
|
|
62
|
+
def save_session(session)
|
|
63
|
+
@sessions[session.id] = session
|
|
64
|
+
session
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @see Base#delete_session
|
|
68
|
+
def delete_session(session_id)
|
|
69
|
+
@sessions.delete(session_id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @see Base#add_event
|
|
73
|
+
def add_event(run_id, event)
|
|
74
|
+
@mutex.synchronize do
|
|
75
|
+
@events[run_id] ||= []
|
|
76
|
+
@events[run_id] << event
|
|
77
|
+
end
|
|
78
|
+
event
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @see Base#get_events
|
|
82
|
+
def get_events(run_id, limit: 100, offset: 0)
|
|
83
|
+
events = @events[run_id] || []
|
|
84
|
+
events.drop(offset).take(limit)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Clear all stored data.
|
|
88
|
+
#
|
|
89
|
+
# @return [void]
|
|
90
|
+
def clear!
|
|
91
|
+
@runs.clear
|
|
92
|
+
@sessions.clear
|
|
93
|
+
@events.clear
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get storage statistics.
|
|
97
|
+
#
|
|
98
|
+
# @return [Hash] counts of runs, sessions, and events
|
|
99
|
+
def stats
|
|
100
|
+
{
|
|
101
|
+
runs: @runs.size,
|
|
102
|
+
sessions: @sessions.size,
|
|
103
|
+
events: @events.values.sum(&:length)
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|