acp_ruby 0.1.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 +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +125 -0
- data/lib/acp_ruby.rb +3 -0
- data/lib/agent_client_protocol/agent/connection.rb +125 -0
- data/lib/agent_client_protocol/agent/router.rb +62 -0
- data/lib/agent_client_protocol/agent.rb +49 -0
- data/lib/agent_client_protocol/client/connection.rb +112 -0
- data/lib/agent_client_protocol/client/router.rb +85 -0
- data/lib/agent_client_protocol/client.rb +53 -0
- data/lib/agent_client_protocol/connection.rb +144 -0
- data/lib/agent_client_protocol/contrib/permission_broker.rb +57 -0
- data/lib/agent_client_protocol/contrib/session_accumulator.rb +160 -0
- data/lib/agent_client_protocol/contrib/tool_call_tracker.rb +115 -0
- data/lib/agent_client_protocol/error.rb +41 -0
- data/lib/agent_client_protocol/helpers.rb +176 -0
- data/lib/agent_client_protocol/meta.rb +30 -0
- data/lib/agent_client_protocol/router.rb +108 -0
- data/lib/agent_client_protocol/schema/base_model.rb +158 -0
- data/lib/agent_client_protocol/schema/generated.rb +508 -0
- data/lib/agent_client_protocol/schema/types.rb +200 -0
- data/lib/agent_client_protocol/stdio.rb +129 -0
- data/lib/agent_client_protocol/transport.rb +70 -0
- data/lib/agent_client_protocol/version.rb +5 -0
- data/lib/agent_client_protocol.rb +54 -0
- data/schema/VERSION +1 -0
- data/schema/meta.json +24 -0
- data/schema/schema.json +3430 -0
- metadata +103 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# AUTO-GENERATED from meta.json — DO NOT EDIT
|
|
4
|
+
|
|
5
|
+
module AgentClientProtocol
|
|
6
|
+
PROTOCOL_VERSION = 1
|
|
7
|
+
|
|
8
|
+
AGENT_METHODS = {
|
|
9
|
+
authenticate: "authenticate",
|
|
10
|
+
initialize: "initialize",
|
|
11
|
+
session_cancel: "session/cancel",
|
|
12
|
+
session_load: "session/load",
|
|
13
|
+
session_new: "session/new",
|
|
14
|
+
session_prompt: "session/prompt",
|
|
15
|
+
session_set_config_option: "session/set_config_option",
|
|
16
|
+
session_set_mode: "session/set_mode",
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
CLIENT_METHODS = {
|
|
20
|
+
fs_read_text_file: "fs/read_text_file",
|
|
21
|
+
fs_write_text_file: "fs/write_text_file",
|
|
22
|
+
session_request_permission: "session/request_permission",
|
|
23
|
+
session_update: "session/update",
|
|
24
|
+
terminal_create: "terminal/create",
|
|
25
|
+
terminal_kill: "terminal/kill",
|
|
26
|
+
terminal_output: "terminal/output",
|
|
27
|
+
terminal_release: "terminal/release",
|
|
28
|
+
terminal_wait_for_exit: "terminal/wait_for_exit",
|
|
29
|
+
}.freeze
|
|
30
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentClientProtocol
|
|
4
|
+
class Router
|
|
5
|
+
def initialize
|
|
6
|
+
@request_handlers = {}
|
|
7
|
+
@notification_handlers = {}
|
|
8
|
+
@ext_method_handler = nil
|
|
9
|
+
@ext_notification_handler = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def on_request(method, request_class: nil, response_class: nil, optional: false, &handler)
|
|
13
|
+
@request_handlers[method] = {
|
|
14
|
+
handler: handler,
|
|
15
|
+
request_class: request_class,
|
|
16
|
+
response_class: response_class,
|
|
17
|
+
optional: optional
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_notification(method, request_class: nil, &handler)
|
|
22
|
+
@notification_handlers[method] = {
|
|
23
|
+
handler: handler,
|
|
24
|
+
request_class: request_class
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_ext_method(&handler)
|
|
29
|
+
@ext_method_handler = handler
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def on_ext_notification(&handler)
|
|
33
|
+
@ext_notification_handler = handler
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call(method, params, is_notification)
|
|
37
|
+
if is_notification
|
|
38
|
+
dispatch_notification(method, params)
|
|
39
|
+
else
|
|
40
|
+
dispatch_request(method, params)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def dispatch_request(method, params)
|
|
47
|
+
# Extension methods start with _
|
|
48
|
+
if method.start_with?("_")
|
|
49
|
+
if @ext_method_handler
|
|
50
|
+
return @ext_method_handler.call(method, params)
|
|
51
|
+
else
|
|
52
|
+
raise RequestError.method_not_found(method)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
entry = @request_handlers[method]
|
|
57
|
+
unless entry
|
|
58
|
+
raise RequestError.method_not_found(method)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
deserialized = deserialize_params(params, entry[:request_class])
|
|
63
|
+
result = entry[:handler].call(deserialized)
|
|
64
|
+
|
|
65
|
+
if result.nil? && entry[:optional]
|
|
66
|
+
return nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
serialize_result(result)
|
|
70
|
+
rescue NotImplementedError
|
|
71
|
+
if entry[:optional]
|
|
72
|
+
nil
|
|
73
|
+
else
|
|
74
|
+
raise RequestError.method_not_found(method)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def dispatch_notification(method, params)
|
|
80
|
+
if method.start_with?("_")
|
|
81
|
+
@ext_notification_handler&.call(method, params)
|
|
82
|
+
return nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
entry = @notification_handlers[method]
|
|
86
|
+
return nil unless entry
|
|
87
|
+
|
|
88
|
+
deserialized = deserialize_params(params, entry[:request_class])
|
|
89
|
+
entry[:handler].call(deserialized)
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def deserialize_params(params, klass)
|
|
94
|
+
return params unless klass && params.is_a?(Hash)
|
|
95
|
+
|
|
96
|
+
klass.from_hash(params)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def serialize_result(result)
|
|
100
|
+
case result
|
|
101
|
+
when Schema::BaseModel then result.to_h
|
|
102
|
+
when Hash then result
|
|
103
|
+
when nil then nil
|
|
104
|
+
else result
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentClientProtocol
|
|
4
|
+
module Schema
|
|
5
|
+
class BaseModel
|
|
6
|
+
class << self
|
|
7
|
+
def properties
|
|
8
|
+
@properties ||= {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def required_properties
|
|
12
|
+
@required_properties ||= Set.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Register a discriminator field + value for this class.
|
|
16
|
+
# E.g., `discriminator "type", "text"` means to_h includes {"type" => "text"}
|
|
17
|
+
def discriminator(field = nil, value = nil)
|
|
18
|
+
if field
|
|
19
|
+
@discriminator_field = field
|
|
20
|
+
@discriminator_value = value
|
|
21
|
+
end
|
|
22
|
+
@discriminator_field ? [@discriminator_field, @discriminator_value] : nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def property(ruby_name, json_name: nil, type: nil, default: :_no_default, required: false)
|
|
26
|
+
json_name ||= to_camel_case(ruby_name.to_s)
|
|
27
|
+
properties[ruby_name] = {json_name: json_name, type: type, default: default}
|
|
28
|
+
required_properties << ruby_name if required
|
|
29
|
+
attr_reader ruby_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def from_hash(hash)
|
|
33
|
+
return nil if hash.nil?
|
|
34
|
+
|
|
35
|
+
kwargs = {}
|
|
36
|
+
properties.each do |ruby_name, meta|
|
|
37
|
+
json_name = meta[:json_name]
|
|
38
|
+
if hash.key?(json_name)
|
|
39
|
+
kwargs[ruby_name] = deserialize_value(hash[json_name], meta[:type])
|
|
40
|
+
elsif hash.key?(ruby_name.to_s)
|
|
41
|
+
kwargs[ruby_name] = deserialize_value(hash[ruby_name.to_s], meta[:type])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
new(**kwargs)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def inherited(subclass)
|
|
48
|
+
super
|
|
49
|
+
subclass.instance_variable_set(:@properties, properties.dup)
|
|
50
|
+
subclass.instance_variable_set(:@required_properties, required_properties.dup)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def to_camel_case(str)
|
|
56
|
+
return str if str == "_meta"
|
|
57
|
+
|
|
58
|
+
parts = str.split("_")
|
|
59
|
+
parts[0] + parts[1..].map(&:capitalize).join
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def deserialize_value(value, type)
|
|
63
|
+
return nil if value.nil?
|
|
64
|
+
|
|
65
|
+
case type
|
|
66
|
+
when Class
|
|
67
|
+
if type < BaseModel
|
|
68
|
+
type.from_hash(value)
|
|
69
|
+
else
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
when :content_block
|
|
73
|
+
ContentBlock.parse(value)
|
|
74
|
+
when :session_update
|
|
75
|
+
SessionUpdate.parse(value)
|
|
76
|
+
when :tool_call_content
|
|
77
|
+
ToolCallContent.parse(value)
|
|
78
|
+
when :permission_outcome
|
|
79
|
+
RequestPermissionOutcome.parse(value)
|
|
80
|
+
when Array
|
|
81
|
+
if type[0] == :array && value.is_a?(::Array)
|
|
82
|
+
value.map { |v| deserialize_value(v, type[1]) }
|
|
83
|
+
else
|
|
84
|
+
value
|
|
85
|
+
end
|
|
86
|
+
when :hash
|
|
87
|
+
value
|
|
88
|
+
else
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def initialize(**kwargs)
|
|
95
|
+
self.class.properties.each do |ruby_name, meta|
|
|
96
|
+
if kwargs.key?(ruby_name)
|
|
97
|
+
instance_variable_set(:"@#{ruby_name}", kwargs[ruby_name])
|
|
98
|
+
elsif meta[:default] != :_no_default
|
|
99
|
+
default = meta[:default]
|
|
100
|
+
default = default.dup if default.is_a?(::Hash) || default.is_a?(::Array)
|
|
101
|
+
instance_variable_set(:"@#{ruby_name}", default)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def to_h
|
|
107
|
+
result = {}
|
|
108
|
+
|
|
109
|
+
# Include discriminator if set on this class
|
|
110
|
+
disc = self.class.discriminator
|
|
111
|
+
if disc
|
|
112
|
+
result[disc[0]] = disc[1]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
self.class.properties.each do |ruby_name, meta|
|
|
116
|
+
value = instance_variable_get(:"@#{ruby_name}")
|
|
117
|
+
next if value.nil? && !self.class.required_properties.include?(ruby_name)
|
|
118
|
+
|
|
119
|
+
result[meta[:json_name]] = serialize_value(value)
|
|
120
|
+
end
|
|
121
|
+
result
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def to_json(*)
|
|
125
|
+
JSON.generate(to_h, *)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def ==(other)
|
|
129
|
+
other.is_a?(self.class) && to_h == other.to_h
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def hash
|
|
133
|
+
[self.class, to_h].hash
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def eql?(other)
|
|
137
|
+
self == other
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def inspect
|
|
141
|
+
pairs = self.class.properties.keys.map { |k| "#{k}: #{instance_variable_get(:"@#{k}").inspect}" }
|
|
142
|
+
"#<#{self.class.name} #{pairs.join(", ")}>"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def serialize_value(value)
|
|
148
|
+
case value
|
|
149
|
+
when BaseModel then value.to_h
|
|
150
|
+
when ::Array then value.map { |v| serialize_value(v) }
|
|
151
|
+
when ::Hash then value.transform_values { |v| serialize_value(v) }
|
|
152
|
+
else
|
|
153
|
+
value.respond_to?(:to_h) && !value.is_a?(Numeric) ? value.to_h : value
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|