acp-sdk 0.1.0 → 0.3.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/CHANGELOG.md +18 -0
- data/README.md +66 -41
- data/lib/acp/agent_connection/client.rb +82 -3
- data/lib/acp/agent_connection/optional_method/authenticate.rb +21 -0
- data/lib/acp/agent_connection/optional_method/close_session.rb +21 -0
- data/lib/acp/agent_connection/optional_method/delete_session.rb +21 -0
- data/lib/acp/agent_connection/optional_method/list_sessions.rb +21 -0
- data/lib/acp/agent_connection/optional_method/load_session.rb +21 -0
- data/lib/acp/agent_connection/optional_method/resume_session.rb +21 -0
- data/lib/acp/agent_connection/optional_method.rb +1 -25
- data/lib/acp/agent_connection.rb +47 -47
- data/lib/acp/agent_process.rb +119 -0
- data/lib/acp/client_connection.rb +13 -15
- data/lib/acp/request_error.rb +81 -0
- data/lib/acp/transport/reply.rb +4 -4
- data/lib/acp/transport/stdio.rb +28 -25
- data/lib/acp/types.rb +4 -0
- data/lib/acp/version.rb +1 -1
- data/sig/generated/acp/agent_connection/client.rbs +51 -2
- data/sig/generated/acp/agent_connection/optional_method/authenticate.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/close_session.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/delete_session.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/list_sessions.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/load_session.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/resume_session.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method.rbs +1 -18
- data/sig/generated/acp/agent_connection.rbs +17 -22
- data/sig/generated/acp/agent_process.rbs +72 -0
- data/sig/generated/acp/client_connection.rbs +16 -16
- data/sig/generated/acp/request_error.rbs +65 -0
- data/sig/generated/acp/transport/reply.rbs +5 -5
- data/sig/generated/acp/transport/stdio.rbs +14 -19
- data/sig/generated/acp/types.rbs +4 -0
- data/sig/manual/acp/agent_connection/optional_method/authenticate.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/close_session.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/delete_session.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/list_sessions.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/load_session.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/resume_session.rbs +6 -0
- data/sig/manual/acp/agent_connection.rbs +55 -6
- data/sig/manual/acp/agent_process.rbs +7 -0
- data/sig/manual/acp/client_connection.rbs +1 -1
- data/sig/manual/acp/transport/stdio.rbs +7 -1
- metadata +26 -8
- data/lib/acp/transport/response_error.rb +0 -28
- data/lib/acp/transport/result.rb +0 -35
- data/sig/generated/acp/transport/response_error.rbs +0 -21
- data/sig/generated/acp/transport/result.rbs +0 -25
- data/sig/manual/acp/types.rbs +0 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
# A child agent process and the ACP::ClientConnection wired to its stdio.
|
|
6
|
+
# Spawn one with ACP::AgentProcess.spawn.
|
|
7
|
+
class ACP::AgentProcess
|
|
8
|
+
# @rbs @connection: ACP::ClientConnection
|
|
9
|
+
# @rbs @reader: Thread
|
|
10
|
+
# @rbs @pid: Integer
|
|
11
|
+
# @rbs @stdin: IO
|
|
12
|
+
# @rbs @stdout: IO
|
|
13
|
+
# @rbs @waiter: Process::Waiter
|
|
14
|
+
# @rbs @err_r: IO?
|
|
15
|
+
# @rbs @err_reader: Thread?
|
|
16
|
+
# @rbs @stderr: String?
|
|
17
|
+
|
|
18
|
+
# @dynamic pid
|
|
19
|
+
attr_reader :pid #: Integer
|
|
20
|
+
|
|
21
|
+
# @dynamic connection
|
|
22
|
+
attr_reader :connection #: ACP::ClientConnection
|
|
23
|
+
|
|
24
|
+
# Child stderr captured when spawn was given stderr: :capture, else nil.
|
|
25
|
+
#
|
|
26
|
+
# @dynamic stderr
|
|
27
|
+
attr_reader :stderr #: String?
|
|
28
|
+
|
|
29
|
+
# Starts a command with args, env and cwd, wires its stdio to a new
|
|
30
|
+
# ACP::ClientConnection and starts it. With a block, yields the connection
|
|
31
|
+
# and this process, then terminates the child when the block exits; without
|
|
32
|
+
# one, returns the process and terminate is the caller's job.
|
|
33
|
+
#
|
|
34
|
+
# @rbs command: String
|
|
35
|
+
# @rbs *args: String
|
|
36
|
+
# @rbs env: Hash[String, String]?
|
|
37
|
+
# @rbs cwd: String?
|
|
38
|
+
# @rbs stderr: (:inherit | :capture)
|
|
39
|
+
# @rbs permission: ACP::ClientConnection::_PermissionHandler
|
|
40
|
+
# @rbs updates: ACP::ClientConnection::_UpdateHandler
|
|
41
|
+
# @rbs &block: ? (ACP::ClientConnection, ACP::AgentProcess) -> untyped
|
|
42
|
+
# @rbs return: (untyped | ACP::AgentProcess)
|
|
43
|
+
def self.spawn(
|
|
44
|
+
command,
|
|
45
|
+
*args,
|
|
46
|
+
permission:,
|
|
47
|
+
env: nil,
|
|
48
|
+
cwd: nil,
|
|
49
|
+
stderr: :inherit,
|
|
50
|
+
updates: ACP::ClientConnection::IGNORE,
|
|
51
|
+
&block
|
|
52
|
+
)
|
|
53
|
+
err_r, err_w = stderr == :capture ? IO.pipe : nil
|
|
54
|
+
opts = {} #: Hash[Symbol, untyped]
|
|
55
|
+
opts[:chdir] = cwd if cwd
|
|
56
|
+
opts[:err] = err_w if err_w
|
|
57
|
+
spawner = Open3.method(:popen2) #: _Spawner
|
|
58
|
+
stdin, stdout, waiter = spawner.call(*(env ? [env, command, *args] : [command, *args]), **opts)
|
|
59
|
+
err_w&.close
|
|
60
|
+
connection = ACP::ClientConnection.new(
|
|
61
|
+
transport: ACP::Transport::Stdio.new(input: stdout, output: stdin),
|
|
62
|
+
permission:,
|
|
63
|
+
updates:
|
|
64
|
+
)
|
|
65
|
+
process = new(connection:, reader: connection.start, stdin:, stdout:, waiter:, err_r:)
|
|
66
|
+
return process unless block
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
yield connection, process
|
|
70
|
+
ensure
|
|
71
|
+
process.terminate
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Closes stdin, waits for the child to exit — sending TERM and then KILL if
|
|
76
|
+
# it will not — and joins the transport reader before closing the pipe it
|
|
77
|
+
# reads.
|
|
78
|
+
#
|
|
79
|
+
# @rbs return: void
|
|
80
|
+
def terminate
|
|
81
|
+
@stdin.close unless @stdin.closed?
|
|
82
|
+
@waiter.join(1) or signal('TERM')
|
|
83
|
+
@waiter.join(2) or signal('KILL')
|
|
84
|
+
@waiter.join(2)
|
|
85
|
+
@reader.join(1)
|
|
86
|
+
@stdout.close unless @stdout.closed?
|
|
87
|
+
@err_reader&.join(1)
|
|
88
|
+
@err_r&.close unless @err_r&.closed?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# @rbs connection: ACP::ClientConnection
|
|
94
|
+
# @rbs reader: Thread
|
|
95
|
+
# @rbs stdin: IO
|
|
96
|
+
# @rbs stdout: IO
|
|
97
|
+
# @rbs waiter: Process::Waiter
|
|
98
|
+
# @rbs err_r: IO?
|
|
99
|
+
# @rbs return: void
|
|
100
|
+
def initialize(connection:, reader:, stdin:, stdout:, waiter:, err_r:)
|
|
101
|
+
@connection = connection
|
|
102
|
+
@reader = reader
|
|
103
|
+
@pid = waiter.pid
|
|
104
|
+
@stdin = stdin
|
|
105
|
+
@stdout = stdout
|
|
106
|
+
@waiter = waiter
|
|
107
|
+
@err_r = err_r
|
|
108
|
+
@stderr = err_r ? +'' : nil
|
|
109
|
+
@err_reader = Thread.new { err_r.each_line { |line| @stderr&.concat(line) } } if err_r
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @rbs sig: String
|
|
113
|
+
# @rbs return: void
|
|
114
|
+
def signal(sig)
|
|
115
|
+
Process.kill(sig, @pid)
|
|
116
|
+
rescue Errno::ESRCH
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -33,33 +33,33 @@ class ACP::ClientConnection
|
|
|
33
33
|
# sent by connect.
|
|
34
34
|
#
|
|
35
35
|
# @rbs request: ACP::Types::InitializeRequest
|
|
36
|
-
# @rbs return: ACP::Types::InitializeResponse | ACP::
|
|
36
|
+
# @rbs return: ACP::Types::InitializeResponse | ACP::RequestError
|
|
37
37
|
def connect(request)
|
|
38
38
|
parse(ACP::Types::InitializeResponse, @transport.request('initialize', request.to_h))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# @rbs request: ACP::Types::NewSessionRequest
|
|
42
|
-
# @rbs return: ACP::Types::NewSessionResponse | ACP::
|
|
42
|
+
# @rbs return: ACP::Types::NewSessionResponse | ACP::RequestError
|
|
43
43
|
def session_new(request)
|
|
44
44
|
parse(ACP::Types::NewSessionResponse, @transport.request('session/new', request.to_h))
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# @rbs request: ACP::Types::PromptRequest
|
|
48
48
|
# @rbs &block: (ACP::Types::SessionUpdate::t) -> void
|
|
49
|
-
# @rbs return: ACP::Types::PromptResponse | ACP::
|
|
49
|
+
# @rbs return: ACP::Types::PromptResponse | ACP::RequestError
|
|
50
50
|
def session_prompt(request, &)
|
|
51
51
|
parse(ACP::Types::PromptResponse, stream(request.session_id, 'session/prompt', request.to_h, &))
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# @rbs request: ACP::Types::LoadSessionRequest
|
|
55
55
|
# @rbs &block: (ACP::Types::SessionUpdate::t) -> void
|
|
56
|
-
# @rbs return: ACP::Types::LoadSessionResponse | ACP::
|
|
56
|
+
# @rbs return: ACP::Types::LoadSessionResponse | ACP::RequestError
|
|
57
57
|
def session_load(request, &)
|
|
58
58
|
parse(ACP::Types::LoadSessionResponse, stream(request.session_id, 'session/load', request.to_h, &))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# @rbs request: ACP::Types::ListSessionsRequest
|
|
62
|
-
# @rbs return: ACP::Types::ListSessionsResponse | ACP::
|
|
62
|
+
# @rbs return: ACP::Types::ListSessionsResponse | ACP::RequestError
|
|
63
63
|
def session_list(request)
|
|
64
64
|
parse(ACP::Types::ListSessionsResponse, @transport.request('session/list', request.to_h))
|
|
65
65
|
end
|
|
@@ -73,10 +73,12 @@ class ACP::ClientConnection
|
|
|
73
73
|
private
|
|
74
74
|
|
|
75
75
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
76
|
-
# @rbs result: ACP::
|
|
76
|
+
# @rbs result: (Hash[String, untyped] | ACP::RequestError)
|
|
77
77
|
# @rbs return: untyped
|
|
78
78
|
def parse(type, result)
|
|
79
|
-
result
|
|
79
|
+
return result if result.is_a?(ACP::RequestError)
|
|
80
|
+
|
|
81
|
+
type.from_h(result)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
# The request waits on its own thread so the caller's thread can yield each
|
|
@@ -87,7 +89,7 @@ class ACP::ClientConnection
|
|
|
87
89
|
# @rbs method: String
|
|
88
90
|
# @rbs params: Hash[String, untyped]
|
|
89
91
|
# @rbs &block: (ACP::Types::SessionUpdate::t) -> void
|
|
90
|
-
# @rbs return: ACP::
|
|
92
|
+
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
91
93
|
def stream(session_id, method, params)
|
|
92
94
|
queue = Thread::Queue.new
|
|
93
95
|
@lock.synchronize { @streams[session_id] = queue }
|
|
@@ -114,16 +116,12 @@ class ACP::ClientConnection
|
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
# @rbs params: untyped
|
|
117
|
-
# @rbs return: ACP::
|
|
119
|
+
# @rbs return: (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
118
120
|
def request_permission(params)
|
|
119
121
|
request = ACP::Types::RequestPermissionRequest.from_h(params)
|
|
120
122
|
rescue KeyError, TypeError, NoMethodError
|
|
121
|
-
ACP::
|
|
123
|
+
ACP::RequestError.invalid_params
|
|
122
124
|
else
|
|
123
|
-
|
|
124
|
-
case response
|
|
125
|
-
when ACP::Transport::ResponseError then ACP::Transport::Result.error(response)
|
|
126
|
-
else ACP::Transport::Result.ok(response.to_h)
|
|
127
|
-
end
|
|
125
|
+
@permission.call(request)
|
|
128
126
|
end
|
|
129
127
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The schema's JSON-RPC Error object, named after the official SDKs'
|
|
4
|
+
# RequestError: built by handlers and local refusals, and decoded from a
|
|
5
|
+
# peer's error replies by the transport.
|
|
6
|
+
class ACP::RequestError
|
|
7
|
+
PARSE_ERROR = -32_700 #: Integer
|
|
8
|
+
INVALID_REQUEST = -32_600 #: Integer
|
|
9
|
+
METHOD_NOT_FOUND = -32_601 #: Integer
|
|
10
|
+
INVALID_PARAMS = -32_602 #: Integer
|
|
11
|
+
INTERNAL_ERROR = -32_603 #: Integer
|
|
12
|
+
REQUEST_CANCELLED = -32_800 #: Integer
|
|
13
|
+
AUTH_REQUIRED = -32_000 #: Integer
|
|
14
|
+
RESOURCE_NOT_FOUND = -32_002 #: Integer
|
|
15
|
+
|
|
16
|
+
# @rbs code: Integer
|
|
17
|
+
# @rbs message: String
|
|
18
|
+
# @rbs data: untyped
|
|
19
|
+
# @rbs return: void
|
|
20
|
+
def initialize(code:, message:, data: nil)
|
|
21
|
+
@code = code
|
|
22
|
+
@message = message
|
|
23
|
+
@data = data
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @dynamic code
|
|
28
|
+
attr_reader :code #: Integer
|
|
29
|
+
|
|
30
|
+
# @dynamic message
|
|
31
|
+
attr_reader :message #: String
|
|
32
|
+
|
|
33
|
+
# @dynamic data
|
|
34
|
+
attr_reader :data #: untyped
|
|
35
|
+
|
|
36
|
+
# @rbs return: Hash[String, untyped]
|
|
37
|
+
def to_h
|
|
38
|
+
{ 'code' => code, 'message' => message, 'data' => data }.compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs return: ACP::RequestError
|
|
42
|
+
def self.parse_error
|
|
43
|
+
new(code: PARSE_ERROR, message: 'Parse error')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @rbs return: ACP::RequestError
|
|
47
|
+
def self.invalid_request
|
|
48
|
+
new(code: INVALID_REQUEST, message: 'Invalid request')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs return: ACP::RequestError
|
|
52
|
+
def self.method_not_found
|
|
53
|
+
new(code: METHOD_NOT_FOUND, message: 'Method not found')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs return: ACP::RequestError
|
|
57
|
+
def self.invalid_params
|
|
58
|
+
new(code: INVALID_PARAMS, message: 'Invalid params')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @rbs return: ACP::RequestError
|
|
62
|
+
def self.internal_error
|
|
63
|
+
new(code: INTERNAL_ERROR, message: 'Internal error')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @rbs return: ACP::RequestError
|
|
67
|
+
def self.request_cancelled
|
|
68
|
+
new(code: REQUEST_CANCELLED, message: 'Request cancelled')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @rbs return: ACP::RequestError
|
|
72
|
+
def self.auth_required
|
|
73
|
+
new(code: AUTH_REQUIRED, message: 'Authentication required')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @rbs uri: String?
|
|
77
|
+
# @rbs return: ACP::RequestError
|
|
78
|
+
def self.resource_not_found(uri = nil)
|
|
79
|
+
new(code: RESOURCE_NOT_FOUND, message: 'Resource not found', data: uri && { uri: uri })
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/acp/transport/reply.rb
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# A request handler's
|
|
4
|
-
#
|
|
3
|
+
# A request handler's outcome plus a callback the transport runs once that
|
|
4
|
+
# outcome is on the wire, for messages the peer must only see after the reply.
|
|
5
5
|
class ACP::Transport::Reply
|
|
6
6
|
# @dynamic result
|
|
7
|
-
attr_reader :result #: ACP::Transport::
|
|
7
|
+
attr_reader :result #: ACP::Transport::Stdio::_ToH
|
|
8
8
|
|
|
9
9
|
# @dynamic after
|
|
10
10
|
attr_reader :after #: ^() -> void
|
|
11
11
|
|
|
12
|
-
# @rbs result: ACP::Transport::
|
|
12
|
+
# @rbs result: ACP::Transport::Stdio::_ToH
|
|
13
13
|
# @rbs after: ^() -> void
|
|
14
14
|
# @rbs return: void
|
|
15
15
|
def initialize(result, after:)
|
data/lib/acp/transport/stdio.rb
CHANGED
|
@@ -16,11 +16,9 @@ class ACP::Transport::Stdio
|
|
|
16
16
|
# @rbs @next_id: Integer
|
|
17
17
|
# @rbs @closed: bool
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
METHOD_NOT_FOUND = ACP::Transport::ResponseError.new(code: -32_601, message: 'Method not found') #: ACP::Transport::ResponseError
|
|
23
|
-
CONNECTION_CLOSED = ACP::Transport::ResponseError.new(code: INTERNAL_ERROR, message: 'Connection closed') #: ACP::Transport::ResponseError
|
|
19
|
+
CONNECTION_CLOSED = ACP::RequestError.new(
|
|
20
|
+
code: ACP::RequestError::INTERNAL_ERROR, message: 'Connection closed'
|
|
21
|
+
) #: ACP::RequestError
|
|
24
22
|
|
|
25
23
|
# @rbs input: _Reader
|
|
26
24
|
# @rbs output: _Writer
|
|
@@ -52,14 +50,14 @@ class ACP::Transport::Stdio
|
|
|
52
50
|
|
|
53
51
|
# @rbs method: String
|
|
54
52
|
# @rbs params: untyped
|
|
55
|
-
# @rbs return: ACP::
|
|
53
|
+
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
56
54
|
def request(method, params = nil)
|
|
57
55
|
queue = Thread::Queue.new
|
|
58
56
|
id = register(queue)
|
|
59
|
-
return
|
|
57
|
+
return CONNECTION_CLOSED unless id
|
|
60
58
|
|
|
61
59
|
write({ 'jsonrpc' => '2.0', 'id' => id, 'method' => method, 'params' => params }.compact)
|
|
62
|
-
queue.pop ||
|
|
60
|
+
queue.pop || CONNECTION_CLOSED
|
|
63
61
|
end
|
|
64
62
|
|
|
65
63
|
# @rbs method: String
|
|
@@ -78,7 +76,7 @@ class ACP::Transport::Stdio
|
|
|
78
76
|
def receive(line, requests, notifications)
|
|
79
77
|
message = parse(line)
|
|
80
78
|
case message
|
|
81
|
-
when ACP::
|
|
79
|
+
when ACP::RequestError then reply(nil, message)
|
|
82
80
|
else route(message, requests, notifications)
|
|
83
81
|
end
|
|
84
82
|
end
|
|
@@ -98,25 +96,24 @@ class ACP::Transport::Stdio
|
|
|
98
96
|
handler = notifications[method]
|
|
99
97
|
quietly { handler.call(message['params']) } if handler
|
|
100
98
|
elsif message.key?('result')
|
|
101
|
-
settle(message['id'],
|
|
99
|
+
settle(message['id'], message['result'])
|
|
102
100
|
elsif error.is_a?(Hash) && error['code'].is_a?(Integer) && error['message'].is_a?(String)
|
|
103
101
|
code, text, data = error.values_at('code', 'message', 'data')
|
|
104
|
-
|
|
105
|
-
settle(message['id'], ACP::Transport::Result.error(response_error))
|
|
102
|
+
settle(message['id'], ACP::RequestError.new(code:, message: text, data:))
|
|
106
103
|
else
|
|
107
|
-
reply(nil, ACP::
|
|
104
|
+
reply(nil, ACP::RequestError.invalid_request)
|
|
108
105
|
end
|
|
109
106
|
end
|
|
110
107
|
|
|
111
108
|
# @rbs line: String
|
|
112
|
-
# @rbs return: Hash[String, untyped] | ACP::
|
|
109
|
+
# @rbs return: Hash[String, untyped] | ACP::RequestError
|
|
113
110
|
def parse(line)
|
|
114
|
-
return
|
|
111
|
+
return ACP::RequestError.parse_error unless line.valid_encoding?
|
|
115
112
|
|
|
116
113
|
message = JSON.parse(line)
|
|
117
|
-
message.is_a?(Hash) ? message :
|
|
114
|
+
message.is_a?(Hash) ? message : ACP::RequestError.invalid_request
|
|
118
115
|
rescue JSON::ParserError
|
|
119
|
-
|
|
116
|
+
ACP::RequestError.parse_error
|
|
120
117
|
end
|
|
121
118
|
|
|
122
119
|
# @rbs handler: _Handler?
|
|
@@ -125,7 +122,7 @@ class ACP::Transport::Stdio
|
|
|
125
122
|
# @rbs return: void
|
|
126
123
|
def serve(handler, id, params)
|
|
127
124
|
Thread.new do
|
|
128
|
-
outcome = handler ? invoke(handler, params) : ACP::
|
|
125
|
+
outcome = handler ? invoke(handler, params) : ACP::RequestError.method_not_found
|
|
129
126
|
case outcome
|
|
130
127
|
when ACP::Transport::Reply
|
|
131
128
|
reply(id, outcome.result)
|
|
@@ -152,19 +149,25 @@ class ACP::Transport::Stdio
|
|
|
152
149
|
#
|
|
153
150
|
# @rbs handler: _Handler
|
|
154
151
|
# @rbs params: untyped
|
|
155
|
-
# @rbs return: ACP::
|
|
152
|
+
# @rbs return: (_ToH | ACP::RequestError | ACP::Transport::Reply)
|
|
156
153
|
def invoke(handler, params)
|
|
157
154
|
handler.call(params)
|
|
158
155
|
rescue StandardError => e
|
|
159
|
-
ACP::
|
|
156
|
+
ACP::RequestError.new(code: ACP::RequestError::INTERNAL_ERROR, message: e.message)
|
|
160
157
|
end
|
|
161
158
|
|
|
159
|
+
# The success arm is whatever the handler returned, which the transport only
|
|
160
|
+
# knows serializes with to_h; RequestError picks the error arm of the reply.
|
|
161
|
+
#
|
|
162
162
|
# @rbs id: untyped
|
|
163
|
-
# @rbs
|
|
163
|
+
# @rbs outcome: (_ToH | ACP::RequestError)
|
|
164
164
|
# @rbs return: void
|
|
165
|
-
def reply(id,
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
def reply(id, outcome)
|
|
166
|
+
payload = case outcome
|
|
167
|
+
when ACP::RequestError then { 'error' => outcome.to_h }
|
|
168
|
+
else { 'result' => outcome.to_h }
|
|
169
|
+
end
|
|
170
|
+
write({ 'jsonrpc' => '2.0', 'id' => id }.merge(payload))
|
|
168
171
|
end
|
|
169
172
|
|
|
170
173
|
# @rbs queue: Thread::Queue
|
|
@@ -180,7 +183,7 @@ class ACP::Transport::Stdio
|
|
|
180
183
|
end
|
|
181
184
|
|
|
182
185
|
# @rbs id: untyped
|
|
183
|
-
# @rbs result: ACP::
|
|
186
|
+
# @rbs result: (Hash[String, untyped] | ACP::RequestError)
|
|
184
187
|
# @rbs return: void
|
|
185
188
|
def settle(id, result)
|
|
186
189
|
@lock.synchronize { @pending.delete(id) }&.push(result)
|
data/lib/acp/types.rb
ADDED
data/lib/acp/version.rb
CHANGED
|
@@ -24,6 +24,55 @@ class ACP::AgentConnection::Client
|
|
|
24
24
|
# closes.
|
|
25
25
|
#
|
|
26
26
|
# @rbs request: ACP::Types::RequestPermissionRequest
|
|
27
|
-
# @rbs return: ACP::Types::RequestPermissionResponse | ACP::
|
|
28
|
-
def request_permission: (ACP::Types::RequestPermissionRequest request) -> (ACP::Types::RequestPermissionResponse | ACP::
|
|
27
|
+
# @rbs return: ACP::Types::RequestPermissionResponse | ACP::RequestError
|
|
28
|
+
def request_permission: (ACP::Types::RequestPermissionRequest request) -> (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
29
|
+
|
|
30
|
+
# @rbs request: ACP::Types::ReadTextFileRequest
|
|
31
|
+
# @rbs return: ACP::Types::ReadTextFileResponse | ACP::RequestError
|
|
32
|
+
def read_text_file: (ACP::Types::ReadTextFileRequest request) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError)
|
|
33
|
+
|
|
34
|
+
# @rbs request: ACP::Types::WriteTextFileRequest
|
|
35
|
+
# @rbs return: ACP::Types::WriteTextFileResponse | ACP::RequestError
|
|
36
|
+
def write_text_file: (ACP::Types::WriteTextFileRequest request) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError)
|
|
37
|
+
|
|
38
|
+
# @rbs request: ACP::Types::CreateTerminalRequest
|
|
39
|
+
# @rbs return: ACP::Types::CreateTerminalResponse | ACP::RequestError
|
|
40
|
+
def create_terminal: (ACP::Types::CreateTerminalRequest request) -> (ACP::Types::CreateTerminalResponse | ACP::RequestError)
|
|
41
|
+
|
|
42
|
+
# @rbs request: ACP::Types::TerminalOutputRequest
|
|
43
|
+
# @rbs return: ACP::Types::TerminalOutputResponse | ACP::RequestError
|
|
44
|
+
def terminal_output: (ACP::Types::TerminalOutputRequest request) -> (ACP::Types::TerminalOutputResponse | ACP::RequestError)
|
|
45
|
+
|
|
46
|
+
# @rbs request: ACP::Types::WaitForTerminalExitRequest
|
|
47
|
+
# @rbs return: ACP::Types::WaitForTerminalExitResponse | ACP::RequestError
|
|
48
|
+
def wait_for_terminal_exit: (ACP::Types::WaitForTerminalExitRequest request) -> (ACP::Types::WaitForTerminalExitResponse | ACP::RequestError)
|
|
49
|
+
|
|
50
|
+
# @rbs request: ACP::Types::KillTerminalRequest
|
|
51
|
+
# @rbs return: ACP::Types::KillTerminalResponse | ACP::RequestError
|
|
52
|
+
def kill_terminal: (ACP::Types::KillTerminalRequest request) -> (ACP::Types::KillTerminalResponse | ACP::RequestError)
|
|
53
|
+
|
|
54
|
+
# @rbs request: ACP::Types::ReleaseTerminalRequest
|
|
55
|
+
# @rbs return: ACP::Types::ReleaseTerminalResponse | ACP::RequestError
|
|
56
|
+
def release_terminal: (ACP::Types::ReleaseTerminalRequest request) -> (ACP::Types::ReleaseTerminalResponse | ACP::RequestError)
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# @rbs rpc_method: String
|
|
61
|
+
# @rbs type: ACP::AgentConnection::_Parser
|
|
62
|
+
# @rbs request: ACP::AgentConnection::_Response
|
|
63
|
+
# @rbs return: untyped
|
|
64
|
+
def call: (String rpc_method, ACP::AgentConnection::_Parser type, ACP::AgentConnection::_Response request) -> untyped
|
|
65
|
+
|
|
66
|
+
# @rbs rpc_method: String
|
|
67
|
+
# @rbs type: ACP::AgentConnection::_Parser
|
|
68
|
+
# @rbs request: ACP::AgentConnection::_Response
|
|
69
|
+
# @rbs return: untyped
|
|
70
|
+
def terminal_call: (String rpc_method, ACP::AgentConnection::_Parser type, ACP::AgentConnection::_Response request) -> untyped
|
|
71
|
+
|
|
72
|
+
# Same method-not-found code a peer would send, but with a message that says
|
|
73
|
+
# the refusal was local, so logs can tell the two cases apart.
|
|
74
|
+
#
|
|
75
|
+
# @rbs capability: String
|
|
76
|
+
# @rbs return: ACP::RequestError
|
|
77
|
+
def unadvertised: (String capability) -> ACP::RequestError
|
|
29
78
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/authenticate.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::Authenticate
|
|
4
|
+
# @rbs return: String
|
|
5
|
+
def rpc_method: () -> String
|
|
6
|
+
|
|
7
|
+
# @rbs return: Symbol
|
|
8
|
+
def agent_method: () -> Symbol
|
|
9
|
+
|
|
10
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
11
|
+
# @rbs return: bool
|
|
12
|
+
def advertised?: (ACP::Types::InitializeResponse initialize_response) -> bool
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/close_session.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::CloseSession
|
|
4
|
+
# @rbs return: String
|
|
5
|
+
def rpc_method: () -> String
|
|
6
|
+
|
|
7
|
+
# @rbs return: Symbol
|
|
8
|
+
def agent_method: () -> Symbol
|
|
9
|
+
|
|
10
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
11
|
+
# @rbs return: bool
|
|
12
|
+
def advertised?: (ACP::Types::InitializeResponse initialize_response) -> bool
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/delete_session.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::DeleteSession
|
|
4
|
+
# @rbs return: String
|
|
5
|
+
def rpc_method: () -> String
|
|
6
|
+
|
|
7
|
+
# @rbs return: Symbol
|
|
8
|
+
def agent_method: () -> Symbol
|
|
9
|
+
|
|
10
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
11
|
+
# @rbs return: bool
|
|
12
|
+
def advertised?: (ACP::Types::InitializeResponse initialize_response) -> bool
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/list_sessions.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::ListSessions
|
|
4
|
+
# @rbs return: String
|
|
5
|
+
def rpc_method: () -> String
|
|
6
|
+
|
|
7
|
+
# @rbs return: Symbol
|
|
8
|
+
def agent_method: () -> Symbol
|
|
9
|
+
|
|
10
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
11
|
+
# @rbs return: bool
|
|
12
|
+
def advertised?: (ACP::Types::InitializeResponse initialize_response) -> bool
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/load_session.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::LoadSession
|
|
4
|
+
# @rbs return: String
|
|
5
|
+
def rpc_method: () -> String
|
|
6
|
+
|
|
7
|
+
# @rbs return: Symbol
|
|
8
|
+
def agent_method: () -> Symbol
|
|
9
|
+
|
|
10
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
11
|
+
# @rbs return: bool
|
|
12
|
+
def advertised?: (ACP::Types::InitializeResponse initialize_response) -> bool
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/resume_session.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::ResumeSession
|
|
4
|
+
# @rbs return: String
|
|
5
|
+
def rpc_method: () -> String
|
|
6
|
+
|
|
7
|
+
# @rbs return: Symbol
|
|
8
|
+
def agent_method: () -> Symbol
|
|
9
|
+
|
|
10
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
11
|
+
# @rbs return: bool
|
|
12
|
+
def advertised?: (ACP::Types::InitializeResponse initialize_response) -> bool
|
|
13
|
+
end
|
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
# Generated from lib/acp/agent_connection/optional_method.rb with RBS::Inline
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
@capability: ^(ACP::Types::AgentCapabilities) -> boolish
|
|
5
|
-
|
|
6
|
-
# @dynamic rpc_method
|
|
7
|
-
attr_reader rpc_method: String
|
|
8
|
-
|
|
9
|
-
# @dynamic agent_method
|
|
10
|
-
attr_reader agent_method: Symbol
|
|
11
|
-
|
|
12
|
-
# @rbs rpc_method: String
|
|
13
|
-
# @rbs agent_method: Symbol
|
|
14
|
-
# @rbs &capability: (ACP::Types::AgentCapabilities) -> boolish
|
|
15
|
-
# @rbs return: void
|
|
16
|
-
def initialize: (rpc_method: String, agent_method: Symbol) { (ACP::Types::AgentCapabilities) -> boolish } -> void
|
|
17
|
-
|
|
18
|
-
# @rbs capabilities: ACP::Types::AgentCapabilities
|
|
19
|
-
# @rbs return: bool
|
|
20
|
-
def advertised?: (ACP::Types::AgentCapabilities capabilities) -> bool
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod
|
|
21
4
|
end
|