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,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Models
|
|
5
|
+
# Fundamental communication structure in ACP.
|
|
6
|
+
#
|
|
7
|
+
# Messages represent units of communication between users and agents,
|
|
8
|
+
# containing one or more parts (text, JSON, images, etc.).
|
|
9
|
+
#
|
|
10
|
+
# @example Creating a simple text message
|
|
11
|
+
# msg = Message.user("Hello, world!")
|
|
12
|
+
#
|
|
13
|
+
# @example Creating a message with multiple parts
|
|
14
|
+
# msg = Message.agent(
|
|
15
|
+
# MessagePart.text("Here's the data:"),
|
|
16
|
+
# MessagePart.json({ count: 42 })
|
|
17
|
+
# )
|
|
18
|
+
class Message < Base
|
|
19
|
+
# @!attribute [r] role
|
|
20
|
+
# @return [String] "user" or "agent" (or "agent/name")
|
|
21
|
+
attribute :role, required: true
|
|
22
|
+
|
|
23
|
+
# @!attribute [r] parts
|
|
24
|
+
# @return [Array<MessagePart>] content parts
|
|
25
|
+
attribute :parts, default: -> { [] }
|
|
26
|
+
|
|
27
|
+
# @!attribute [r] created_at
|
|
28
|
+
# @return [Time, nil] when the message was created
|
|
29
|
+
attribute :created_at
|
|
30
|
+
|
|
31
|
+
# @!attribute [r] completed_at
|
|
32
|
+
# @return [Time, nil] when the message was completed
|
|
33
|
+
attribute :completed_at
|
|
34
|
+
|
|
35
|
+
def initialize(**kwargs)
|
|
36
|
+
super
|
|
37
|
+
@parts ||= []
|
|
38
|
+
@created_at ||= Time.now
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Create from a hash (JSON deserialization).
|
|
42
|
+
#
|
|
43
|
+
# @param hash [Hash, nil] message data
|
|
44
|
+
# @return [Message, nil] the message or nil
|
|
45
|
+
def self.from_hash(hash)
|
|
46
|
+
return nil if hash.nil?
|
|
47
|
+
|
|
48
|
+
instance = allocate
|
|
49
|
+
instance.send(:initialize_from_hash, hash)
|
|
50
|
+
instance
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Create a user message from content.
|
|
54
|
+
#
|
|
55
|
+
# @param contents [Array<String, MessagePart, Hash>] message content
|
|
56
|
+
# @return [Message] the user message
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# Message.user("Hello!")
|
|
60
|
+
# Message.user(MessagePart.text("Hello"), MessagePart.json({key: "value"}))
|
|
61
|
+
def self.user(*contents)
|
|
62
|
+
parts = contents.map do |content|
|
|
63
|
+
case content
|
|
64
|
+
when MessagePart
|
|
65
|
+
content
|
|
66
|
+
when String
|
|
67
|
+
MessagePart.text(content)
|
|
68
|
+
when Hash
|
|
69
|
+
MessagePart.from_hash(content)
|
|
70
|
+
else
|
|
71
|
+
MessagePart.json(content)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
new(role: Types::Role::USER, parts: parts)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Create an agent message from content.
|
|
79
|
+
#
|
|
80
|
+
# @param contents [Array<String, MessagePart, Hash>] message content
|
|
81
|
+
# @return [Message] the agent message
|
|
82
|
+
#
|
|
83
|
+
# @example
|
|
84
|
+
# Message.agent("Hello, I'm your assistant!")
|
|
85
|
+
def self.agent(*contents)
|
|
86
|
+
parts = contents.map do |content|
|
|
87
|
+
case content
|
|
88
|
+
when MessagePart
|
|
89
|
+
content
|
|
90
|
+
when String
|
|
91
|
+
MessagePart.text(content)
|
|
92
|
+
when Hash
|
|
93
|
+
MessagePart.from_hash(content)
|
|
94
|
+
else
|
|
95
|
+
MessagePart.json(content)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
new(role: Types::Role::AGENT, parts: parts)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Check if this is a user message.
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] true if role is "user"
|
|
105
|
+
def user?
|
|
106
|
+
@role == Types::Role::USER
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Check if this is an agent message.
|
|
110
|
+
#
|
|
111
|
+
# @return [Boolean] true if role is "agent" or starts with "agent/"
|
|
112
|
+
def agent?
|
|
113
|
+
@role == Types::Role::AGENT || @role.to_s.start_with?("agent/")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get the agent name if this is a named agent message.
|
|
117
|
+
#
|
|
118
|
+
# @return [String, nil] agent name extracted from "agent/name" role
|
|
119
|
+
def agent_name
|
|
120
|
+
return nil unless agent?
|
|
121
|
+
|
|
122
|
+
if @role.to_s.start_with?("agent/")
|
|
123
|
+
@role.to_s.sub("agent/", "")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Add a part to this message.
|
|
128
|
+
#
|
|
129
|
+
# @param part [MessagePart, Hash] the part to add
|
|
130
|
+
# @return [self] for chaining
|
|
131
|
+
def add_part(part)
|
|
132
|
+
@parts << (part.is_a?(MessagePart) ? part : MessagePart.from_hash(part))
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Mark the message as completed.
|
|
137
|
+
#
|
|
138
|
+
# @return [self] for chaining
|
|
139
|
+
def complete!
|
|
140
|
+
@completed_at = Time.now
|
|
141
|
+
self
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Check if the message is completed.
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean] true if completed_at is set
|
|
147
|
+
def completed?
|
|
148
|
+
!@completed_at.nil?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Combine two messages by appending parts.
|
|
152
|
+
#
|
|
153
|
+
# @param other [Message] message to append
|
|
154
|
+
# @return [Message] new combined message
|
|
155
|
+
def +(other)
|
|
156
|
+
combined = self.class.new(role: @role, parts: @parts.dup)
|
|
157
|
+
other.parts.each { |p| combined.add_part(p) }
|
|
158
|
+
combined
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Get combined text content from all text parts.
|
|
162
|
+
#
|
|
163
|
+
# @return [String] concatenated text content
|
|
164
|
+
def text_content
|
|
165
|
+
@parts.select(&:text?).map(&:content).join("\n")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Create a new message with adjacent text parts combined.
|
|
169
|
+
#
|
|
170
|
+
# @return [Message] compressed message
|
|
171
|
+
def compress
|
|
172
|
+
return self if @parts.length <= 1
|
|
173
|
+
|
|
174
|
+
compressed_parts = []
|
|
175
|
+
current_text = nil
|
|
176
|
+
|
|
177
|
+
@parts.each do |part|
|
|
178
|
+
if part.text? && !part.base64_encoded?
|
|
179
|
+
if current_text
|
|
180
|
+
current_text = MessagePart.text("#{current_text.content}\n#{part.content}")
|
|
181
|
+
else
|
|
182
|
+
current_text = part.dup
|
|
183
|
+
end
|
|
184
|
+
else
|
|
185
|
+
compressed_parts << current_text if current_text
|
|
186
|
+
current_text = nil
|
|
187
|
+
compressed_parts << part
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
compressed_parts << current_text if current_text
|
|
192
|
+
|
|
193
|
+
self.class.new(role: @role, parts: compressed_parts, created_at: @created_at)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Validate the message.
|
|
197
|
+
#
|
|
198
|
+
# @return [Boolean] true if role is valid, has parts, and all parts are valid
|
|
199
|
+
def valid?
|
|
200
|
+
return false unless Types::Role.valid?(@role)
|
|
201
|
+
return false if @parts.empty?
|
|
202
|
+
return false unless @parts.all?(&:valid?)
|
|
203
|
+
|
|
204
|
+
true
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Convert to string representation.
|
|
208
|
+
#
|
|
209
|
+
# @return [String] concatenated string representation of all parts
|
|
210
|
+
def to_s
|
|
211
|
+
@parts.map(&:to_s).join("\n")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private
|
|
215
|
+
|
|
216
|
+
def initialize_from_hash(hash)
|
|
217
|
+
@role = hash["role"] || hash[:role]
|
|
218
|
+
@created_at = parse_time(hash["created_at"] || hash[:created_at])
|
|
219
|
+
@completed_at = parse_time(hash["completed_at"] || hash[:completed_at])
|
|
220
|
+
|
|
221
|
+
parts_data = hash["parts"] || hash[:parts] || []
|
|
222
|
+
@parts = parts_data.map { |p| MessagePart.from_hash(p) }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def parse_time(value)
|
|
226
|
+
return nil if value.nil?
|
|
227
|
+
return value if value.is_a?(Time)
|
|
228
|
+
|
|
229
|
+
Time.parse(value.to_s)
|
|
230
|
+
rescue ArgumentError
|
|
231
|
+
nil
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module SimpleAcp
|
|
6
|
+
module Models
|
|
7
|
+
# Individual content unit within a Message.
|
|
8
|
+
#
|
|
9
|
+
# Message parts can contain text, JSON, images, or URL references.
|
|
10
|
+
# Each part has a content type (MIME type) and either inline content
|
|
11
|
+
# or a URL reference.
|
|
12
|
+
#
|
|
13
|
+
# @example Text part
|
|
14
|
+
# part = MessagePart.text("Hello, world!")
|
|
15
|
+
#
|
|
16
|
+
# @example JSON part
|
|
17
|
+
# part = MessagePart.json({ key: "value" })
|
|
18
|
+
#
|
|
19
|
+
# @example Image from base64
|
|
20
|
+
# part = MessagePart.image(base64_data, mime_type: "image/png")
|
|
21
|
+
#
|
|
22
|
+
# @example URL reference
|
|
23
|
+
# part = MessagePart.from_url("https://example.com/image.png", content_type: "image/png")
|
|
24
|
+
class MessagePart < Base
|
|
25
|
+
# @!attribute [r] name
|
|
26
|
+
# @return [String, nil] optional name for the part
|
|
27
|
+
attribute :name
|
|
28
|
+
|
|
29
|
+
# @!attribute [r] content_type
|
|
30
|
+
# @return [String] MIME type (e.g., "text/plain", "application/json")
|
|
31
|
+
attribute :content_type, required: true
|
|
32
|
+
|
|
33
|
+
# @!attribute [r] content
|
|
34
|
+
# @return [String, nil] inline content (mutually exclusive with content_url)
|
|
35
|
+
attribute :content
|
|
36
|
+
|
|
37
|
+
# @!attribute [r] content_encoding
|
|
38
|
+
# @return [String] "plain" or "base64"
|
|
39
|
+
attribute :content_encoding, default: Types::ContentEncoding::PLAIN
|
|
40
|
+
|
|
41
|
+
# @!attribute [r] content_url
|
|
42
|
+
# @return [String, nil] URL reference (mutually exclusive with content)
|
|
43
|
+
attribute :content_url
|
|
44
|
+
|
|
45
|
+
# @!attribute [r] metadata
|
|
46
|
+
# @return [CitationMetadata, TrajectoryMetadata, nil] optional metadata
|
|
47
|
+
attribute :metadata
|
|
48
|
+
|
|
49
|
+
def initialize(**kwargs)
|
|
50
|
+
super
|
|
51
|
+
validate!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Create from a hash (JSON deserialization).
|
|
55
|
+
#
|
|
56
|
+
# @param hash [Hash, nil] part data
|
|
57
|
+
# @return [MessagePart, nil] the part or nil
|
|
58
|
+
def self.from_hash(hash)
|
|
59
|
+
return nil if hash.nil?
|
|
60
|
+
|
|
61
|
+
instance = allocate
|
|
62
|
+
instance.send(:initialize_from_hash, hash)
|
|
63
|
+
instance
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Create a plain text message part.
|
|
67
|
+
#
|
|
68
|
+
# @param content [String] the text content
|
|
69
|
+
# @param name [String, nil] optional name
|
|
70
|
+
# @return [MessagePart] the text part
|
|
71
|
+
def self.text(content, name: nil)
|
|
72
|
+
new(
|
|
73
|
+
content_type: "text/plain",
|
|
74
|
+
content: content,
|
|
75
|
+
name: name
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Create a JSON message part.
|
|
80
|
+
#
|
|
81
|
+
# @param data [Hash, Array, String] JSON data (will be serialized if not string)
|
|
82
|
+
# @param name [String, nil] optional name
|
|
83
|
+
# @return [MessagePart] the JSON part
|
|
84
|
+
def self.json(data, name: nil)
|
|
85
|
+
new(
|
|
86
|
+
content_type: "application/json",
|
|
87
|
+
content: data.is_a?(String) ? data : data.to_json,
|
|
88
|
+
name: name
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Create an image message part from base64 data.
|
|
93
|
+
#
|
|
94
|
+
# @param data [String] base64-encoded image data
|
|
95
|
+
# @param mime_type [String] image MIME type (default: "image/png")
|
|
96
|
+
# @param name [String, nil] optional name
|
|
97
|
+
# @return [MessagePart] the image part
|
|
98
|
+
def self.image(data, mime_type: "image/png", name: nil)
|
|
99
|
+
new(
|
|
100
|
+
content_type: mime_type,
|
|
101
|
+
content: data,
|
|
102
|
+
content_encoding: Types::ContentEncoding::BASE64,
|
|
103
|
+
name: name
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Create a message part referencing a URL.
|
|
108
|
+
#
|
|
109
|
+
# @param url [String] the URL to reference
|
|
110
|
+
# @param content_type [String] the content type at the URL
|
|
111
|
+
# @param name [String, nil] optional name
|
|
112
|
+
# @return [MessagePart] the URL reference part
|
|
113
|
+
def self.from_url(url, content_type:, name: nil)
|
|
114
|
+
new(
|
|
115
|
+
content_type: content_type,
|
|
116
|
+
content_url: url,
|
|
117
|
+
name: name
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Check if this is a text content part.
|
|
122
|
+
#
|
|
123
|
+
# @return [Boolean] true if content_type starts with "text/"
|
|
124
|
+
def text?
|
|
125
|
+
@content_type&.start_with?("text/")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check if this is a JSON content part.
|
|
129
|
+
#
|
|
130
|
+
# @return [Boolean] true if content_type is "application/json"
|
|
131
|
+
def json?
|
|
132
|
+
@content_type == "application/json"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if this is an image content part.
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] true if content_type starts with "image/"
|
|
138
|
+
def image?
|
|
139
|
+
@content_type&.start_with?("image/")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Check if content is base64 encoded.
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean] true if content_encoding is "base64"
|
|
145
|
+
def base64_encoded?
|
|
146
|
+
@content_encoding == Types::ContentEncoding::BASE64
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Check if this part references a URL.
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean] true if content_url is set
|
|
152
|
+
def url_reference?
|
|
153
|
+
!@content_url.nil?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get decoded content (decodes base64 if needed).
|
|
157
|
+
#
|
|
158
|
+
# @return [String] decoded content
|
|
159
|
+
def decoded_content
|
|
160
|
+
return @content unless base64_encoded?
|
|
161
|
+
|
|
162
|
+
Base64.decode64(@content)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Parse JSON content into Ruby objects.
|
|
166
|
+
#
|
|
167
|
+
# @return [Hash, Array, nil] parsed JSON or nil if not JSON
|
|
168
|
+
def parsed_json
|
|
169
|
+
return nil unless json?
|
|
170
|
+
|
|
171
|
+
JSON.parse(@content)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Validate the message part.
|
|
175
|
+
#
|
|
176
|
+
# @return [Boolean] true if content_type is set and has content or URL (not both)
|
|
177
|
+
def valid?
|
|
178
|
+
return false if @content_type.nil?
|
|
179
|
+
return false if @content.nil? && @content_url.nil?
|
|
180
|
+
return false if @content && @content_url
|
|
181
|
+
|
|
182
|
+
true
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Convert to string representation.
|
|
186
|
+
#
|
|
187
|
+
# @return [String] content for text, URL for references, or "<type>" placeholder
|
|
188
|
+
def to_s
|
|
189
|
+
return @content if text?
|
|
190
|
+
return @content_url if url_reference?
|
|
191
|
+
|
|
192
|
+
"<#{@content_type}>"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def initialize_from_hash(hash)
|
|
198
|
+
@name = hash["name"] || hash[:name]
|
|
199
|
+
@content_type = hash["content_type"] || hash[:content_type]
|
|
200
|
+
@content = hash["content"] || hash[:content]
|
|
201
|
+
@content_encoding = hash["content_encoding"] || hash[:content_encoding] || Types::ContentEncoding::PLAIN
|
|
202
|
+
@content_url = hash["content_url"] || hash[:content_url]
|
|
203
|
+
|
|
204
|
+
metadata_hash = hash["metadata"] || hash[:metadata]
|
|
205
|
+
if metadata_hash
|
|
206
|
+
kind = metadata_hash["kind"] || metadata_hash[:kind]
|
|
207
|
+
@metadata = case kind
|
|
208
|
+
when "citation"
|
|
209
|
+
CitationMetadata.from_hash(metadata_hash)
|
|
210
|
+
when "trajectory"
|
|
211
|
+
TrajectoryMetadata.from_hash(metadata_hash)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
validate!
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def validate!
|
|
219
|
+
if @content && @content_url
|
|
220
|
+
raise SimpleAcp::ValidationError, "MessagePart cannot have both content and content_url"
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAcp
|
|
4
|
+
module Models
|
|
5
|
+
# Citation metadata for source attribution
|
|
6
|
+
class CitationMetadata < Base
|
|
7
|
+
attribute :kind, default: "citation"
|
|
8
|
+
attribute :start_index
|
|
9
|
+
attribute :end_index
|
|
10
|
+
attribute :url
|
|
11
|
+
attribute :title
|
|
12
|
+
attribute :description
|
|
13
|
+
|
|
14
|
+
def self.from_hash(hash)
|
|
15
|
+
return nil if hash.nil?
|
|
16
|
+
return nil unless hash["kind"] == "citation" || hash[:kind] == "citation"
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Trajectory metadata for tracking multi-step reasoning
|
|
23
|
+
class TrajectoryMetadata < Base
|
|
24
|
+
attribute :kind, default: "trajectory"
|
|
25
|
+
attribute :message
|
|
26
|
+
attribute :tool_name
|
|
27
|
+
attribute :tool_input
|
|
28
|
+
attribute :tool_output
|
|
29
|
+
|
|
30
|
+
def self.from_hash(hash)
|
|
31
|
+
return nil if hash.nil?
|
|
32
|
+
return nil unless hash["kind"] == "trajectory" || hash[:kind] == "trajectory"
|
|
33
|
+
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Author information
|
|
39
|
+
class Author < Base
|
|
40
|
+
attribute :name, required: true
|
|
41
|
+
attribute :email
|
|
42
|
+
attribute :url
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Contributor information (same structure as Author)
|
|
46
|
+
class Contributor < Base
|
|
47
|
+
attribute :name, required: true
|
|
48
|
+
attribute :email
|
|
49
|
+
attribute :url
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Link to external resources
|
|
53
|
+
class Link < Base
|
|
54
|
+
attribute :type, required: true
|
|
55
|
+
attribute :url, required: true
|
|
56
|
+
|
|
57
|
+
def valid?
|
|
58
|
+
Types::LinkType::ALL.include?(@type) && Types.valid_url?(@url)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Dependency declaration
|
|
63
|
+
class Dependency < Base
|
|
64
|
+
attribute :type, required: true
|
|
65
|
+
attribute :name, required: true
|
|
66
|
+
|
|
67
|
+
def valid?
|
|
68
|
+
Types::DependencyType::ALL.include?(@type)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Agent capability
|
|
73
|
+
class Capability < Base
|
|
74
|
+
attribute :name, required: true
|
|
75
|
+
attribute :description, required: true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Annotations for platform-specific metadata
|
|
79
|
+
class Annotations < Base
|
|
80
|
+
attribute :beeai_ui
|
|
81
|
+
attribute :extra, default: -> { {} }
|
|
82
|
+
|
|
83
|
+
def [](key)
|
|
84
|
+
@extra[key]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def []=(key, value)
|
|
88
|
+
@extra[key] = value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_h
|
|
92
|
+
hash = super
|
|
93
|
+
@extra&.each { |k, v| hash[k] = v }
|
|
94
|
+
hash
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Agent status metrics
|
|
99
|
+
class AgentStatus < Base
|
|
100
|
+
attribute :average_tokens
|
|
101
|
+
attribute :average_run_time
|
|
102
|
+
attribute :success_rate
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Full agent metadata
|
|
106
|
+
class Metadata < Base
|
|
107
|
+
attribute :annotations
|
|
108
|
+
attribute :documentation
|
|
109
|
+
attribute :license
|
|
110
|
+
attribute :programming_language
|
|
111
|
+
attribute :natural_languages, default: -> { [] }
|
|
112
|
+
attribute :framework
|
|
113
|
+
attribute :capabilities, default: -> { [] }
|
|
114
|
+
attribute :domains, default: -> { [] }
|
|
115
|
+
attribute :tags, default: -> { [] }
|
|
116
|
+
attribute :created_at
|
|
117
|
+
attribute :updated_at
|
|
118
|
+
attribute :author
|
|
119
|
+
attribute :contributors, default: -> { [] }
|
|
120
|
+
attribute :links, default: -> { [] }
|
|
121
|
+
attribute :dependencies, default: -> { [] }
|
|
122
|
+
attribute :recommended_models, default: -> { [] }
|
|
123
|
+
|
|
124
|
+
def self.from_hash(hash)
|
|
125
|
+
return nil if hash.nil?
|
|
126
|
+
|
|
127
|
+
instance = super
|
|
128
|
+
|
|
129
|
+
if hash["annotations"] || hash[:annotations]
|
|
130
|
+
instance.annotations = Annotations.from_hash(hash["annotations"] || hash[:annotations])
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if hash["author"] || hash[:author]
|
|
134
|
+
instance.author = Author.from_hash(hash["author"] || hash[:author])
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if hash["contributors"] || hash[:contributors]
|
|
138
|
+
contributors = hash["contributors"] || hash[:contributors]
|
|
139
|
+
instance.contributors = contributors.map { |c| Contributor.from_hash(c) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if hash["capabilities"] || hash[:capabilities]
|
|
143
|
+
capabilities = hash["capabilities"] || hash[:capabilities]
|
|
144
|
+
instance.capabilities = capabilities.map { |c| Capability.from_hash(c) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if hash["links"] || hash[:links]
|
|
148
|
+
links = hash["links"] || hash[:links]
|
|
149
|
+
instance.links = links.map { |l| Link.from_hash(l) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if hash["dependencies"] || hash[:dependencies]
|
|
153
|
+
dependencies = hash["dependencies"] || hash[:dependencies]
|
|
154
|
+
instance.dependencies = dependencies.map { |d| Dependency.from_hash(d) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
instance
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|