clamo 0.1.0 → 0.4.0
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 +4 -4
- data/lib/clamo/gpt_json_rpc_server.rb +186 -0
- data/lib/clamo/jsonrpc.rb +6 -3
- data/lib/clamo/server.rb +8 -1
- data/lib/clamo/version.rb +1 -1
- metadata +11 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50a648eb843f70c35499806bb4f53736046560d333af184759917ff10f321dbe
|
4
|
+
data.tar.gz: ce450a2e07864e7908ec0321f1f1f116168dbe6fd7ae7cc054bffc84442f33d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 242c0a8d44b69ada88887001943213ab8a6cd074eecfec4482e08de60b39d505df2fd58b4e34a7f00b2266250e3d54f07bc187b036dc08d47889ec1fe55d6140
|
7
|
+
data.tar.gz: 4c72e3384fd958d1e9fe2a5a5b0ae607c4a667fbb9bc69eb04b1c8a10889ced46fd7caa47e8cc8f413dc5aec1746104c509c83165ec70863b4e02cc717090845
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module GPTJsonRpcServer
|
6
|
+
PROTOCOL_VERSION = "2.0"
|
7
|
+
|
8
|
+
def self.handle_request(object:, request_json:)
|
9
|
+
requests = JSON.parse(request_json)
|
10
|
+
if requests.is_a?(Array)
|
11
|
+
responses = requests.map do |request|
|
12
|
+
Thread.new { process_request(object, request) }
|
13
|
+
end.map(&:value).compact
|
14
|
+
responses.empty? ? nil : responses.to_json
|
15
|
+
else
|
16
|
+
response = process_request(object, requests)
|
17
|
+
response&.to_json
|
18
|
+
end
|
19
|
+
rescue JSON::ParserError
|
20
|
+
{ jsonrpc: PROTOCOL_VERSION, error: { code: -32_700, message: "Parse error" }, id: nil }.to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.process_request(object, request)
|
24
|
+
method = request["method"]
|
25
|
+
params = request["params"]
|
26
|
+
id = request["id"]
|
27
|
+
|
28
|
+
unless valid_id?(id)
|
29
|
+
return { jsonrpc: PROTOCOL_VERSION, error: { code: -32_600, message: "Invalid Request" }, id: nil }
|
30
|
+
end
|
31
|
+
|
32
|
+
unless method.is_a?(String) && object.public_methods(false).include?(method.to_sym)
|
33
|
+
return { jsonrpc: PROTOCOL_VERSION, error: { code: -32_601, message: "Method not found" }, id: id }
|
34
|
+
end
|
35
|
+
|
36
|
+
method_object = object.method(method)
|
37
|
+
param_list = method_object.parameters
|
38
|
+
valid_params = case params
|
39
|
+
when Array
|
40
|
+
param_list.none? { |type, _| type == :rest } && params.size == param_list.count do |type, _|
|
41
|
+
%i[req opt rest].include?(type)
|
42
|
+
end
|
43
|
+
when Hash
|
44
|
+
required_params = param_list.slice(:keyreq).map(&:last)
|
45
|
+
required_params.all? { |key| params.key?(key) } && param_list.all? do |type, name|
|
46
|
+
type != :keyreq || params.key?(name)
|
47
|
+
end
|
48
|
+
when NilClass
|
49
|
+
param_list.empty?
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
unless valid_params
|
55
|
+
return { jsonrpc: PROTOCOL_VERSION, error: { code: -32_602, message: "Invalid params" },
|
56
|
+
id: id }
|
57
|
+
end
|
58
|
+
|
59
|
+
if id.nil?
|
60
|
+
# Treat id: null as a notification
|
61
|
+
Thread.new { safe_call_method(method_object, params) }
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
|
65
|
+
begin
|
66
|
+
result = safe_call_method(method_object, params)
|
67
|
+
{ jsonrpc: PROTOCOL_VERSION, result: result, id: id }
|
68
|
+
rescue StandardError => e
|
69
|
+
{ jsonrpc: PROTOCOL_VERSION, error: { code: -32_603, message: "Internal error", data: e.message }, id: id }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.safe_call_method(method_object, params)
|
74
|
+
if params.is_a?(Array)
|
75
|
+
method_object.call(*params)
|
76
|
+
elsif params.is_a?(Hash)
|
77
|
+
method_object.call(**params)
|
78
|
+
else
|
79
|
+
method_object.call
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.valid_id?(id)
|
84
|
+
return false unless id.is_a?(String) || id.is_a?(Numeric) || id.nil?
|
85
|
+
return false if id.is_a?(Numeric) && id != id.to_i
|
86
|
+
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Example usage
|
92
|
+
|
93
|
+
class MyService
|
94
|
+
def add(a, b)
|
95
|
+
a + b
|
96
|
+
end
|
97
|
+
|
98
|
+
def subtract(a:, b:)
|
99
|
+
a - b
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def private_method
|
105
|
+
"This should not be exposed"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
service = MyService.new
|
110
|
+
|
111
|
+
# Example JSON-RPC requests
|
112
|
+
single_request_positional = {
|
113
|
+
jsonrpc: "2.0",
|
114
|
+
method: "add",
|
115
|
+
params: [1, 2],
|
116
|
+
id: 1
|
117
|
+
}.to_json
|
118
|
+
|
119
|
+
single_request_keyword = {
|
120
|
+
jsonrpc: "2.0",
|
121
|
+
method: "subtract",
|
122
|
+
params: { a: 5, b: 3 },
|
123
|
+
id: 2
|
124
|
+
}.to_json
|
125
|
+
|
126
|
+
batch_request = [
|
127
|
+
{ jsonrpc: "2.0", method: "add", params: [1, 2], id: 1 },
|
128
|
+
{ jsonrpc: "2.0", method: "subtract", params: { a: 5, b: 3 }, id: 2 },
|
129
|
+
{ jsonrpc: "2.0", method: "add", params: [7, 3], id: 3 }
|
130
|
+
].to_json
|
131
|
+
|
132
|
+
notification_request = {
|
133
|
+
jsonrpc: "2.0",
|
134
|
+
method: "add",
|
135
|
+
params: [1, 2]
|
136
|
+
}.to_json
|
137
|
+
|
138
|
+
invalid_id_null_request = {
|
139
|
+
jsonrpc: "2.0",
|
140
|
+
method: "add",
|
141
|
+
params: [1, 2],
|
142
|
+
id: nil
|
143
|
+
}.to_json
|
144
|
+
|
145
|
+
invalid_id_object_request = {
|
146
|
+
jsonrpc: "2.0",
|
147
|
+
method: "add",
|
148
|
+
params: [1, 2],
|
149
|
+
id: {}
|
150
|
+
}.to_json
|
151
|
+
|
152
|
+
invalid_method_request = {
|
153
|
+
jsonrpc: "2.0",
|
154
|
+
method: {},
|
155
|
+
params: [1, 2],
|
156
|
+
id: 1
|
157
|
+
}.to_json
|
158
|
+
|
159
|
+
# Handling single request with positional parameters
|
160
|
+
response_json_positional = GPTJsonRpcServer.handle_request(object: service, request_json: single_request_positional)
|
161
|
+
puts response_json_positional # Output: {"jsonrpc":"2.0","result":3,"id":1}
|
162
|
+
|
163
|
+
# Handling single request with keyword parameters
|
164
|
+
response_json_keyword = GPTJsonRpcServer.handle_request(object: service, request_json: single_request_keyword)
|
165
|
+
puts response_json_keyword # Output: {"jsonrpc":"2.0","result":2,"id":2}
|
166
|
+
|
167
|
+
# Handling batch request
|
168
|
+
batch_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: batch_request)
|
169
|
+
puts batch_response_json # Output: [{"jsonrpc":"2.0","result":3,"id":1},{"jsonrpc":"2.0","result":2,"id":2},{"jsonrpc":"2.0","result":10,"id":3}]
|
170
|
+
|
171
|
+
# Handling notification request (no response expected)
|
172
|
+
notification_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: notification_request)
|
173
|
+
puts notification_response_json.nil? # Output: true
|
174
|
+
|
175
|
+
# Handling request with id = null (treated as a notification)
|
176
|
+
invalid_id_null_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: invalid_id_null_request)
|
177
|
+
puts invalid_id_null_response_json.nil? # Output: true
|
178
|
+
|
179
|
+
# Handling request with id as an object (should return an error)
|
180
|
+
invalid_id_object_response_json = GPTJsonRpcServer.handle_request(object: service,
|
181
|
+
request_json: invalid_id_object_request)
|
182
|
+
puts invalid_id_object_response_json # Output: {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}
|
183
|
+
|
184
|
+
# Handling request with method as an object (should return an error)
|
185
|
+
invalid_method_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: invalid_method_request)
|
186
|
+
puts invalid_method_response_json # Output: {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":1}
|
data/lib/clamo/jsonrpc.rb
CHANGED
@@ -37,7 +37,7 @@ module Clamo
|
|
37
37
|
|
38
38
|
def proper_params_if_any?(request)
|
39
39
|
if request.key?("params")
|
40
|
-
request["params"].is_a?(Array)
|
40
|
+
request["params"].is_a?(Array) || request["params"].is_a?(Hash)
|
41
41
|
else
|
42
42
|
true
|
43
43
|
end
|
@@ -47,8 +47,11 @@ module Clamo
|
|
47
47
|
request.is_a?(Hash) &&
|
48
48
|
proper_pragma?(request) &&
|
49
49
|
proper_method?(request) &&
|
50
|
-
proper_id_if_any?(request)
|
51
|
-
|
50
|
+
proper_id_if_any?(request)
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid_params?(request)
|
54
|
+
proper_params_if_any?(request)
|
52
55
|
end
|
53
56
|
|
54
57
|
def build_request **opts
|
data/lib/clamo/server.rb
CHANGED
@@ -41,7 +41,7 @@ module Clamo
|
|
41
41
|
when Array
|
42
42
|
object.send method.to_sym, *params
|
43
43
|
when Hash
|
44
|
-
object.send method.to_sym, **params
|
44
|
+
object.send method.to_sym, **params.transform_keys(&:to_sym)
|
45
45
|
when NilClass
|
46
46
|
object.send method.to_sym
|
47
47
|
else
|
@@ -86,6 +86,13 @@ module Clamo
|
|
86
86
|
)
|
87
87
|
end
|
88
88
|
|
89
|
+
unless JSONRPC.valid_params?(request)
|
90
|
+
return JSONRPC.build_error_response_from(
|
91
|
+
id: request["id"],
|
92
|
+
descriptor: JSONRPC::ProtocolErrors::INVALID_PARAMS
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
89
96
|
unless request.key?("id") # notification - no result needed
|
90
97
|
# TODO: block.call off the current thread
|
91
98
|
Thread.new do
|
data/lib/clamo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clamo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andriy Tyurnikov
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-04-23 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: parallel
|
@@ -16,14 +15,14 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
18
|
+
version: 1.27.0
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
25
|
+
version: 1.27.0
|
27
26
|
description: JSON-RPC Client/Server tooling for Ruby
|
28
27
|
email:
|
29
28
|
- Andriy.Tyurnikov@gmail.com
|
@@ -36,18 +35,18 @@ files:
|
|
36
35
|
- Rakefile
|
37
36
|
- lib/clamo.rb
|
38
37
|
- lib/clamo/client.rb
|
38
|
+
- lib/clamo/gpt_json_rpc_server.rb
|
39
39
|
- lib/clamo/jsonrpc.rb
|
40
40
|
- lib/clamo/server.rb
|
41
41
|
- lib/clamo/version.rb
|
42
42
|
- sig/clamo.rbs
|
43
|
-
homepage: https://github.com/
|
43
|
+
homepage: https://github.com/rubakas/clamo
|
44
44
|
licenses: []
|
45
45
|
metadata:
|
46
|
-
homepage_uri: https://github.com/
|
47
|
-
source_code_uri: https://github.com/
|
48
|
-
changelog_uri: https://github.com/
|
49
|
-
rubygems_mfa_required: '
|
50
|
-
post_install_message:
|
46
|
+
homepage_uri: https://github.com/rubakas/clamo
|
47
|
+
source_code_uri: https://github.com/rubakas/clamo
|
48
|
+
changelog_uri: https://github.com/rubakas/clamo
|
49
|
+
rubygems_mfa_required: 'true'
|
51
50
|
rdoc_options: []
|
52
51
|
require_paths:
|
53
52
|
- lib
|
@@ -62,8 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
61
|
- !ruby/object:Gem::Version
|
63
62
|
version: '0'
|
64
63
|
requirements: []
|
65
|
-
rubygems_version: 3.5
|
66
|
-
signing_key:
|
64
|
+
rubygems_version: 3.6.5
|
67
65
|
specification_version: 4
|
68
66
|
summary: JSON-RPC Client/Server tooling for Ruby
|
69
67
|
test_files: []
|