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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/CHANGELOG.md +5 -0
  4. data/COMMITS.md +196 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +385 -0
  7. data/Rakefile +13 -0
  8. data/docs/api/client-base.md +383 -0
  9. data/docs/api/index.md +159 -0
  10. data/docs/api/models.md +286 -0
  11. data/docs/api/server-base.md +379 -0
  12. data/docs/api/storage.md +347 -0
  13. data/docs/assets/images/simple_acp.jpg +0 -0
  14. data/docs/client/index.md +279 -0
  15. data/docs/client/sessions.md +324 -0
  16. data/docs/client/streaming.md +345 -0
  17. data/docs/client/sync-async.md +308 -0
  18. data/docs/core-concepts/agents.md +253 -0
  19. data/docs/core-concepts/events.md +337 -0
  20. data/docs/core-concepts/index.md +147 -0
  21. data/docs/core-concepts/messages.md +211 -0
  22. data/docs/core-concepts/runs.md +278 -0
  23. data/docs/core-concepts/sessions.md +281 -0
  24. data/docs/examples.md +659 -0
  25. data/docs/getting-started/configuration.md +166 -0
  26. data/docs/getting-started/index.md +62 -0
  27. data/docs/getting-started/installation.md +95 -0
  28. data/docs/getting-started/quick-start.md +189 -0
  29. data/docs/index.md +119 -0
  30. data/docs/server/creating-agents.md +360 -0
  31. data/docs/server/http-endpoints.md +411 -0
  32. data/docs/server/index.md +218 -0
  33. data/docs/server/multi-turn.md +329 -0
  34. data/docs/server/streaming.md +315 -0
  35. data/docs/storage/custom.md +414 -0
  36. data/docs/storage/index.md +176 -0
  37. data/docs/storage/memory.md +198 -0
  38. data/docs/storage/postgresql.md +350 -0
  39. data/docs/storage/redis.md +287 -0
  40. data/examples/01_basic/client.rb +88 -0
  41. data/examples/01_basic/server.rb +100 -0
  42. data/examples/02_async_execution/client.rb +107 -0
  43. data/examples/02_async_execution/server.rb +56 -0
  44. data/examples/03_run_management/client.rb +115 -0
  45. data/examples/03_run_management/server.rb +84 -0
  46. data/examples/04_rich_messages/client.rb +160 -0
  47. data/examples/04_rich_messages/server.rb +180 -0
  48. data/examples/05_await_resume/client.rb +164 -0
  49. data/examples/05_await_resume/server.rb +114 -0
  50. data/examples/06_agent_metadata/client.rb +188 -0
  51. data/examples/06_agent_metadata/server.rb +192 -0
  52. data/examples/README.md +252 -0
  53. data/examples/run_demo.sh +137 -0
  54. data/lib/simple_acp/client/base.rb +448 -0
  55. data/lib/simple_acp/client/sse.rb +141 -0
  56. data/lib/simple_acp/models/agent_manifest.rb +129 -0
  57. data/lib/simple_acp/models/await.rb +123 -0
  58. data/lib/simple_acp/models/base.rb +147 -0
  59. data/lib/simple_acp/models/errors.rb +102 -0
  60. data/lib/simple_acp/models/events.rb +256 -0
  61. data/lib/simple_acp/models/message.rb +235 -0
  62. data/lib/simple_acp/models/message_part.rb +225 -0
  63. data/lib/simple_acp/models/metadata.rb +161 -0
  64. data/lib/simple_acp/models/run.rb +298 -0
  65. data/lib/simple_acp/models/session.rb +137 -0
  66. data/lib/simple_acp/models/types.rb +210 -0
  67. data/lib/simple_acp/server/agent.rb +116 -0
  68. data/lib/simple_acp/server/app.rb +264 -0
  69. data/lib/simple_acp/server/base.rb +510 -0
  70. data/lib/simple_acp/server/context.rb +210 -0
  71. data/lib/simple_acp/server/falcon_runner.rb +61 -0
  72. data/lib/simple_acp/storage/base.rb +129 -0
  73. data/lib/simple_acp/storage/memory.rb +108 -0
  74. data/lib/simple_acp/storage/postgresql.rb +233 -0
  75. data/lib/simple_acp/storage/redis.rb +178 -0
  76. data/lib/simple_acp/version.rb +5 -0
  77. data/lib/simple_acp.rb +91 -0
  78. data/mkdocs.yml +152 -0
  79. data/sig/simple_acp.rbs +4 -0
  80. metadata +418 -0
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleAcp
4
+ module Models
5
+ # Base class for await requests.
6
+ #
7
+ # When an agent needs client input during execution, it creates
8
+ # an await request describing what input is needed.
9
+ #
10
+ # @abstract Subclass for specific await types
11
+ class AwaitRequest < Base
12
+ # @!attribute [r] type
13
+ # @return [String] await request type
14
+ attribute :type, required: true
15
+
16
+ # Parse from hash, creating the appropriate subclass.
17
+ #
18
+ # @param hash [Hash, nil] await request data
19
+ # @return [AwaitRequest, nil] the request or nil
20
+ def self.from_hash(hash)
21
+ return nil if hash.nil?
22
+
23
+ type = hash["type"] || hash[:type]
24
+
25
+ case type
26
+ when "message"
27
+ MessageAwaitRequest.from_hash(hash)
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
33
+
34
+ # Request for a message from the client.
35
+ #
36
+ # Used when an agent needs text or other message input to continue.
37
+ class MessageAwaitRequest < AwaitRequest
38
+ # @!attribute [r] message
39
+ # @return [Message, nil] prompt message to display to client
40
+ attribute :message
41
+
42
+ def initialize(**kwargs)
43
+ kwargs[:type] = "message"
44
+ super
45
+ end
46
+
47
+ def self.from_hash(hash)
48
+ return nil if hash.nil?
49
+
50
+ instance = allocate
51
+ instance.instance_variable_set(:@type, "message")
52
+
53
+ if hash["message"] || hash[:message]
54
+ instance.instance_variable_set(
55
+ :@message,
56
+ Message.from_hash(hash["message"] || hash[:message])
57
+ )
58
+ end
59
+
60
+ instance
61
+ end
62
+ end
63
+
64
+ # Base class for await resume payloads.
65
+ #
66
+ # Clients send resume payloads to continue an awaited run
67
+ # with the requested input.
68
+ #
69
+ # @abstract Subclass for specific resume types
70
+ class AwaitResume < Base
71
+ # @!attribute [r] type
72
+ # @return [String] resume type
73
+ attribute :type, required: true
74
+
75
+ # Parse from hash, creating the appropriate subclass.
76
+ #
77
+ # @param hash [Hash, nil] resume payload data
78
+ # @return [AwaitResume, nil] the resume or nil
79
+ def self.from_hash(hash)
80
+ return nil if hash.nil?
81
+
82
+ type = hash["type"] || hash[:type]
83
+
84
+ case type
85
+ when "message"
86
+ MessageAwaitResume.from_hash(hash)
87
+ else
88
+ super
89
+ end
90
+ end
91
+ end
92
+
93
+ # Resume payload containing a message response.
94
+ #
95
+ # Used to provide text or other message input to an awaiting run.
96
+ class MessageAwaitResume < AwaitResume
97
+ # @!attribute [r] message
98
+ # @return [Message, nil] the client's response message
99
+ attribute :message
100
+
101
+ def initialize(**kwargs)
102
+ kwargs[:type] = "message"
103
+ super
104
+ end
105
+
106
+ def self.from_hash(hash)
107
+ return nil if hash.nil?
108
+
109
+ instance = allocate
110
+ instance.instance_variable_set(:@type, "message")
111
+
112
+ if hash["message"] || hash[:message]
113
+ instance.instance_variable_set(
114
+ :@message,
115
+ Message.from_hash(hash["message"] || hash[:message])
116
+ )
117
+ end
118
+
119
+ instance
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleAcp
4
+ module Models
5
+ # Base class for all ACP models providing common serialization.
6
+ #
7
+ # Provides a simple DSL for declaring attributes with defaults,
8
+ # automatic JSON serialization/deserialization, and equality comparisons.
9
+ #
10
+ # @example Defining a model
11
+ # class MyModel < Base
12
+ # attribute :name, required: true
13
+ # attribute :count, default: 0
14
+ # attribute :tags, default: -> { [] }
15
+ # end
16
+ #
17
+ # @abstract Subclass and use {.attribute} to define attributes
18
+ class Base
19
+ class << self
20
+ # Define an attribute on this model.
21
+ #
22
+ # @param name [Symbol] the attribute name
23
+ # @param type [Class, nil] optional type hint (for documentation)
24
+ # @param default [Object, Proc, nil] default value or proc
25
+ # @param required [Boolean] whether the attribute is required
26
+ # @return [void]
27
+ def attribute(name, type: nil, default: nil, required: false)
28
+ @attributes ||= {}
29
+ @attributes[name] = { type: type, default: default, required: required }
30
+
31
+ attr_reader name
32
+
33
+ define_method(:"#{name}=") do |value|
34
+ instance_variable_set(:"@#{name}", value)
35
+ end
36
+ end
37
+
38
+ # Get all attributes including inherited ones.
39
+ #
40
+ # @return [Hash<Symbol, Hash>] attribute definitions
41
+ def attributes
42
+ @attributes ||= {}
43
+
44
+ # Inherit attributes from parent class
45
+ if superclass.respond_to?(:attributes)
46
+ superclass.attributes.merge(@attributes)
47
+ else
48
+ @attributes
49
+ end
50
+ end
51
+
52
+ # Get only attributes defined on this class (not inherited).
53
+ #
54
+ # @return [Hash<Symbol, Hash>] attribute definitions
55
+ def own_attributes
56
+ @attributes ||= {}
57
+ end
58
+
59
+ # Create an instance from a hash.
60
+ #
61
+ # @param hash [Hash, nil] attribute values (string or symbol keys)
62
+ # @return [Base, nil] the new instance or nil if hash is nil
63
+ def from_hash(hash)
64
+ return nil if hash.nil?
65
+
66
+ instance = new
67
+ attributes.each do |name, _opts|
68
+ key = name.to_s
69
+ value = hash.key?(key) ? hash[key] : hash[name]
70
+ instance.send(:"#{name}=", value) unless value.nil?
71
+ end
72
+ instance
73
+ end
74
+
75
+ alias from_h from_hash
76
+ end
77
+
78
+ # Initialize with keyword arguments.
79
+ #
80
+ # @param kwargs [Hash] attribute values
81
+ def initialize(**kwargs)
82
+ self.class.attributes.each do |name, opts|
83
+ value = kwargs.fetch(name) { opts[:default].is_a?(Proc) ? opts[:default].call : opts[:default] }
84
+ instance_variable_set(:"@#{name}", value)
85
+ end
86
+ end
87
+
88
+ # Convert to a hash for JSON serialization.
89
+ #
90
+ # @return [Hash] attribute values (nil values excluded)
91
+ def to_h
92
+ self.class.attributes.each_with_object({}) do |(name, _opts), hash|
93
+ value = send(name)
94
+ next if value.nil?
95
+
96
+ hash[name] = serialize_value(value)
97
+ end
98
+ end
99
+
100
+ # Convert to JSON string.
101
+ #
102
+ # @param args [Array] arguments passed to Hash#to_json
103
+ # @return [String] JSON representation
104
+ def to_json(*args)
105
+ to_h.to_json(*args)
106
+ end
107
+
108
+ # Check equality based on all attributes.
109
+ #
110
+ # @param other [Object] object to compare
111
+ # @return [Boolean] true if same class and all attributes equal
112
+ def ==(other)
113
+ return false unless other.is_a?(self.class)
114
+
115
+ self.class.attributes.keys.all? do |name|
116
+ send(name) == other.send(name)
117
+ end
118
+ end
119
+
120
+ alias eql? ==
121
+
122
+ # Compute hash based on all attributes.
123
+ #
124
+ # @return [Integer] hash code
125
+ def hash
126
+ self.class.attributes.keys.map { |name| send(name) }.hash
127
+ end
128
+
129
+ private
130
+
131
+ def serialize_value(value)
132
+ case value
133
+ when Base
134
+ value.to_h
135
+ when Array
136
+ value.map { |v| serialize_value(v) }
137
+ when Hash
138
+ value.transform_values { |v| serialize_value(v) }
139
+ when Time
140
+ value.utc.iso8601
141
+ else
142
+ value
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleAcp
4
+ module Models
5
+ # Error codes as defined in the ACP specification.
6
+ module ErrorCode
7
+ # Internal server error
8
+ SERVER_ERROR = "server_error"
9
+ # Invalid input from client
10
+ INVALID_INPUT = "invalid_input"
11
+ # Requested resource not found
12
+ NOT_FOUND = "not_found"
13
+ end
14
+
15
+ # Structured error response following the ACP specification.
16
+ #
17
+ # @example Creating an error
18
+ # error = Error.server_error("Something went wrong")
19
+ # error.code # => "server_error"
20
+ # error.message # => "Something went wrong"
21
+ class Error
22
+ # @return [String] error code (server_error, invalid_input, not_found)
23
+ attr_reader :code
24
+
25
+ # @return [String] human-readable error message
26
+ attr_reader :message
27
+
28
+ # @return [Object, nil] additional error data
29
+ attr_reader :data
30
+
31
+ # Create a new error.
32
+ #
33
+ # @param code [String] error code
34
+ # @param message [String] error message
35
+ # @param data [Object, nil] additional data
36
+ def initialize(code:, message:, data: nil)
37
+ @code = code
38
+ @message = message
39
+ @data = data
40
+ end
41
+
42
+ # Convert to hash for JSON serialization.
43
+ #
44
+ # @return [Hash] error as hash
45
+ def to_h
46
+ hash = {
47
+ code: @code,
48
+ message: @message
49
+ }
50
+ hash[:data] = @data if @data
51
+ hash
52
+ end
53
+
54
+ # Convert to JSON string.
55
+ #
56
+ # @param args [Array] arguments passed to Hash#to_json
57
+ # @return [String] JSON representation
58
+ def to_json(*args)
59
+ to_h.to_json(*args)
60
+ end
61
+
62
+ # Create from a hash (JSON deserialization).
63
+ #
64
+ # @param hash [Hash] error data
65
+ # @return [Error] the error
66
+ def self.from_hash(hash)
67
+ new(
68
+ code: hash["code"] || hash[:code],
69
+ message: hash["message"] || hash[:message],
70
+ data: hash["data"] || hash[:data]
71
+ )
72
+ end
73
+
74
+ # Create a server error.
75
+ #
76
+ # @param message [String] error message
77
+ # @param data [Object, nil] additional data
78
+ # @return [Error] the error
79
+ def self.server_error(message, data: nil)
80
+ new(code: ErrorCode::SERVER_ERROR, message: message, data: data)
81
+ end
82
+
83
+ # Create an invalid input error.
84
+ #
85
+ # @param message [String] error message
86
+ # @param data [Object, nil] additional data
87
+ # @return [Error] the error
88
+ def self.invalid_input(message, data: nil)
89
+ new(code: ErrorCode::INVALID_INPUT, message: message, data: data)
90
+ end
91
+
92
+ # Create a not found error.
93
+ #
94
+ # @param message [String] error message
95
+ # @param data [Object, nil] additional data
96
+ # @return [Error] the error
97
+ def self.not_found(message, data: nil)
98
+ new(code: ErrorCode::NOT_FOUND, message: message, data: data)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleAcp
4
+ module Models
5
+ # Base class for all Server-Sent Events (SSE).
6
+ #
7
+ # Events are streamed during run execution to provide
8
+ # real-time updates on progress and output.
9
+ #
10
+ # @abstract Subclass and set type in initializer
11
+ class Event < Base
12
+ # @!attribute [r] type
13
+ # @return [String] event type (see Types::EventType)
14
+ attribute :type, required: true
15
+
16
+ # Format for Server-Sent Events protocol.
17
+ #
18
+ # @return [String] SSE-formatted event
19
+ def sse_format
20
+ "event: #{@type}\ndata: #{to_json}\n\n"
21
+ end
22
+ end
23
+
24
+ # Message created event
25
+ class MessageCreatedEvent < Event
26
+ attribute :message
27
+
28
+ def initialize(**kwargs)
29
+ kwargs[:type] = Types::EventType::MESSAGE_CREATED
30
+ super
31
+ end
32
+
33
+ def self.from_hash(hash)
34
+ return nil if hash.nil?
35
+
36
+ instance = super
37
+ if hash["message"] || hash[:message]
38
+ instance.message = Message.from_hash(hash["message"] || hash[:message])
39
+ end
40
+ instance
41
+ end
42
+ end
43
+
44
+ # Message part event (for streaming individual parts)
45
+ class MessagePartEvent < Event
46
+ attribute :part
47
+
48
+ def initialize(**kwargs)
49
+ kwargs[:type] = Types::EventType::MESSAGE_PART
50
+ super
51
+ end
52
+
53
+ def self.from_hash(hash)
54
+ return nil if hash.nil?
55
+
56
+ instance = super
57
+ if hash["part"] || hash[:part]
58
+ instance.part = MessagePart.from_hash(hash["part"] || hash[:part])
59
+ end
60
+ instance
61
+ end
62
+ end
63
+
64
+ # Message completed event
65
+ class MessageCompletedEvent < Event
66
+ attribute :message
67
+
68
+ def initialize(**kwargs)
69
+ kwargs[:type] = Types::EventType::MESSAGE_COMPLETED
70
+ super
71
+ end
72
+
73
+ def self.from_hash(hash)
74
+ return nil if hash.nil?
75
+
76
+ instance = super
77
+ if hash["message"] || hash[:message]
78
+ instance.message = Message.from_hash(hash["message"] || hash[:message])
79
+ end
80
+ instance
81
+ end
82
+ end
83
+
84
+ # Run created event
85
+ class RunCreatedEvent < Event
86
+ attribute :run
87
+
88
+ def initialize(**kwargs)
89
+ kwargs[:type] = Types::EventType::RUN_CREATED
90
+ super
91
+ end
92
+
93
+ def self.from_hash(hash)
94
+ return nil if hash.nil?
95
+
96
+ instance = super
97
+ if hash["run"] || hash[:run]
98
+ instance.run = Run.from_hash(hash["run"] || hash[:run])
99
+ end
100
+ instance
101
+ end
102
+ end
103
+
104
+ # Run in-progress event
105
+ class RunInProgressEvent < Event
106
+ attribute :run_id
107
+
108
+ def initialize(**kwargs)
109
+ kwargs[:type] = Types::EventType::RUN_IN_PROGRESS
110
+ super
111
+ end
112
+ end
113
+
114
+ # Run awaiting event
115
+ class RunAwaitingEvent < Event
116
+ attribute :run_id
117
+ attribute :await_request
118
+
119
+ def initialize(**kwargs)
120
+ kwargs[:type] = Types::EventType::RUN_AWAITING
121
+ super
122
+ end
123
+
124
+ def self.from_hash(hash)
125
+ return nil if hash.nil?
126
+
127
+ instance = super
128
+ if hash["await_request"] || hash[:await_request]
129
+ instance.await_request = AwaitRequest.from_hash(hash["await_request"] || hash[:await_request])
130
+ end
131
+ instance
132
+ end
133
+ end
134
+
135
+ # Run completed event
136
+ class RunCompletedEvent < Event
137
+ attribute :run
138
+
139
+ def initialize(**kwargs)
140
+ kwargs[:type] = Types::EventType::RUN_COMPLETED
141
+ super
142
+ end
143
+
144
+ def self.from_hash(hash)
145
+ return nil if hash.nil?
146
+
147
+ instance = super
148
+ if hash["run"] || hash[:run]
149
+ instance.run = Run.from_hash(hash["run"] || hash[:run])
150
+ end
151
+ instance
152
+ end
153
+ end
154
+
155
+ # Run cancelled event
156
+ class RunCancelledEvent < Event
157
+ attribute :run_id
158
+
159
+ def initialize(**kwargs)
160
+ kwargs[:type] = Types::EventType::RUN_CANCELLED
161
+ super
162
+ end
163
+ end
164
+
165
+ # Run failed event
166
+ class RunFailedEvent < Event
167
+ attribute :run_id
168
+ attribute :error
169
+
170
+ def initialize(**kwargs)
171
+ kwargs[:type] = Types::EventType::RUN_FAILED
172
+ super
173
+ end
174
+
175
+ def self.from_hash(hash)
176
+ return nil if hash.nil?
177
+
178
+ instance = super
179
+ if hash["error"] || hash[:error]
180
+ instance.error = Error.from_hash(hash["error"] || hash[:error])
181
+ end
182
+ instance
183
+ end
184
+ end
185
+
186
+ # Generic event
187
+ class GenericEvent < Event
188
+ attribute :data
189
+
190
+ def initialize(**kwargs)
191
+ kwargs[:type] = Types::EventType::GENERIC
192
+ super
193
+ end
194
+ end
195
+
196
+ # Error event
197
+ class ErrorEvent < Event
198
+ attribute :error
199
+
200
+ def initialize(**kwargs)
201
+ kwargs[:type] = Types::EventType::ERROR
202
+ super
203
+ end
204
+
205
+ def self.from_hash(hash)
206
+ return nil if hash.nil?
207
+
208
+ instance = super
209
+ if hash["error"] || hash[:error]
210
+ instance.error = Error.from_hash(hash["error"] || hash[:error])
211
+ end
212
+ instance
213
+ end
214
+ end
215
+
216
+ # Factory module for parsing events from hashes.
217
+ module Events
218
+ # Mapping of event types to classes
219
+ EVENT_CLASSES = {
220
+ Types::EventType::MESSAGE_CREATED => MessageCreatedEvent,
221
+ Types::EventType::MESSAGE_PART => MessagePartEvent,
222
+ Types::EventType::MESSAGE_COMPLETED => MessageCompletedEvent,
223
+ Types::EventType::RUN_CREATED => RunCreatedEvent,
224
+ Types::EventType::RUN_IN_PROGRESS => RunInProgressEvent,
225
+ Types::EventType::RUN_AWAITING => RunAwaitingEvent,
226
+ Types::EventType::RUN_COMPLETED => RunCompletedEvent,
227
+ Types::EventType::RUN_CANCELLED => RunCancelledEvent,
228
+ Types::EventType::RUN_FAILED => RunFailedEvent,
229
+ Types::EventType::GENERIC => GenericEvent,
230
+ Types::EventType::ERROR => ErrorEvent
231
+ }.freeze
232
+
233
+ # Parse an event from a hash.
234
+ #
235
+ # @param hash [Hash, nil] event data with "type" key
236
+ # @return [Event, nil] the appropriate event subclass or nil
237
+ def self.from_hash(hash)
238
+ return nil if hash.nil?
239
+
240
+ type = hash["type"] || hash[:type]
241
+ klass = EVENT_CLASSES[type] || GenericEvent
242
+ klass.from_hash(hash)
243
+ end
244
+
245
+ # Parse JSON SSE data.
246
+ #
247
+ # @param data [String] JSON string
248
+ # @return [Hash, nil] parsed hash or nil if invalid
249
+ def self.parse_sse(data)
250
+ JSON.parse(data)
251
+ rescue JSON::ParserError
252
+ nil
253
+ end
254
+ end
255
+ end
256
+ end