agent99 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,183 @@
1
+ # lib/agent99/tcp_message_client.rb
2
+ #
3
+ # NOTE: This is an early attempt at a true
4
+ # peer-to-peer messaging platform. Its not
5
+ # not ready for prime time.
6
+
7
+ require 'socket'
8
+ require 'json'
9
+ require 'logger'
10
+
11
+ class Agent99::TcpMessageClient
12
+ attr_accessor :agents
13
+
14
+ def initialize(
15
+ agents: {},
16
+ logger: Logger.new($stdout)
17
+ )
18
+ @agents = agents
19
+ @logger = logger
20
+ @server_socket = nil
21
+ @client_connections = {}
22
+ @handlers = {}
23
+ @running = false
24
+ end
25
+
26
+ def listen_for_messages(queue, request_handler:, response_handler:, control_handler:)
27
+ @handlers = {
28
+ request: request_handler,
29
+ response: response_handler,
30
+ control: control_handler
31
+ }
32
+
33
+ start_server(queue[:port])
34
+ end
35
+
36
+ def publish(message)
37
+ target = message.dig(:header, :to_uuid)
38
+ return unless target
39
+
40
+ agent_info = agents(target)
41
+ return unless agent_info
42
+
43
+ socket = connect_to_agent(agent_info[:ip], agent_info[:port])
44
+ return unless socket
45
+
46
+ begin
47
+ socket.puts(message.to_json)
48
+ true
49
+
50
+ rescue StandardError => e
51
+ @logger.error("Failed to send message: #{e.message}")
52
+ false
53
+
54
+ ensure
55
+ socket.close unless socket.closed?
56
+ end
57
+ end
58
+
59
+ def stop
60
+ @running = false
61
+ @server_socket&.close
62
+ @client_connections.each_value(&:close)
63
+ @client_connections.clear
64
+ end
65
+
66
+ private
67
+
68
+ def start_server(port)
69
+ @server_socket = TCPServer.new(port)
70
+ @running = true
71
+
72
+ Thread.new do
73
+ while @running
74
+ begin
75
+ client = @server_socket.accept
76
+ handle_client(client)
77
+ rescue StandardError => e
78
+ @logger.error("Server error: #{e.message}")
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def handle_client(client)
85
+ Thread.new do
86
+ while @running
87
+ begin
88
+ message = client.gets
89
+ break if message.nil?
90
+
91
+ parsed_message = JSON.parse(message, symbolize_names: true)
92
+ route_message(parsed_message)
93
+
94
+ rescue JSON::ParserError => e
95
+ @logger.error("Invalid JSON received: #{e.message}")
96
+
97
+ rescue StandardError => e
98
+ @logger.error("Error handling client: #{e.message}")
99
+ break
100
+ end
101
+ end
102
+
103
+ client.close unless client.closed?
104
+ end
105
+ end
106
+
107
+ def route_message(message)
108
+ type = message.dig(:header, :type)&.to_sym
109
+ handler = @handlers[type]
110
+
111
+ if handler
112
+ handler.call(message)
113
+ else
114
+ @logger.warn("No handler for message type: #{type}")
115
+ end
116
+ end
117
+
118
+ def connect_to_agent(ip, port)
119
+ TCPSocket.new(ip, port)
120
+
121
+ rescue StandardError => e
122
+ @logger.error("Failed to connect to #{ip}:#{port}: #{e.message}")
123
+ nil
124
+ end
125
+ end
126
+
127
+
128
+ __END__
129
+
130
+ Based on the provided code for the `Agent99::TcpMessageClient` class, here's an analysis of your questions:
131
+
132
+ 1. Does this class queue up JSON messages while a previous message is being processed?
133
+
134
+ No, this class does not explicitly queue up JSON messages while a previous message is being processed. The class processes messages as they are received, without maintaining an internal queue. Each incoming message is handled in its own thread (see the `handle_client` method), which allows for concurrent processing of messages from multiple clients.
135
+
136
+ 2. Does it present a complete JSON message at once or does it only provide part of one?
137
+
138
+ The class attempts to present complete JSON messages at once. Here's why:
139
+
140
+ - In the `handle_client` method, messages are read using `client.gets`, which typically reads a full line of input (up to a newline character).
141
+ - The received message is then parsed as JSON using `JSON.parse(message, symbolize_names: true)`.
142
+ - If the parsing is successful, the entire parsed message is passed to the `route_message` method.
143
+
144
+ However, there are a few potential issues to consider:
145
+
146
+ - If a JSON message spans multiple lines, `client.gets` might not capture the entire message in one read.
147
+ - There's no explicit handling for partial messages or message boundaries.
148
+ - Large messages might be split across multiple TCP packets, and the current implementation doesn't account for reassembling these.
149
+
150
+ To ensure complete message handling, you might want to consider implementing a more robust message framing protocol, such as using message length prefixes or delimiter-based framing.
151
+
152
+ For example, you could modify the `handle_client` method to use a delimiter-based approach:
153
+
154
+ ```ruby
155
+ def handle_client(client)
156
+ Thread.new do
157
+ buffer = ""
158
+ while @running
159
+ begin
160
+ chunk = client.readpartial(1024)
161
+ buffer += chunk
162
+ while (message_end = buffer.index("\n"))
163
+ message = buffer[0...message_end]
164
+ buffer = buffer[(message_end + 1)..]
165
+ parsed_message = JSON.parse(message, symbolize_names: true)
166
+ route_message(parsed_message)
167
+ end
168
+ rescue EOFError
169
+ break
170
+ rescue JSON::ParserError => e
171
+ @logger.error("Invalid JSON received: #{e.message}")
172
+ rescue StandardError => e
173
+ @logger.error("Error handling client: #{e.message}")
174
+ break
175
+ end
176
+ end
177
+ client.close unless client.closed?
178
+ end
179
+ end
180
+ ```
181
+
182
+ This modification would allow for handling of messages that might be split across multiple reads, ensuring that complete JSON messages are processed.
183
+
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Agent99
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
 
6
6
  def self.version = VERSION
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agent99
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-09 00:00:00.000000000 Z
11
+ date: 2024-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -154,11 +154,15 @@ files:
154
154
  - Rakefile
155
155
  - docs/README.md
156
156
  - docs/advanced_features.md
157
+ - docs/agent99_framework/central_registry.md
158
+ - docs/agent99_framework/message_client.md
159
+ - docs/agent99_framework/registry_client.md
157
160
  - docs/agent_discovery.md
158
161
  - docs/agent_lifecycle.md
159
162
  - docs/agent_registry_processes.md
160
163
  - docs/api_reference.md
161
164
  - docs/architecture.md
165
+ - docs/breaking_change_v0.0.4.md
162
166
  - docs/configuration.md
163
167
  - docs/control_actions.md
164
168
  - docs/custom_agent_implementation.md
@@ -176,6 +180,7 @@ files:
176
180
  - docs/schema_definition.md
177
181
  - docs/security.md
178
182
  - docs/troubleshooting.md
183
+ - docs/what_is_an_agent.md
179
184
  - examples/README.md
180
185
  - examples/agent_watcher.rb
181
186
  - examples/agents/.keep
@@ -201,6 +206,7 @@ files:
201
206
  - lib/agent99/message_processing.rb
202
207
  - lib/agent99/nats_message_client.rb
203
208
  - lib/agent99/registry_client.rb
209
+ - lib/agent99/tcp_message_client.rb
204
210
  - lib/agent99/timestamp.rb
205
211
  - lib/agent99/version.rb
206
212
  - sig/ai_agent.rbs