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,115 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Run Management Example - Client
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Cancelling a running task with run_cancel
|
|
8
|
+
# - Retrieving run event history with run_events
|
|
9
|
+
# - Pagination of events
|
|
10
|
+
#
|
|
11
|
+
# First start the server: ruby examples/03_run_management/server.rb
|
|
12
|
+
# Then run this client: ruby examples/03_run_management/client.rb
|
|
13
|
+
|
|
14
|
+
require_relative "../../lib/simple_acp"
|
|
15
|
+
|
|
16
|
+
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
17
|
+
|
|
18
|
+
puts "=== Run Management Demo ==="
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
# Verify server is running
|
|
22
|
+
unless client.ping
|
|
23
|
+
puts "Server is not responding"
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
26
|
+
puts "Server is healthy"
|
|
27
|
+
puts
|
|
28
|
+
|
|
29
|
+
# --- Demo 1: Cancelling a running task ---
|
|
30
|
+
puts "--- Demo 1: Run Cancellation ---"
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
puts "Starting a long-running cancellable task..."
|
|
34
|
+
run = client.run_async(agent: "cancellable-task", input: "data processing job")
|
|
35
|
+
puts "Run ID: #{run.run_id}"
|
|
36
|
+
puts
|
|
37
|
+
|
|
38
|
+
puts "Letting it run for 1.5 seconds..."
|
|
39
|
+
sleep(1.5)
|
|
40
|
+
|
|
41
|
+
status = client.run_status(run.run_id)
|
|
42
|
+
puts "Current status: #{status.status}"
|
|
43
|
+
puts
|
|
44
|
+
|
|
45
|
+
puts "Cancelling the run..."
|
|
46
|
+
cancelled_run = client.run_cancel(run.run_id)
|
|
47
|
+
puts "Cancel requested. Status: #{cancelled_run.status}"
|
|
48
|
+
puts
|
|
49
|
+
|
|
50
|
+
# Wait a moment for cancellation to complete
|
|
51
|
+
sleep(0.5)
|
|
52
|
+
|
|
53
|
+
final_status = client.run_status(run.run_id)
|
|
54
|
+
puts "Final status: #{final_status.status}"
|
|
55
|
+
puts "Output messages:"
|
|
56
|
+
final_status.output.each do |message|
|
|
57
|
+
puts " #{message.text_content}"
|
|
58
|
+
end
|
|
59
|
+
puts
|
|
60
|
+
|
|
61
|
+
# --- Demo 2: Let a task complete normally for comparison ---
|
|
62
|
+
puts "--- Demo 2: Normal Completion (for comparison) ---"
|
|
63
|
+
puts
|
|
64
|
+
|
|
65
|
+
puts "Starting a shorter task (will complete normally)..."
|
|
66
|
+
run = client.run_sync(agent: "cancellable-task", input: "quick task")
|
|
67
|
+
puts "Status: #{run.status}"
|
|
68
|
+
puts "Final message: #{run.output.last&.text_content}"
|
|
69
|
+
puts
|
|
70
|
+
|
|
71
|
+
# --- Demo 3: Event History ---
|
|
72
|
+
puts "--- Demo 3: Event History with run_events ---"
|
|
73
|
+
puts
|
|
74
|
+
|
|
75
|
+
puts "Generating events..."
|
|
76
|
+
run = client.run_sync(agent: "event-generator", input: "8")
|
|
77
|
+
puts "Run completed with #{run.output.length} output messages"
|
|
78
|
+
puts
|
|
79
|
+
|
|
80
|
+
puts "Retrieving all events for this run..."
|
|
81
|
+
events = client.run_events(run.run_id)
|
|
82
|
+
puts "Total events retrieved: #{events.length}"
|
|
83
|
+
puts
|
|
84
|
+
puts "Event types:"
|
|
85
|
+
event_counts = events.group_by { |e| e.class.name.split("::").last }
|
|
86
|
+
event_counts.each do |type, evts|
|
|
87
|
+
puts " #{type}: #{evts.length}"
|
|
88
|
+
end
|
|
89
|
+
puts
|
|
90
|
+
|
|
91
|
+
puts "First 5 events:"
|
|
92
|
+
events.first(5).each_with_index do |event, i|
|
|
93
|
+
event_type = event.class.name.split("::").last
|
|
94
|
+
puts " #{i + 1}. #{event_type}"
|
|
95
|
+
end
|
|
96
|
+
puts
|
|
97
|
+
|
|
98
|
+
# --- Demo 4: Event Pagination ---
|
|
99
|
+
puts "--- Demo 4: Event Pagination ---"
|
|
100
|
+
puts
|
|
101
|
+
|
|
102
|
+
puts "Retrieving events with pagination (limit: 5, offset: 0)..."
|
|
103
|
+
page1 = client.run_events(run.run_id, limit: 5, offset: 0)
|
|
104
|
+
puts "Page 1: #{page1.length} events"
|
|
105
|
+
|
|
106
|
+
puts "Retrieving events with pagination (limit: 5, offset: 5)..."
|
|
107
|
+
page2 = client.run_events(run.run_id, limit: 5, offset: 5)
|
|
108
|
+
puts "Page 2: #{page2.length} events"
|
|
109
|
+
|
|
110
|
+
puts "Retrieving events with pagination (limit: 5, offset: 10)..."
|
|
111
|
+
page3 = client.run_events(run.run_id, limit: 5, offset: 10)
|
|
112
|
+
puts "Page 3: #{page3.length} events"
|
|
113
|
+
puts
|
|
114
|
+
|
|
115
|
+
puts "=== Run Management Demo Complete ==="
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Run Management Example - Server
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates run cancellation and event tracking.
|
|
7
|
+
#
|
|
8
|
+
# Run with: ruby examples/03_run_management/server.rb
|
|
9
|
+
# Then connect with: ruby examples/03_run_management/client.rb
|
|
10
|
+
|
|
11
|
+
require_relative "../../lib/simple_acp"
|
|
12
|
+
|
|
13
|
+
server = SimpleAcp::Server::Base.new
|
|
14
|
+
|
|
15
|
+
# Long-running cancellable agent
|
|
16
|
+
server.agent("cancellable-task",
|
|
17
|
+
description: "A long task that can be cancelled (10 iterations)"
|
|
18
|
+
) do |context|
|
|
19
|
+
task_name = context.input.first&.text_content || "unnamed task"
|
|
20
|
+
|
|
21
|
+
Enumerator.new do |yielder|
|
|
22
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
23
|
+
SimpleAcp::Models::Message.agent("Starting: #{task_name}")
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
10.times do |i|
|
|
27
|
+
# Check for cancellation before each iteration
|
|
28
|
+
if context.cancelled?
|
|
29
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
30
|
+
SimpleAcp::Models::Message.agent("Task cancelled at iteration #{i + 1}")
|
|
31
|
+
)
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Simulate work
|
|
36
|
+
if defined?(Async::Task) && Async::Task.current?
|
|
37
|
+
Async::Task.current.sleep(0.5)
|
|
38
|
+
else
|
|
39
|
+
sleep(0.5)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
43
|
+
SimpleAcp::Models::Message.agent("Iteration #{i + 1}/10 complete")
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
unless context.cancelled?
|
|
48
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
49
|
+
SimpleAcp::Models::Message.agent("Task '#{task_name}' finished successfully!")
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Agent that generates many events for event history demo
|
|
56
|
+
server.agent("event-generator",
|
|
57
|
+
description: "Generates multiple events for history tracking"
|
|
58
|
+
) do |context|
|
|
59
|
+
count = context.input.first&.text_content&.to_i || 5
|
|
60
|
+
count = [count, 20].min # Cap at 20
|
|
61
|
+
|
|
62
|
+
Enumerator.new do |yielder|
|
|
63
|
+
count.times do |i|
|
|
64
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
65
|
+
SimpleAcp::Models::Message.agent("Event #{i + 1}: Generated at #{Time.now.strftime('%H:%M:%S.%L')}")
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if defined?(Async::Task) && Async::Task.current?
|
|
69
|
+
Async::Task.current.sleep(0.1)
|
|
70
|
+
else
|
|
71
|
+
sleep(0.1)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
puts "Starting Run Management Demo Server..."
|
|
78
|
+
puts "Available agents:"
|
|
79
|
+
server.agents.each do |name, agent|
|
|
80
|
+
puts " - #{name}: #{agent.description}"
|
|
81
|
+
end
|
|
82
|
+
puts
|
|
83
|
+
|
|
84
|
+
server.run(port: 8000)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Rich Messages Example - Client
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Receiving JSON content in message parts
|
|
8
|
+
# - Receiving base64 encoded data (images)
|
|
9
|
+
# - Receiving URL references
|
|
10
|
+
# - Multi-part messages with different content types
|
|
11
|
+
#
|
|
12
|
+
# First start the server: ruby examples/04_rich_messages/server.rb
|
|
13
|
+
# Then run this client: ruby examples/04_rich_messages/client.rb
|
|
14
|
+
|
|
15
|
+
require_relative "../../lib/simple_acp"
|
|
16
|
+
require "json"
|
|
17
|
+
require "base64"
|
|
18
|
+
|
|
19
|
+
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
20
|
+
|
|
21
|
+
puts "=== Rich Messages Demo ==="
|
|
22
|
+
puts
|
|
23
|
+
|
|
24
|
+
# Verify server is running
|
|
25
|
+
unless client.ping
|
|
26
|
+
puts "Server is not responding"
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
puts "Server is healthy"
|
|
30
|
+
puts
|
|
31
|
+
|
|
32
|
+
# --- Demo 1: JSON Data ---
|
|
33
|
+
puts "--- Demo 1: JSON Content ---"
|
|
34
|
+
puts
|
|
35
|
+
|
|
36
|
+
puts "Querying for 'users' data..."
|
|
37
|
+
run = client.run_sync(agent: "json-data", input: "users")
|
|
38
|
+
|
|
39
|
+
run.output.each do |message|
|
|
40
|
+
message.parts.each do |part|
|
|
41
|
+
case part.content_type
|
|
42
|
+
when "application/json"
|
|
43
|
+
puts "JSON Part:"
|
|
44
|
+
data = JSON.parse(part.content)
|
|
45
|
+
puts " Query: #{data['query']}"
|
|
46
|
+
puts " Count: #{data['count']}"
|
|
47
|
+
puts " Results:"
|
|
48
|
+
data["results"]&.each do |user|
|
|
49
|
+
puts " - #{user['name']} (#{user['role']})"
|
|
50
|
+
end
|
|
51
|
+
when "text/plain"
|
|
52
|
+
puts "Text Part: #{part.content}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
puts
|
|
57
|
+
|
|
58
|
+
puts "Querying for 'stats' data..."
|
|
59
|
+
run = client.run_sync(agent: "json-data", input: "stats")
|
|
60
|
+
|
|
61
|
+
run.output.each do |message|
|
|
62
|
+
message.parts.each do |part|
|
|
63
|
+
if part.content_type == "application/json"
|
|
64
|
+
data = JSON.parse(part.content)
|
|
65
|
+
puts "Stats (#{data['timestamp']}):"
|
|
66
|
+
data["metrics"]&.each do |key, value|
|
|
67
|
+
puts " #{key}: #{value}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
puts
|
|
73
|
+
|
|
74
|
+
# --- Demo 2: Image/Binary Data ---
|
|
75
|
+
puts "--- Demo 2: Base64 Encoded Image ---"
|
|
76
|
+
puts
|
|
77
|
+
|
|
78
|
+
%w[red green blue].each do |color|
|
|
79
|
+
puts "Generating #{color} circle..."
|
|
80
|
+
run = client.run_sync(agent: "image-generator", input: color)
|
|
81
|
+
|
|
82
|
+
run.output.each do |message|
|
|
83
|
+
message.parts.each do |part|
|
|
84
|
+
case part.content_type
|
|
85
|
+
when "image/svg+xml"
|
|
86
|
+
# Decode and show first line of SVG
|
|
87
|
+
svg = Base64.decode64(part.content)
|
|
88
|
+
puts " SVG data received (#{part.content.length} bytes base64)"
|
|
89
|
+
puts " First line: #{svg.lines.first.strip}"
|
|
90
|
+
when "text/plain"
|
|
91
|
+
puts " #{part.content}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
puts
|
|
97
|
+
|
|
98
|
+
# --- Demo 3: URL References ---
|
|
99
|
+
puts "--- Demo 3: URL References ---"
|
|
100
|
+
puts
|
|
101
|
+
|
|
102
|
+
%w[ruby python javascript].each do |topic|
|
|
103
|
+
puts "Getting links for '#{topic}'..."
|
|
104
|
+
run = client.run_sync(agent: "link-provider", input: topic)
|
|
105
|
+
|
|
106
|
+
run.output.each do |message|
|
|
107
|
+
message.parts.each do |part|
|
|
108
|
+
if part.metadata && part.metadata["url"]
|
|
109
|
+
puts " #{part.metadata['title']}"
|
|
110
|
+
puts " URL: #{part.metadata['url']}"
|
|
111
|
+
elsif part.content&.include?("Found")
|
|
112
|
+
puts " #{part.content}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
puts
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# --- Demo 4: Multi-Format Response ---
|
|
120
|
+
puts "--- Demo 4: Multi-Format Response ---"
|
|
121
|
+
puts
|
|
122
|
+
|
|
123
|
+
puts "Requesting greeting in multiple formats..."
|
|
124
|
+
run = client.run_sync(agent: "multi-format", input: "Developer")
|
|
125
|
+
|
|
126
|
+
run.output.each do |message|
|
|
127
|
+
puts "Message has #{message.parts.length} parts:"
|
|
128
|
+
message.parts.each_with_index do |part, i|
|
|
129
|
+
puts
|
|
130
|
+
puts " Part #{i + 1} (#{part.content_type}):"
|
|
131
|
+
case part.content_type
|
|
132
|
+
when "application/json"
|
|
133
|
+
data = JSON.parse(part.content)
|
|
134
|
+
puts " #{data.inspect}"
|
|
135
|
+
when "text/html"
|
|
136
|
+
# Show HTML without rendering
|
|
137
|
+
puts " #{part.content.gsub("\n", " ").strip}"
|
|
138
|
+
else
|
|
139
|
+
puts " #{part.content}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
puts
|
|
144
|
+
|
|
145
|
+
# --- Demo 5: Content Type Inspection ---
|
|
146
|
+
puts "--- Demo 5: Agent Content Type Capabilities ---"
|
|
147
|
+
puts
|
|
148
|
+
|
|
149
|
+
puts "Listing agents and their content types:"
|
|
150
|
+
agents_response = client.agents
|
|
151
|
+
agents_response.agents.each do |agent|
|
|
152
|
+
puts
|
|
153
|
+
puts "#{agent.name}:"
|
|
154
|
+
puts " Description: #{agent.description}"
|
|
155
|
+
puts " Input types: #{agent.input_content_types&.join(', ') || 'any'}"
|
|
156
|
+
puts " Output types: #{agent.output_content_types&.join(', ') || 'any'}"
|
|
157
|
+
end
|
|
158
|
+
puts
|
|
159
|
+
|
|
160
|
+
puts "=== Rich Messages Demo Complete ==="
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Rich Messages Example - Server
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates different message part types:
|
|
7
|
+
# - Text content
|
|
8
|
+
# - JSON content
|
|
9
|
+
# - Base64 encoded data
|
|
10
|
+
# - URL references
|
|
11
|
+
#
|
|
12
|
+
# Run with: ruby examples/04_rich_messages/server.rb
|
|
13
|
+
# Then connect with: ruby examples/04_rich_messages/client.rb
|
|
14
|
+
|
|
15
|
+
require_relative "../../lib/simple_acp"
|
|
16
|
+
require "json"
|
|
17
|
+
require "base64"
|
|
18
|
+
|
|
19
|
+
server = SimpleAcp::Server::Base.new
|
|
20
|
+
|
|
21
|
+
# Agent that returns JSON data
|
|
22
|
+
server.agent("json-data",
|
|
23
|
+
description: "Returns structured JSON data",
|
|
24
|
+
output_content_types: ["application/json", "text/plain"]
|
|
25
|
+
) do |context|
|
|
26
|
+
query = context.input.first&.text_content || "users"
|
|
27
|
+
|
|
28
|
+
# Simulate different data queries
|
|
29
|
+
data = case query.downcase
|
|
30
|
+
when "users"
|
|
31
|
+
{
|
|
32
|
+
query: "users",
|
|
33
|
+
count: 3,
|
|
34
|
+
results: [
|
|
35
|
+
{ id: 1, name: "Alice", role: "admin" },
|
|
36
|
+
{ id: 2, name: "Bob", role: "user" },
|
|
37
|
+
{ id: 3, name: "Charlie", role: "user" }
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
when "stats"
|
|
41
|
+
{
|
|
42
|
+
query: "stats",
|
|
43
|
+
timestamp: Time.now.iso8601,
|
|
44
|
+
metrics: {
|
|
45
|
+
requests_today: 1234,
|
|
46
|
+
active_users: 42,
|
|
47
|
+
uptime_percent: 99.9
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
{
|
|
52
|
+
query: query,
|
|
53
|
+
error: "Unknown query type",
|
|
54
|
+
available: %w[users stats]
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Return message with both JSON and text parts
|
|
59
|
+
message = SimpleAcp::Models::Message.new(
|
|
60
|
+
role: "agent",
|
|
61
|
+
parts: [
|
|
62
|
+
SimpleAcp::Models::MessagePart.json(data),
|
|
63
|
+
SimpleAcp::Models::MessagePart.text("Query '#{query}' returned #{data[:results]&.length || 0} results")
|
|
64
|
+
]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
message
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Agent that returns a simple image (small SVG as base64)
|
|
71
|
+
server.agent("image-generator",
|
|
72
|
+
description: "Generates a simple SVG image",
|
|
73
|
+
output_content_types: ["image/svg+xml", "text/plain"]
|
|
74
|
+
) do |context|
|
|
75
|
+
color = context.input.first&.text_content || "blue"
|
|
76
|
+
|
|
77
|
+
# Create a simple SVG
|
|
78
|
+
svg = <<~SVG
|
|
79
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
|
80
|
+
<rect width="100" height="100" fill="transparent"/>
|
|
81
|
+
<circle cx="50" cy="50" r="40" fill="#{color}" opacity="0.8"/>
|
|
82
|
+
<text x="50" y="55" text-anchor="middle" fill="white" font-size="12">#{color}</text>
|
|
83
|
+
</svg>
|
|
84
|
+
SVG
|
|
85
|
+
|
|
86
|
+
encoded = Base64.strict_encode64(svg)
|
|
87
|
+
|
|
88
|
+
message = SimpleAcp::Models::Message.new(
|
|
89
|
+
role: "agent",
|
|
90
|
+
parts: [
|
|
91
|
+
SimpleAcp::Models::MessagePart.new(
|
|
92
|
+
kind: "data",
|
|
93
|
+
content: encoded,
|
|
94
|
+
content_type: "image/svg+xml"
|
|
95
|
+
),
|
|
96
|
+
SimpleAcp::Models::MessagePart.text("Generated #{color} circle SVG (#{encoded.length} bytes base64)")
|
|
97
|
+
]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
message
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Agent that returns URL references
|
|
104
|
+
server.agent("link-provider",
|
|
105
|
+
description: "Provides relevant URLs and references",
|
|
106
|
+
output_content_types: ["text/uri-list", "text/plain"]
|
|
107
|
+
) do |context|
|
|
108
|
+
topic = context.input.first&.text_content || "ruby"
|
|
109
|
+
|
|
110
|
+
links = case topic.downcase
|
|
111
|
+
when "ruby"
|
|
112
|
+
[
|
|
113
|
+
{ url: "https://www.ruby-lang.org/", title: "Ruby Official Site" },
|
|
114
|
+
{ url: "https://rubygems.org/", title: "RubyGems" },
|
|
115
|
+
{ url: "https://guides.rubyonrails.org/", title: "Rails Guides" }
|
|
116
|
+
]
|
|
117
|
+
when "python"
|
|
118
|
+
[
|
|
119
|
+
{ url: "https://www.python.org/", title: "Python Official Site" },
|
|
120
|
+
{ url: "https://pypi.org/", title: "PyPI" },
|
|
121
|
+
{ url: "https://docs.python.org/", title: "Python Docs" }
|
|
122
|
+
]
|
|
123
|
+
else
|
|
124
|
+
[
|
|
125
|
+
{ url: "https://www.google.com/search?q=#{topic}", title: "Search for #{topic}" }
|
|
126
|
+
]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Create message with URL parts
|
|
130
|
+
parts = links.map do |link|
|
|
131
|
+
SimpleAcp::Models::MessagePart.new(
|
|
132
|
+
kind: "text",
|
|
133
|
+
content: "#{link[:title]}: #{link[:url]}",
|
|
134
|
+
content_type: "text/plain",
|
|
135
|
+
metadata: { url: link[:url], title: link[:title] }
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
parts << SimpleAcp::Models::MessagePart.text("Found #{links.length} links for '#{topic}'")
|
|
140
|
+
|
|
141
|
+
SimpleAcp::Models::Message.new(role: "agent", parts: parts)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Agent demonstrating multi-part responses
|
|
145
|
+
server.agent("multi-format",
|
|
146
|
+
description: "Returns data in multiple formats",
|
|
147
|
+
output_content_types: ["application/json", "text/plain", "text/html"]
|
|
148
|
+
) do |context|
|
|
149
|
+
name = context.input.first&.text_content || "World"
|
|
150
|
+
|
|
151
|
+
# Return same greeting in multiple formats
|
|
152
|
+
message = SimpleAcp::Models::Message.new(
|
|
153
|
+
role: "agent",
|
|
154
|
+
parts: [
|
|
155
|
+
SimpleAcp::Models::MessagePart.text("Hello, #{name}!"),
|
|
156
|
+
SimpleAcp::Models::MessagePart.json({ greeting: "Hello", recipient: name, timestamp: Time.now.iso8601 }),
|
|
157
|
+
SimpleAcp::Models::MessagePart.new(
|
|
158
|
+
kind: "text",
|
|
159
|
+
content: "<h1>Hello, #{name}!</h1><p>Welcome to the multi-format demo.</p>",
|
|
160
|
+
content_type: "text/html"
|
|
161
|
+
)
|
|
162
|
+
]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
message
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
puts "Starting Rich Messages Demo Server..."
|
|
169
|
+
puts "Available agents:"
|
|
170
|
+
server.agents.each do |name, agent|
|
|
171
|
+
puts " - #{name}: #{agent.description}"
|
|
172
|
+
puts " Input types: #{agent.manifest.input_content_types.join(', ')}"
|
|
173
|
+
puts " Output types: #{agent.manifest.output_content_types.join(', ')}"
|
|
174
|
+
if agent.manifest.metadata
|
|
175
|
+
puts " Has metadata: yes"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
puts
|
|
179
|
+
|
|
180
|
+
server.run(port: 8000)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Await/Resume Example - Client
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Detecting when a run is awaiting input
|
|
8
|
+
# - Resuming a run with run_resume_sync
|
|
9
|
+
# - Multi-step interactive flows
|
|
10
|
+
# - Streaming resume with run_resume_stream
|
|
11
|
+
#
|
|
12
|
+
# First start the server: ruby examples/05_await_resume/server.rb
|
|
13
|
+
# Then run this client: ruby examples/05_await_resume/client.rb
|
|
14
|
+
|
|
15
|
+
require_relative "../../lib/simple_acp"
|
|
16
|
+
|
|
17
|
+
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
18
|
+
|
|
19
|
+
puts "=== Await/Resume Demo ==="
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
# Verify server is running
|
|
23
|
+
unless client.ping
|
|
24
|
+
puts "Server is not responding"
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
puts "Server is healthy"
|
|
28
|
+
puts
|
|
29
|
+
|
|
30
|
+
# --- Demo 1: Simple greeter with await/resume ---
|
|
31
|
+
puts "--- Demo 1: Simple Greeter (Single Await) ---"
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
puts "Starting greeter agent..."
|
|
35
|
+
run = client.run_sync(agent: "greeter", input: "start")
|
|
36
|
+
|
|
37
|
+
if run.status == "awaiting"
|
|
38
|
+
puts "Status: #{run.status}"
|
|
39
|
+
puts "Agent asks: #{run.await_request&.message&.text_content}"
|
|
40
|
+
puts
|
|
41
|
+
|
|
42
|
+
# Resume with our response
|
|
43
|
+
puts "Responding with: 'Alice'"
|
|
44
|
+
resume = SimpleAcp::Models::MessageAwaitResume.new(
|
|
45
|
+
message: SimpleAcp::Models::Message.user("Alice")
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
completed_run = client.run_resume_sync(run_id: run.run_id, await_resume: resume)
|
|
49
|
+
puts "Status: #{completed_run.status}"
|
|
50
|
+
puts "Agent says: #{completed_run.output.last&.text_content}"
|
|
51
|
+
else
|
|
52
|
+
puts "Unexpected status: #{run.status}"
|
|
53
|
+
end
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
# --- Demo 2: Multi-step survey ---
|
|
57
|
+
puts "--- Demo 2: Multi-Step Survey (Multiple Awaits) ---"
|
|
58
|
+
puts
|
|
59
|
+
|
|
60
|
+
puts "Starting survey..."
|
|
61
|
+
run = client.run_sync(agent: "survey", input: "begin")
|
|
62
|
+
|
|
63
|
+
responses = ["Bob", "blue", "42"]
|
|
64
|
+
response_index = 0
|
|
65
|
+
|
|
66
|
+
while run.status == "awaiting"
|
|
67
|
+
puts "Agent asks: #{run.await_request&.message&.text_content}"
|
|
68
|
+
|
|
69
|
+
if response_index < responses.length
|
|
70
|
+
response = responses[response_index]
|
|
71
|
+
puts "Responding with: '#{response}'"
|
|
72
|
+
puts
|
|
73
|
+
|
|
74
|
+
resume = SimpleAcp::Models::MessageAwaitResume.new(
|
|
75
|
+
message: SimpleAcp::Models::Message.user(response)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
run = client.run_resume_sync(run_id: run.run_id, await_resume: resume)
|
|
79
|
+
response_index += 1
|
|
80
|
+
else
|
|
81
|
+
puts "ERROR: Ran out of responses!"
|
|
82
|
+
break
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if run.status == "completed"
|
|
87
|
+
puts "Survey completed!"
|
|
88
|
+
puts run.output.last&.text_content
|
|
89
|
+
else
|
|
90
|
+
puts "Unexpected final status: #{run.status}"
|
|
91
|
+
end
|
|
92
|
+
puts
|
|
93
|
+
|
|
94
|
+
# --- Demo 3: Confirmation agent - Yes case ---
|
|
95
|
+
puts "--- Demo 3: Confirmation (Yes) ---"
|
|
96
|
+
puts
|
|
97
|
+
|
|
98
|
+
puts "Requesting action: 'delete all files'"
|
|
99
|
+
run = client.run_sync(agent: "confirmer", input: "delete all files")
|
|
100
|
+
|
|
101
|
+
if run.status == "awaiting"
|
|
102
|
+
puts "Agent asks: #{run.await_request&.message&.text_content}"
|
|
103
|
+
puts "Responding with: 'yes'"
|
|
104
|
+
|
|
105
|
+
resume = SimpleAcp::Models::MessageAwaitResume.new(
|
|
106
|
+
message: SimpleAcp::Models::Message.user("yes")
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
completed_run = client.run_resume_sync(run_id: run.run_id, await_resume: resume)
|
|
110
|
+
puts "Result: #{completed_run.output.last&.text_content}"
|
|
111
|
+
end
|
|
112
|
+
puts
|
|
113
|
+
|
|
114
|
+
# --- Demo 4: Confirmation agent - No case ---
|
|
115
|
+
puts "--- Demo 4: Confirmation (No) ---"
|
|
116
|
+
puts
|
|
117
|
+
|
|
118
|
+
puts "Requesting action: 'format hard drive'"
|
|
119
|
+
run = client.run_sync(agent: "confirmer", input: "format hard drive")
|
|
120
|
+
|
|
121
|
+
if run.status == "awaiting"
|
|
122
|
+
puts "Agent asks: #{run.await_request&.message&.text_content}"
|
|
123
|
+
puts "Responding with: 'no'"
|
|
124
|
+
|
|
125
|
+
resume = SimpleAcp::Models::MessageAwaitResume.new(
|
|
126
|
+
message: SimpleAcp::Models::Message.user("no")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
completed_run = client.run_resume_sync(run_id: run.run_id, await_resume: resume)
|
|
130
|
+
puts "Result: #{completed_run.output.last&.text_content}"
|
|
131
|
+
end
|
|
132
|
+
puts
|
|
133
|
+
|
|
134
|
+
# --- Demo 5: Streaming resume ---
|
|
135
|
+
puts "--- Demo 5: Streaming Resume ---"
|
|
136
|
+
puts
|
|
137
|
+
|
|
138
|
+
puts "Starting greeter with streaming resume..."
|
|
139
|
+
run = client.run_sync(agent: "greeter", input: "start")
|
|
140
|
+
|
|
141
|
+
if run.status == "awaiting"
|
|
142
|
+
puts "Agent asks: #{run.await_request&.message&.text_content}"
|
|
143
|
+
puts "Resuming with streaming..."
|
|
144
|
+
|
|
145
|
+
resume = SimpleAcp::Models::MessageAwaitResume.new(
|
|
146
|
+
message: SimpleAcp::Models::Message.user("Charlie")
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
print "Events: "
|
|
150
|
+
client.run_resume_stream(run_id: run.run_id, await_resume: resume) do |event|
|
|
151
|
+
case event
|
|
152
|
+
when SimpleAcp::Models::RunInProgressEvent
|
|
153
|
+
print "[in_progress] "
|
|
154
|
+
when SimpleAcp::Models::MessageCreatedEvent
|
|
155
|
+
print "[message] "
|
|
156
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
157
|
+
puts "[completed]"
|
|
158
|
+
puts "Final output: #{event.run.output.last&.text_content}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
puts
|
|
163
|
+
|
|
164
|
+
puts "=== Await/Resume Demo Complete ==="
|