panteao-client 1.1.18

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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/panteao_client.rb +234 -0
  3. metadata +42 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 05cd40c5543a8619277c2b9d3ead17650da0c6ce1a08809dd20fd50fc53a6468
4
+ data.tar.gz: 832292ba43dd44c4190282edbe57046dd64933512b6fcdc24fdca0238afa3304
5
+ SHA512:
6
+ metadata.gz: 3d84d924695d2374fa319f28e351b27aa9dc53f711494036012ae45d65160ac734aa7fda2f2c61eaaa7bc5c8e74d16387569975744eddad814e849d0526b57a1
7
+ data.tar.gz: 76b9c30c91c66500d8e85ee6ca234db5cd4a2645b56a18713df507bfba795a6fe51043ad5b2d19e19587b901e5c1834efd632c543ec499702c4fbae1042d6fe3
@@ -0,0 +1,234 @@
1
+ require 'socket'
2
+ require 'json'
3
+
4
+ require 'net/http'
5
+ require 'rubygems/package'
6
+ require 'zlib'
7
+ require 'fileutils'
8
+ require 'open3'
9
+ require 'stringio'
10
+
11
+ module Panteao
12
+ VERSION = '1.1.18'
13
+
14
+ class BdiClient
15
+
16
+ def self.download_engine(bin_path)
17
+ is_win = Gem.win_platform?
18
+ is_mac = /darwin/ =~ RUBY_PLATFORM
19
+ os_name = is_win ? 'win32' : (is_mac ? 'darwin' : 'linux')
20
+
21
+ arch = (/arm/ =~ RUBY_PLATFORM || /aarch64/ =~ RUBY_PLATFORM) ? 'arm64' : 'x64'
22
+
23
+ pkg_name = "panteao-engine-#{os_name}-#{arch}"
24
+ url = "https://registry.npmjs.org/#{pkg_name}/-/#{pkg_name}-#{VERSION}.tgz"
25
+
26
+ puts "\e[36m[Panteao]\e[0m Downloading native engine for #{os_name}-#{arch} (v#{VERSION})..."
27
+
28
+ uri = URI(url)
29
+ response = Net::HTTP.get_response(uri)
30
+
31
+ raise "Failed to download engine: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
32
+
33
+ sio = StringIO.new(response.body)
34
+ gz = Zlib::GzipReader.new(sio)
35
+ tar = Gem::Package::TarReader.new(gz)
36
+
37
+ tar.each do |entry|
38
+ if entry.full_name.end_with?('panteao-engine') || entry.full_name.end_with?('panteao-engine.exe')
39
+ FileUtils.mkdir_p(File.dirname(bin_path))
40
+ File.open(bin_path, 'wb') { |f| f.write(entry.read) }
41
+ FileUtils.chmod(0755, bin_path)
42
+ return
43
+ end
44
+ end
45
+ raise "Binary not found in tarball"
46
+ end
47
+
48
+ def self.read_logs(io)
49
+ Thread.new do
50
+ begin
51
+ io.each_line do |line|
52
+ line = line.strip
53
+ next if line.empty?
54
+ if line.start_with?('[') && (idx2 = line.index(']'))
55
+ name = line[1...idx2].split('.').last
56
+ puts "\e[36m[#{name}]\e[0m #{line[(idx2 + 2)..-1]}"
57
+ else
58
+ puts "\e[36m[MAS]\e[0m #{line}"
59
+ end
60
+ end
61
+ rescue
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.get_free_port
67
+ server = TCPServer.new('127.0.0.1', 0)
68
+ port = server.addr[1]
69
+ server.close
70
+ port
71
+ end
72
+
73
+ def self.find_binary
74
+ is_win = Gem.win_platform?
75
+ bin_name = is_win ? 'panteao-engine.exe' : 'panteao-engine'
76
+
77
+ current_dir = File.expand_path(File.dirname(__FILE__))
78
+ cand1 = File.join(current_dir, bin_name)
79
+ return cand1 if File.exist?(cand1)
80
+ cand2 = File.join(current_dir, 'bin', bin_name)
81
+ return cand2 if File.exist?(cand2)
82
+
83
+ cwd = Dir.pwd
84
+ cand3 = File.join(cwd, bin_name)
85
+ return cand3 if File.exist?(cand3)
86
+ cand4 = File.join(cwd, 'bin', bin_name)
87
+ return cand4 if File.exist?(cand4)
88
+
89
+ bin_name
90
+ end
91
+
92
+ def initialize(host = '127.0.0.1', port = 0, project: nil)
93
+ if project
94
+ if port == 0
95
+ port = self.class.get_free_port
96
+ end
97
+ bin = self.class.find_binary
98
+ unless File.exist?(bin)
99
+ current_dir = File.expand_path(File.dirname(__FILE__))
100
+ bin = File.join(current_dir, Gem.win_platform? ? 'panteao-engine.exe' : 'panteao-engine')
101
+ self.class.download_engine(bin)
102
+ end
103
+
104
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(bin, project, '--port', port.to_s)
105
+ @pid = @wait_thr.pid
106
+ self.class.read_logs(@stdout)
107
+ self.class.read_logs(@stderr)
108
+ sleep 0.8
109
+ elsif port == 0
110
+ port = 44444
111
+ end
112
+
113
+ @socket = TCPSocket.new(host, port)
114
+ while (line = @socket.gets)
115
+ break if line.include?('"type":"mas_ready"')
116
+ end
117
+
118
+ @handlers = {}
119
+ @running = true
120
+ @thread = Thread.new { listen }
121
+ end
122
+
123
+ def send_msg(performative, sender, receiver, content)
124
+ msg = { type: 'message', performative: performative, sender: sender, receiver: receiver, content: content }
125
+ @socket.puts(msg.to_json)
126
+ end
127
+
128
+ def send_perception(action, perception)
129
+ payload = { type: 'perception', action: action, perception: perception }.to_json + "\n"
130
+ @socket.write(payload)
131
+ end
132
+
133
+ def register_action(action_name, &block)
134
+ @handlers[action_name] = block
135
+ end
136
+
137
+ private
138
+
139
+ def send_action_result(id, success)
140
+ payload = { type: 'action_result', id: id, success: success }.to_json + "\n"
141
+ @socket.write(payload)
142
+ end
143
+
144
+ def listen
145
+ while @running && (line = @socket.gets)
146
+ handle_line(line.strip)
147
+ end
148
+ rescue => e
149
+ end
150
+
151
+ def handle_line(line)
152
+ return if line.empty?
153
+ begin
154
+ msg = JSON.parse(line)
155
+ if msg['type'] == 'action'
156
+ raw_action = msg['action']
157
+ action_id = msg['id']
158
+ name, args = parse_action(raw_action)
159
+
160
+ handler = @handlers[name]
161
+ if handler
162
+ respond = proc { |success| send_action_result(action_id, success) }
163
+ handler.call(args, respond)
164
+ else
165
+ send_action_result(action_id, true)
166
+ end
167
+ end
168
+ rescue => e
169
+ end
170
+ end
171
+
172
+ def parse_action(action_str)
173
+ paren_idx = action_str.index('(')
174
+ return action_str.strip, [] unless paren_idx
175
+
176
+ name = action_str[0...paren_idx].strip
177
+ args_str = action_str[paren_idx + 1...action_str.rindex(')')]
178
+
179
+ args = []
180
+ current = ""
181
+ inside_quotes = false
182
+ depth_brackets = 0
183
+ depth_parens = 0
184
+
185
+ args_str.each_char do |c|
186
+ if c == '"'
187
+ inside_quotes = !inside_quotes
188
+ current << c
189
+ elsif !inside_quotes && c == '['
190
+ depth_brackets += 1
191
+ current << c
192
+ elsif !inside_quotes && c == ']'
193
+ depth_brackets -= 1
194
+ current << c
195
+ elsif !inside_quotes && c == '('
196
+ depth_parens += 1
197
+ current << c
198
+ elsif !inside_quotes && c == ')'
199
+ depth_parens -= 1
200
+ current << c
201
+ elsif c == ',' && !inside_quotes && depth_brackets == 0 && depth_parens == 0
202
+ args << clean_arg(current)
203
+ current = ""
204
+ else
205
+ current << c
206
+ end
207
+ end
208
+ args << clean_arg(current) unless current.empty?
209
+
210
+ return name, args
211
+ end
212
+
213
+ def clean_arg(arg)
214
+ s = arg.strip
215
+ if s.start_with?('"') && s.end_with?('"') && s.length >= 2
216
+ return s[1..-2]
217
+ end
218
+ s
219
+ end
220
+
221
+ public
222
+
223
+ def close
224
+ @running = false
225
+ @socket.close rescue nil
226
+ @thread.join rescue nil
227
+ if @pid
228
+ Process.kill('KILL', @pid) rescue nil
229
+ Process.wait(@pid) rescue nil
230
+ @pid = nil
231
+ end
232
+ end
233
+ end
234
+ end
metadata ADDED
@@ -0,0 +1,42 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: panteao-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.18
5
+ platform: ruby
6
+ authors:
7
+ - Panteao Authors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/panteao_client.rb
20
+ homepage:
21
+ licenses: []
22
+ metadata: {}
23
+ post_install_message:
24
+ rdoc_options: []
25
+ require_paths:
26
+ - lib
27
+ required_ruby_version: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 2.7.0
32
+ required_rubygems_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ requirements: []
38
+ rubygems_version: 3.4.20
39
+ signing_key:
40
+ specification_version: 4
41
+ summary: Ruby client for Panteao BDI Coprocessor
42
+ test_files: []