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,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Storage
|
|
5
|
+
# PostgreSQL-backed storage for persistent deployments.
|
|
6
|
+
#
|
|
7
|
+
# Stores data in PostgreSQL using the Sequel gem.
|
|
8
|
+
# Automatically creates required tables on first use.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# storage = SimpleAcp::Storage::PostgreSQL.new(
|
|
12
|
+
# url: "postgres://localhost/simple_acp"
|
|
13
|
+
# )
|
|
14
|
+
# server = SimpleAcp::Server::Base.new(storage: storage)
|
|
15
|
+
class PostgreSQL < Base
|
|
16
|
+
# Initialize PostgreSQL storage.
|
|
17
|
+
#
|
|
18
|
+
# @param options [Hash] configuration options
|
|
19
|
+
# @option options [Sequel::Database] :db existing Sequel connection
|
|
20
|
+
# @option options [String] :url database URL (default: $DATABASE_URL or localhost)
|
|
21
|
+
# @option options [Boolean] :skip_setup skip automatic table creation
|
|
22
|
+
# @option options [String] :host database host
|
|
23
|
+
# @option options [Integer] :port database port
|
|
24
|
+
# @option options [String] :database database name
|
|
25
|
+
# @option options [String] :user database user
|
|
26
|
+
# @option options [String] :password database password
|
|
27
|
+
def initialize(options = {})
|
|
28
|
+
super
|
|
29
|
+
@db = options[:db] || connect_db(options)
|
|
30
|
+
setup_tables unless options[:skip_setup]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @see Base#get_run
|
|
34
|
+
def get_run(run_id)
|
|
35
|
+
row = @db[:acp_runs].where(run_id: run_id).first
|
|
36
|
+
return nil unless row
|
|
37
|
+
|
|
38
|
+
deserialize_run(row)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @see Base#save_run
|
|
42
|
+
def save_run(run)
|
|
43
|
+
data = {
|
|
44
|
+
run_id: run.run_id,
|
|
45
|
+
agent_name: run.agent_name,
|
|
46
|
+
session_id: run.session_id,
|
|
47
|
+
status: run.status,
|
|
48
|
+
output: run.output.to_json,
|
|
49
|
+
error: run.error&.to_json,
|
|
50
|
+
await_request: run.await_request&.to_json,
|
|
51
|
+
created_at: run.created_at,
|
|
52
|
+
finished_at: run.finished_at,
|
|
53
|
+
updated_at: Time.now
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if @db[:acp_runs].where(run_id: run.run_id).count.positive?
|
|
57
|
+
@db[:acp_runs].where(run_id: run.run_id).update(data)
|
|
58
|
+
else
|
|
59
|
+
@db[:acp_runs].insert(data)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
run
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @see Base#delete_run
|
|
66
|
+
def delete_run(run_id)
|
|
67
|
+
@db[:acp_events].where(run_id: run_id).delete
|
|
68
|
+
@db[:acp_runs].where(run_id: run_id).delete
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @see Base#list_runs
|
|
72
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
73
|
+
dataset = @db[:acp_runs]
|
|
74
|
+
dataset = dataset.where(agent_name: agent_name) if agent_name
|
|
75
|
+
dataset = dataset.where(session_id: session_id) if session_id
|
|
76
|
+
|
|
77
|
+
total = dataset.count
|
|
78
|
+
rows = dataset.order(Sequel.desc(:created_at)).limit(limit).offset(offset).all
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
runs: rows.map { |row| deserialize_run(row) },
|
|
82
|
+
total: total
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @see Base#get_session
|
|
87
|
+
def get_session(session_id)
|
|
88
|
+
row = @db[:acp_sessions].where(id: session_id).first
|
|
89
|
+
return nil unless row
|
|
90
|
+
|
|
91
|
+
deserialize_session(row)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @see Base#save_session
|
|
95
|
+
def save_session(session)
|
|
96
|
+
data = {
|
|
97
|
+
id: session.id,
|
|
98
|
+
history: session.history.to_json,
|
|
99
|
+
state: session.state&.to_json,
|
|
100
|
+
updated_at: Time.now
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if @db[:acp_sessions].where(id: session.id).count.positive?
|
|
104
|
+
@db[:acp_sessions].where(id: session.id).update(data)
|
|
105
|
+
else
|
|
106
|
+
data[:created_at] = Time.now
|
|
107
|
+
@db[:acp_sessions].insert(data)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
session
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @see Base#delete_session
|
|
114
|
+
def delete_session(session_id)
|
|
115
|
+
@db[:acp_sessions].where(id: session_id).delete
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @see Base#add_event
|
|
119
|
+
def add_event(run_id, event)
|
|
120
|
+
@db[:acp_events].insert(
|
|
121
|
+
run_id: run_id,
|
|
122
|
+
event_type: event.type,
|
|
123
|
+
data: event.to_json,
|
|
124
|
+
created_at: Time.now
|
|
125
|
+
)
|
|
126
|
+
event
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @see Base#get_events
|
|
130
|
+
def get_events(run_id, limit: 100, offset: 0)
|
|
131
|
+
rows = @db[:acp_events]
|
|
132
|
+
.where(run_id: run_id)
|
|
133
|
+
.order(:created_at)
|
|
134
|
+
.limit(limit)
|
|
135
|
+
.offset(offset)
|
|
136
|
+
.all
|
|
137
|
+
|
|
138
|
+
rows.map { |row| Models::Events.from_hash(JSON.parse(row[:data])) }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @see Base#close
|
|
142
|
+
def close
|
|
143
|
+
@db.disconnect if @db.respond_to?(:disconnect)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @see Base#ping
|
|
147
|
+
def ping
|
|
148
|
+
@db.test_connection
|
|
149
|
+
rescue StandardError
|
|
150
|
+
false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Clear all stored data.
|
|
154
|
+
#
|
|
155
|
+
# @return [void]
|
|
156
|
+
def clear!
|
|
157
|
+
@db[:acp_events].delete
|
|
158
|
+
@db[:acp_runs].delete
|
|
159
|
+
@db[:acp_sessions].delete
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def connect_db(options)
|
|
165
|
+
require "sequel"
|
|
166
|
+
Sequel.connect(
|
|
167
|
+
options[:url] || ENV.fetch("DATABASE_URL", "postgres://localhost/acp"),
|
|
168
|
+
**options.slice(:host, :port, :database, :user, :password)
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def setup_tables
|
|
173
|
+
@db.create_table?(:acp_runs) do
|
|
174
|
+
String :run_id, primary_key: true
|
|
175
|
+
String :agent_name, null: false, index: true
|
|
176
|
+
String :session_id, index: true
|
|
177
|
+
String :status, null: false
|
|
178
|
+
Text :output
|
|
179
|
+
Text :error
|
|
180
|
+
Text :await_request
|
|
181
|
+
DateTime :created_at
|
|
182
|
+
DateTime :finished_at
|
|
183
|
+
DateTime :updated_at
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
@db.create_table?(:acp_sessions) do
|
|
187
|
+
String :id, primary_key: true
|
|
188
|
+
Text :history
|
|
189
|
+
Text :state
|
|
190
|
+
DateTime :created_at
|
|
191
|
+
DateTime :updated_at
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
@db.create_table?(:acp_events) do
|
|
195
|
+
primary_key :id
|
|
196
|
+
String :run_id, null: false, index: true
|
|
197
|
+
String :event_type, null: false
|
|
198
|
+
Text :data
|
|
199
|
+
DateTime :created_at
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def deserialize_run(row)
|
|
204
|
+
output = row[:output] ? JSON.parse(row[:output]).map { |m| Models::Message.from_hash(m) } : []
|
|
205
|
+
error = row[:error] ? Models::Error.from_hash(JSON.parse(row[:error])) : nil
|
|
206
|
+
await_request = row[:await_request] ? Models::AwaitRequest.from_hash(JSON.parse(row[:await_request])) : nil
|
|
207
|
+
|
|
208
|
+
run = Models::Run.new(
|
|
209
|
+
run_id: row[:run_id],
|
|
210
|
+
agent_name: row[:agent_name],
|
|
211
|
+
session_id: row[:session_id],
|
|
212
|
+
status: row[:status]
|
|
213
|
+
)
|
|
214
|
+
run.instance_variable_set(:@output, output)
|
|
215
|
+
run.instance_variable_set(:@error, error)
|
|
216
|
+
run.instance_variable_set(:@await_request, await_request)
|
|
217
|
+
run.instance_variable_set(:@created_at, row[:created_at])
|
|
218
|
+
run.instance_variable_set(:@finished_at, row[:finished_at])
|
|
219
|
+
run
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def deserialize_session(row)
|
|
223
|
+
history = row[:history] ? JSON.parse(row[:history]).map { |m| Models::Message.from_hash(m) } : []
|
|
224
|
+
state = row[:state] ? JSON.parse(row[:state]) : nil
|
|
225
|
+
|
|
226
|
+
session = Models::Session.new(id: row[:id])
|
|
227
|
+
session.instance_variable_set(:@history, history)
|
|
228
|
+
session.instance_variable_set(:@state, state)
|
|
229
|
+
session
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Storage
|
|
5
|
+
# Redis-backed storage for distributed deployments.
|
|
6
|
+
#
|
|
7
|
+
# Stores data in Redis with configurable TTL for automatic expiration.
|
|
8
|
+
# Suitable for multi-process or multi-server deployments.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# storage = SimpleAcp::Storage::Redis.new(
|
|
12
|
+
# url: "redis://localhost:6379",
|
|
13
|
+
# ttl: 3600 # 1 hour
|
|
14
|
+
# )
|
|
15
|
+
# server = SimpleAcp::Server::Base.new(storage: storage)
|
|
16
|
+
class Redis < Base
|
|
17
|
+
# Default TTL of 24 hours
|
|
18
|
+
DEFAULT_TTL = 86_400
|
|
19
|
+
|
|
20
|
+
# Default key prefix
|
|
21
|
+
KEY_PREFIX = "acp:"
|
|
22
|
+
|
|
23
|
+
# Initialize Redis storage.
|
|
24
|
+
#
|
|
25
|
+
# @param options [Hash] configuration options
|
|
26
|
+
# @option options [Redis] :redis existing Redis connection
|
|
27
|
+
# @option options [String] :url Redis URL (default: $REDIS_URL or localhost)
|
|
28
|
+
# @option options [Integer] :ttl TTL in seconds (default: 86400)
|
|
29
|
+
# @option options [String] :prefix key prefix (default: "acp:")
|
|
30
|
+
# @option options [String] :host Redis host
|
|
31
|
+
# @option options [Integer] :port Redis port
|
|
32
|
+
# @option options [Integer] :db Redis database number
|
|
33
|
+
# @option options [String] :password Redis password
|
|
34
|
+
# @option options [Boolean] :ssl use SSL
|
|
35
|
+
def initialize(options = {})
|
|
36
|
+
super
|
|
37
|
+
@redis = options[:redis] || connect_redis(options)
|
|
38
|
+
@ttl = options[:ttl] || DEFAULT_TTL
|
|
39
|
+
@prefix = options[:prefix] || KEY_PREFIX
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @see Base#get_run
|
|
43
|
+
def get_run(run_id)
|
|
44
|
+
data = @redis.get(run_key(run_id))
|
|
45
|
+
return nil unless data
|
|
46
|
+
|
|
47
|
+
Models::Run.from_hash(JSON.parse(data))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @see Base#save_run
|
|
51
|
+
def save_run(run)
|
|
52
|
+
@redis.setex(run_key(run.run_id), @ttl, run.to_json)
|
|
53
|
+
|
|
54
|
+
# Index by agent name
|
|
55
|
+
@redis.sadd(agent_runs_key(run.agent_name), run.run_id)
|
|
56
|
+
|
|
57
|
+
# Index by session if present
|
|
58
|
+
if run.session_id
|
|
59
|
+
@redis.sadd(session_runs_key(run.session_id), run.run_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
run
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @see Base#delete_run
|
|
66
|
+
def delete_run(run_id)
|
|
67
|
+
run = get_run(run_id)
|
|
68
|
+
return unless run
|
|
69
|
+
|
|
70
|
+
@redis.del(run_key(run_id))
|
|
71
|
+
@redis.del(events_key(run_id))
|
|
72
|
+
@redis.srem(agent_runs_key(run.agent_name), run_id)
|
|
73
|
+
@redis.srem(session_runs_key(run.session_id), run_id) if run.session_id
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @see Base#list_runs
|
|
77
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
78
|
+
run_ids = if agent_name
|
|
79
|
+
@redis.smembers(agent_runs_key(agent_name))
|
|
80
|
+
elsif session_id
|
|
81
|
+
@redis.smembers(session_runs_key(session_id))
|
|
82
|
+
else
|
|
83
|
+
@redis.keys("#{@prefix}run:*").map { |k| k.sub("#{@prefix}run:", "") }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
runs = run_ids.filter_map { |id| get_run(id) }
|
|
87
|
+
runs = runs.sort_by { |r| r.created_at || Time.at(0) }.reverse
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
runs: runs.drop(offset).take(limit),
|
|
91
|
+
total: runs.length
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @see Base#get_session
|
|
96
|
+
def get_session(session_id)
|
|
97
|
+
data = @redis.get(session_key(session_id))
|
|
98
|
+
return nil unless data
|
|
99
|
+
|
|
100
|
+
Models::Session.from_hash(JSON.parse(data))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @see Base#save_session
|
|
104
|
+
def save_session(session)
|
|
105
|
+
@redis.setex(session_key(session.id), @ttl, session.to_json)
|
|
106
|
+
session
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @see Base#delete_session
|
|
110
|
+
def delete_session(session_id)
|
|
111
|
+
@redis.del(session_key(session_id))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @see Base#add_event
|
|
115
|
+
def add_event(run_id, event)
|
|
116
|
+
@redis.rpush(events_key(run_id), event.to_json)
|
|
117
|
+
@redis.expire(events_key(run_id), @ttl)
|
|
118
|
+
event
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @see Base#get_events
|
|
122
|
+
def get_events(run_id, limit: 100, offset: 0)
|
|
123
|
+
events_data = @redis.lrange(events_key(run_id), offset, offset + limit - 1)
|
|
124
|
+
events_data.map { |data| Models::Events.from_hash(JSON.parse(data)) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @see Base#close
|
|
128
|
+
def close
|
|
129
|
+
@redis.close if @redis.respond_to?(:close)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @see Base#ping
|
|
133
|
+
def ping
|
|
134
|
+
@redis.ping == "PONG"
|
|
135
|
+
rescue StandardError
|
|
136
|
+
false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Clear all stored data.
|
|
140
|
+
#
|
|
141
|
+
# @return [void]
|
|
142
|
+
def clear!
|
|
143
|
+
keys = @redis.keys("#{@prefix}*")
|
|
144
|
+
@redis.del(*keys) unless keys.empty?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def connect_redis(options)
|
|
150
|
+
require "redis"
|
|
151
|
+
::Redis.new(
|
|
152
|
+
url: options[:url] || ENV.fetch("REDIS_URL", "redis://localhost:6379"),
|
|
153
|
+
**options.slice(:host, :port, :db, :password, :ssl)
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def run_key(run_id)
|
|
158
|
+
"#{@prefix}run:#{run_id}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def session_key(session_id)
|
|
162
|
+
"#{@prefix}session:#{session_id}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def events_key(run_id)
|
|
166
|
+
"#{@prefix}events:#{run_id}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def agent_runs_key(agent_name)
|
|
170
|
+
"#{@prefix}agent_runs:#{agent_name}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def session_runs_key(session_id)
|
|
174
|
+
"#{@prefix}session_runs:#{session_id}"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/simple_acp.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "concurrent"
|
|
7
|
+
|
|
8
|
+
require_relative "simple_acp/version"
|
|
9
|
+
|
|
10
|
+
# Models
|
|
11
|
+
require_relative "simple_acp/models/errors"
|
|
12
|
+
require_relative "simple_acp/models/types"
|
|
13
|
+
require_relative "simple_acp/models/base"
|
|
14
|
+
require_relative "simple_acp/models/metadata"
|
|
15
|
+
require_relative "simple_acp/models/message_part"
|
|
16
|
+
require_relative "simple_acp/models/message"
|
|
17
|
+
require_relative "simple_acp/models/run"
|
|
18
|
+
require_relative "simple_acp/models/agent_manifest"
|
|
19
|
+
require_relative "simple_acp/models/session"
|
|
20
|
+
require_relative "simple_acp/models/events"
|
|
21
|
+
require_relative "simple_acp/models/await"
|
|
22
|
+
|
|
23
|
+
# Storage
|
|
24
|
+
require_relative "simple_acp/storage/base"
|
|
25
|
+
require_relative "simple_acp/storage/memory"
|
|
26
|
+
|
|
27
|
+
# Server
|
|
28
|
+
require_relative "simple_acp/server/context"
|
|
29
|
+
require_relative "simple_acp/server/agent"
|
|
30
|
+
require_relative "simple_acp/server/app"
|
|
31
|
+
require_relative "simple_acp/server/base"
|
|
32
|
+
|
|
33
|
+
# Client
|
|
34
|
+
require_relative "simple_acp/client/sse"
|
|
35
|
+
require_relative "simple_acp/client/base"
|
|
36
|
+
|
|
37
|
+
# Ruby implementation of the Agent Communication Protocol (ACP).
|
|
38
|
+
# Provides server and client implementations for AI agent communication
|
|
39
|
+
# with support for synchronous, asynchronous, and streaming execution modes.
|
|
40
|
+
#
|
|
41
|
+
# @example Creating a server
|
|
42
|
+
# server = SimpleAcp::Server::Base.new
|
|
43
|
+
# server.agent("echo", description: "Echoes input") do |context|
|
|
44
|
+
# SimpleAcp::Models::Message.agent(context.input.first.text_content)
|
|
45
|
+
# end
|
|
46
|
+
# server.run(port: 8000)
|
|
47
|
+
#
|
|
48
|
+
# @example Using the client
|
|
49
|
+
# client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
50
|
+
# run = client.run_sync(agent: "echo", input: "Hello!")
|
|
51
|
+
# puts run.output.first.text_content
|
|
52
|
+
#
|
|
53
|
+
# @see https://github.com/i-am-bee/acp Agent Communication Protocol specification
|
|
54
|
+
module SimpleAcp
|
|
55
|
+
# Base error class for all SimpleAcp exceptions
|
|
56
|
+
class Error < StandardError; end
|
|
57
|
+
|
|
58
|
+
# Raised when there is a configuration problem
|
|
59
|
+
class ConfigurationError < Error; end
|
|
60
|
+
|
|
61
|
+
# Raised when input validation fails
|
|
62
|
+
class ValidationError < Error; end
|
|
63
|
+
|
|
64
|
+
# Raised when a requested resource is not found
|
|
65
|
+
class NotFoundError < Error; end
|
|
66
|
+
|
|
67
|
+
# Raised when a run fails during execution
|
|
68
|
+
class RunError < Error; end
|
|
69
|
+
|
|
70
|
+
class << self
|
|
71
|
+
# @return [Logger, nil] optional logger for debugging
|
|
72
|
+
attr_accessor :logger
|
|
73
|
+
|
|
74
|
+
# Configure the SimpleAcp module
|
|
75
|
+
#
|
|
76
|
+
# @yield [self] yields self for configuration
|
|
77
|
+
# @return [void]
|
|
78
|
+
#
|
|
79
|
+
# @example
|
|
80
|
+
# SimpleAcp.configure do |config|
|
|
81
|
+
# config.logger = Logger.new(STDOUT)
|
|
82
|
+
# end
|
|
83
|
+
def configure
|
|
84
|
+
yield self if block_given?
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Convenience top-level aliases (outside the SimpleAcp module to avoid constant conflicts)
|
|
90
|
+
SimpleAcpServer = SimpleAcp::Server::Base
|
|
91
|
+
SimpleAcpClient = SimpleAcp::Client::Base
|
data/mkdocs.yml
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
site_name: SimpleAcp Documentation
|
|
2
|
+
site_description: "Ruby implementation of the Agent Communication Protocol (ACP) for AI agent communication"
|
|
3
|
+
site_author: "Dewayne VanHoozer"
|
|
4
|
+
site_url: "https://madbomber.github.io/simple_acp/"
|
|
5
|
+
|
|
6
|
+
repo_name: "MadBomber/simple_acp"
|
|
7
|
+
repo_url: "https://github.com/MadBomber/simple_acp"
|
|
8
|
+
edit_uri: "edit/main/docs/"
|
|
9
|
+
|
|
10
|
+
theme:
|
|
11
|
+
name: material
|
|
12
|
+
language: en
|
|
13
|
+
|
|
14
|
+
palette:
|
|
15
|
+
- media: "(prefers-color-scheme: light)"
|
|
16
|
+
scheme: default
|
|
17
|
+
primary: indigo
|
|
18
|
+
accent: amber
|
|
19
|
+
toggle:
|
|
20
|
+
icon: material/brightness-7
|
|
21
|
+
name: Switch to dark mode
|
|
22
|
+
|
|
23
|
+
- media: "(prefers-color-scheme: dark)"
|
|
24
|
+
scheme: slate
|
|
25
|
+
primary: indigo
|
|
26
|
+
accent: amber
|
|
27
|
+
toggle:
|
|
28
|
+
icon: material/brightness-4
|
|
29
|
+
name: Switch to light mode
|
|
30
|
+
|
|
31
|
+
font:
|
|
32
|
+
text: Roboto
|
|
33
|
+
code: Roboto Mono
|
|
34
|
+
|
|
35
|
+
features:
|
|
36
|
+
- navigation.instant
|
|
37
|
+
- navigation.tracking
|
|
38
|
+
- navigation.tabs
|
|
39
|
+
- navigation.tabs.sticky
|
|
40
|
+
- navigation.sections
|
|
41
|
+
- navigation.path
|
|
42
|
+
- navigation.indexes
|
|
43
|
+
- navigation.top
|
|
44
|
+
- toc.follow
|
|
45
|
+
- search.suggest
|
|
46
|
+
- search.highlight
|
|
47
|
+
- search.share
|
|
48
|
+
- header.autohide
|
|
49
|
+
- content.code.copy
|
|
50
|
+
- content.code.annotate
|
|
51
|
+
- content.tabs.link
|
|
52
|
+
- content.tooltips
|
|
53
|
+
- content.action.edit
|
|
54
|
+
- content.action.view
|
|
55
|
+
|
|
56
|
+
plugins:
|
|
57
|
+
- search:
|
|
58
|
+
separator: '[\s\-,:!=\[\]()"\`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
|
|
59
|
+
|
|
60
|
+
markdown_extensions:
|
|
61
|
+
- abbr
|
|
62
|
+
- admonition
|
|
63
|
+
- attr_list
|
|
64
|
+
- def_list
|
|
65
|
+
- footnotes
|
|
66
|
+
- md_in_html
|
|
67
|
+
- toc:
|
|
68
|
+
permalink: true
|
|
69
|
+
title: On this page
|
|
70
|
+
- pymdownx.arithmatex:
|
|
71
|
+
generic: true
|
|
72
|
+
- pymdownx.betterem:
|
|
73
|
+
smart_enable: all
|
|
74
|
+
- pymdownx.caret
|
|
75
|
+
- pymdownx.details
|
|
76
|
+
- pymdownx.emoji:
|
|
77
|
+
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
|
78
|
+
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
|
79
|
+
- pymdownx.highlight:
|
|
80
|
+
anchor_linenums: true
|
|
81
|
+
line_spans: __span
|
|
82
|
+
pygments_lang_class: true
|
|
83
|
+
- pymdownx.inlinehilite
|
|
84
|
+
- pymdownx.keys
|
|
85
|
+
- pymdownx.magiclink:
|
|
86
|
+
repo_url_shorthand: true
|
|
87
|
+
user: MadBomber
|
|
88
|
+
repo: simple_acp
|
|
89
|
+
- pymdownx.mark
|
|
90
|
+
- pymdownx.smartsymbols
|
|
91
|
+
- pymdownx.superfences:
|
|
92
|
+
custom_fences:
|
|
93
|
+
- name: mermaid
|
|
94
|
+
class: mermaid
|
|
95
|
+
format: !!python/name:pymdownx.superfences.fence_code_format
|
|
96
|
+
- pymdownx.tabbed:
|
|
97
|
+
alternate_style: true
|
|
98
|
+
- pymdownx.tasklist:
|
|
99
|
+
custom_checkbox: true
|
|
100
|
+
- pymdownx.tilde
|
|
101
|
+
|
|
102
|
+
extra:
|
|
103
|
+
version: 0.1.0
|
|
104
|
+
social:
|
|
105
|
+
- icon: fontawesome/brands/github
|
|
106
|
+
link: https://github.com/MadBomber/simple_acp
|
|
107
|
+
name: GitHub Repository
|
|
108
|
+
- icon: fontawesome/solid/gem
|
|
109
|
+
link: https://rubygems.org/gems/simple_acp
|
|
110
|
+
name: RubyGems
|
|
111
|
+
generator: false
|
|
112
|
+
|
|
113
|
+
nav:
|
|
114
|
+
- Home: index.md
|
|
115
|
+
- Getting Started:
|
|
116
|
+
- getting-started/index.md
|
|
117
|
+
- Installation: getting-started/installation.md
|
|
118
|
+
- Quick Start: getting-started/quick-start.md
|
|
119
|
+
- Configuration: getting-started/configuration.md
|
|
120
|
+
- Examples: examples.md
|
|
121
|
+
- Core Concepts:
|
|
122
|
+
- core-concepts/index.md
|
|
123
|
+
- Messages: core-concepts/messages.md
|
|
124
|
+
- Agents: core-concepts/agents.md
|
|
125
|
+
- Runs: core-concepts/runs.md
|
|
126
|
+
- Sessions: core-concepts/sessions.md
|
|
127
|
+
- Events: core-concepts/events.md
|
|
128
|
+
- Server Guide:
|
|
129
|
+
- server/index.md
|
|
130
|
+
- Creating Agents: server/creating-agents.md
|
|
131
|
+
- Streaming Responses: server/streaming.md
|
|
132
|
+
- Multi-Turn Conversations: server/multi-turn.md
|
|
133
|
+
- HTTP Endpoints: server/http-endpoints.md
|
|
134
|
+
- Client Guide:
|
|
135
|
+
- client/index.md
|
|
136
|
+
- Sync & Async: client/sync-async.md
|
|
137
|
+
- Streaming: client/streaming.md
|
|
138
|
+
- Session Management: client/sessions.md
|
|
139
|
+
- Storage Backends:
|
|
140
|
+
- storage/index.md
|
|
141
|
+
- Memory: storage/memory.md
|
|
142
|
+
- Redis: storage/redis.md
|
|
143
|
+
- PostgreSQL: storage/postgresql.md
|
|
144
|
+
- Custom Backends: storage/custom.md
|
|
145
|
+
- API Reference:
|
|
146
|
+
- api/index.md
|
|
147
|
+
- Server::Base: api/server-base.md
|
|
148
|
+
- Client::Base: api/client-base.md
|
|
149
|
+
- Models: api/models.md
|
|
150
|
+
- Storage: api/storage.md
|
|
151
|
+
|
|
152
|
+
copyright: Copyright © 2025 Dewayne VanHoozer
|
data/sig/simple_acp.rbs
ADDED