acp-sdk 0.5.0 → 0.6.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 +8 -0
- data/README.md +4 -0
- data/lib/acp/agent_connection/client.rb +20 -0
- data/lib/acp/agent_connection.rb +14 -1
- data/lib/acp/client_connection.rb +96 -4
- data/lib/acp/extensions.rb +15 -0
- data/lib/acp/version.rb +1 -1
- data/sig/generated/acp/agent_connection/client.rbs +14 -0
- data/sig/generated/acp/agent_connection.rbs +7 -1
- data/sig/generated/acp/client_connection.rbs +44 -1
- data/sig/generated/acp/extensions.rbs +9 -0
- data/sig/manual/acp/agent_connection.rbs +2 -0
- data/sig/manual/acp/extensions.rbs +6 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 367678f3b99c82b2f768879be0308249d9b32ce8ce94c3d4e6de6c12c4a1c043
|
|
4
|
+
data.tar.gz: 071e457b8e00fa19be760fff65cfd69998102e54b4b3d47f9b3cc0d80c359fc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 706206bd29062ef6ecf1856b9e9671113e02bb6eb6ac837c2815bfcdbbff4bc07ac8d2c6394ec6f7b33c37a3832a9e0c4bb5c4e5c4699c1cdc129e875f693605
|
|
7
|
+
data.tar.gz: 5a73a945d099dedcf5837aa1c897b1dca0313a49d131337756b26c28731ddedb14b96c21722c7d6ecff3a21e6bed811e036551724c12bebcdf53f88a56121f8e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0](https://github.com/bottrall/acp-sdk/compare/v0.5.0...v0.6.0) (2026-10-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* answer pending permission requests with cancelled after session_cancel ([#70](https://github.com/bottrall/acp-sdk/issues/70)) ([06a218b](https://github.com/bottrall/acp-sdk/commit/06a218b6262ff409e301d08a43330af5b60837e0))
|
|
9
|
+
* extension methods on both connections ([#72](https://github.com/bottrall/acp-sdk/issues/72)) ([d1faa1a](https://github.com/bottrall/acp-sdk/commit/d1faa1a738019e73a4dbad5b59f480795f8dd4bf))
|
|
10
|
+
|
|
3
11
|
## [0.5.0](https://github.com/bottrall/acp-sdk/compare/v0.4.0...v0.5.0) (2026-10-04)
|
|
4
12
|
|
|
5
13
|
|
data/README.md
CHANGED
|
@@ -57,6 +57,8 @@ The agent's contract:
|
|
|
57
57
|
- `client.create_terminal`, `client.terminal_output`, `client.wait_for_terminal_exit`, `client.kill_terminal` and `client.release_terminal` take the matching `ACP::Types` request (`CreateTerminalRequest`, `TerminalOutputRequest`, `WaitForTerminalExitRequest`, `KillTerminalRequest`, `ReleaseTerminalRequest`) and return its response type or an `ACP::RequestError`. Unless `client.capabilities` advertises `terminal`, they return `-32601` without sending anything to the client. The agent must release every terminal it creates.
|
|
58
58
|
- `client.create_elicitation` takes an `ACP::Types::CreateElicitationRequest` and returns an `ACP::Types::CreateElicitationResponse` or an `ACP::RequestError`. Unless `client.capabilities` advertises the `elicitation.form` or `elicitation.url` mode the request asks for, it returns `-32601` without sending anything to the client. A response action the SDK does not know comes through as the raw hash the client sent. `client.complete_elicitation` takes an `ACP::Types::CompleteElicitationNotification` and sends nothing, returning `-32601`, unless the client advertises `elicitation.url`, since only URL-mode elicitations are completed.
|
|
59
59
|
|
|
60
|
+
Extensions are `_`-prefixed methods outside the spec. `AgentConnection` takes `extension_requests` and `extension_notifications` hashes keyed by the raw wire name, and `client.ext_request` and `client.ext_notify` send them to the client; a handler name without the `_` prefix raises `ArgumentError`, and an extension request with no handler is answered with `-32601`.
|
|
61
|
+
|
|
60
62
|
[`examples/echo_agent.rb`](https://github.com/bottrall/acp-sdk/blob/main/examples/echo_agent.rb) is a complete agent that streams updates, asks permission, handles cancellation, reads and writes files and runs commands through the client (`/read <path>`, `/write <path> <text>` and `/run <command> [args]`, each advertised as a slash command only when the client supports it), and supports `session/load`, `session/list`, `session/resume`, `session/close` and `session/delete`.
|
|
61
63
|
|
|
62
64
|
## Driving an agent
|
|
@@ -104,6 +106,8 @@ connection = ACP::ClientConnection.new(
|
|
|
104
106
|
- `permission` answers `session/request_permission` with an `ACP::Types::RequestPermissionResponse` or an `ACP::RequestError`. It runs on its own thread, so it may block while the user decides, and `session_cancel` can be sent meanwhile.
|
|
105
107
|
- `read_text_file` and `write_text_file` answer the agent's `fs/read_text_file` and `fs/write_text_file` with an `ACP::Types::ReadTextFileResponse`, an `ACP::Types::WriteTextFileResponse` or an `ACP::RequestError`. They are routed by the `fs` capabilities `connect` sends: a method the client does not advertise is answered with `-32601` without reaching the handler, and `connect` raises `ArgumentError` — before sending `initialize` — when the request advertises a capability that has no handler.
|
|
106
108
|
|
|
109
|
+
Extensions work the same way in both directions: `extension_requests` and `extension_notifications` are hashes keyed by the raw `_`-prefixed wire name, `ext_request` sends one to the agent and returns its result as-is, and `ext_notify` sends an extension notification. A handler name without the `_` prefix raises `ArgumentError`, and an extension request nothing answers gets `-32601`.
|
|
110
|
+
|
|
107
111
|
## Contributing
|
|
108
112
|
|
|
109
113
|
Bug reports and pull requests are welcome on [GitHub](https://github.com/bottrall/acp-sdk). See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and workflow. Everyone interacting in the project is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
|
@@ -105,6 +105,26 @@ class ACP::AgentConnection::Client
|
|
|
105
105
|
@peer.notify('elicitation/complete', notification.to_h)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
# Sends an extension request, keyed by its raw `_`-prefixed wire name, and
|
|
109
|
+
# returns the client's reply as-is: extension methods have no schema to
|
|
110
|
+
# parse the result into.
|
|
111
|
+
#
|
|
112
|
+
# @rbs method: String
|
|
113
|
+
# @rbs params: untyped
|
|
114
|
+
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
115
|
+
def ext_request(method, params = nil)
|
|
116
|
+
ACP::Extensions.validate_name(method)
|
|
117
|
+
@peer.request(method, params)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @rbs method: String
|
|
121
|
+
# @rbs params: untyped
|
|
122
|
+
# @rbs return: void
|
|
123
|
+
def ext_notify(method, params = nil)
|
|
124
|
+
ACP::Extensions.validate_name(method)
|
|
125
|
+
@peer.notify(method, params)
|
|
126
|
+
end
|
|
127
|
+
|
|
108
128
|
private
|
|
109
129
|
|
|
110
130
|
# @rbs rpc_method: String
|
data/lib/acp/agent_connection.rb
CHANGED
|
@@ -6,6 +6,8 @@ class ACP::AgentConnection
|
|
|
6
6
|
# @rbs @agent_info: ACP::Types::Implementation?
|
|
7
7
|
# @rbs @auth_methods: Array[ACP::Types::AuthMethod::t]
|
|
8
8
|
# @rbs @factory: ^(ACP::AgentConnection::Client) -> ACP::AgentConnection::_Agent
|
|
9
|
+
# @rbs @extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
10
|
+
# @rbs @extension_notifications: Hash[String, ^(untyped) -> void]
|
|
9
11
|
# @rbs @logger: ACP::Transport::_Logger
|
|
10
12
|
|
|
11
13
|
PROTOCOL_VERSION = 1 #: Integer
|
|
@@ -30,6 +32,8 @@ class ACP::AgentConnection
|
|
|
30
32
|
# @rbs capabilities: ACP::Types::AgentCapabilities
|
|
31
33
|
# @rbs agent_info: ACP::Types::Implementation?
|
|
32
34
|
# @rbs auth_methods: Array[ACP::Types::AuthMethod::t]
|
|
35
|
+
# @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
36
|
+
# @rbs extension_notifications: Hash[String, ^(untyped) -> void]
|
|
33
37
|
# @rbs logger: ACP::Transport::_Logger
|
|
34
38
|
# @rbs &factory: (ACP::AgentConnection::Client) -> ACP::AgentConnection::_Agent
|
|
35
39
|
# @rbs return: void
|
|
@@ -38,6 +42,8 @@ class ACP::AgentConnection
|
|
|
38
42
|
capabilities:,
|
|
39
43
|
agent_info: nil,
|
|
40
44
|
auth_methods: [],
|
|
45
|
+
extension_requests: {},
|
|
46
|
+
extension_notifications: {},
|
|
41
47
|
logger: ACP::Transport::StderrLogger.new,
|
|
42
48
|
&factory
|
|
43
49
|
)
|
|
@@ -46,6 +52,10 @@ class ACP::AgentConnection
|
|
|
46
52
|
@capabilities = capabilities
|
|
47
53
|
@agent_info = agent_info
|
|
48
54
|
@auth_methods = auth_methods
|
|
55
|
+
@extension_requests = extension_requests
|
|
56
|
+
@extension_notifications = extension_notifications
|
|
57
|
+
extension_requests.each_key { |name| ACP::Extensions.validate_name(name) }
|
|
58
|
+
extension_notifications.each_key { |name| ACP::Extensions.validate_name(name) }
|
|
49
59
|
@factory = factory
|
|
50
60
|
end
|
|
51
61
|
|
|
@@ -61,7 +71,10 @@ class ACP::AgentConnection
|
|
|
61
71
|
raise ArgumentError, "initialize advertises methods the agent lacks: #{missing.join(', ')}" unless missing.empty?
|
|
62
72
|
|
|
63
73
|
unrouted = unadvertised.map(&:rpc_method) + PER_SESSION.reject { |_, name| agent.respond_to?(name) }.keys
|
|
64
|
-
@transport.start(
|
|
74
|
+
@transport.start(
|
|
75
|
+
requests: requests(agent, client).except(*unrouted).merge(@extension_requests),
|
|
76
|
+
notifications: notifications(agent).merge(@extension_notifications)
|
|
77
|
+
)
|
|
65
78
|
end
|
|
66
79
|
|
|
67
80
|
private
|
|
@@ -7,30 +7,53 @@ class ACP::ClientConnection
|
|
|
7
7
|
# @rbs @write_text_file: (^(ACP::Types::WriteTextFileRequest) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError))?
|
|
8
8
|
# @rbs @updates: ACP::ClientConnection::_UpdateHandler
|
|
9
9
|
# @rbs @fs_capabilities: ACP::Types::FileSystemCapabilities?
|
|
10
|
+
# @rbs @extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
11
|
+
# @rbs @extension_notifications: Hash[String, ^(untyped) -> void]
|
|
10
12
|
# @rbs @lock: Thread::Mutex
|
|
11
13
|
# @rbs @streams: Hash[String, Thread::Queue]
|
|
14
|
+
# @rbs @pending_permissions: Hash[String, Array[Thread::Queue]]
|
|
12
15
|
# @rbs @logger: ACP::Transport::_Logger
|
|
13
16
|
|
|
14
17
|
PROTOCOL_VERSION = ACP::AgentConnection::PROTOCOL_VERSION #: Integer
|
|
15
18
|
|
|
16
19
|
IGNORE = ->(_notification) {} #: ^(ACP::Types::SessionNotification) -> void
|
|
17
20
|
|
|
21
|
+
CANCELLED = ACP::Types::RequestPermissionResponse.new(
|
|
22
|
+
outcome: ACP::Types::RequestPermissionOutcome::Cancelled.new
|
|
23
|
+
) #: ACP::Types::RequestPermissionResponse
|
|
24
|
+
|
|
18
25
|
# @rbs transport: ACP::AgentConnection::_Transport
|
|
19
26
|
# @rbs permission: ACP::ClientConnection::_PermissionHandler
|
|
20
27
|
# @rbs read_text_file: (^(ACP::Types::ReadTextFileRequest) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError))?
|
|
21
28
|
# @rbs write_text_file: (^(ACP::Types::WriteTextFileRequest) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError))?
|
|
22
29
|
# @rbs updates: ACP::ClientConnection::_UpdateHandler
|
|
30
|
+
# @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
31
|
+
# @rbs extension_notifications: Hash[String, ^(untyped) -> void]
|
|
23
32
|
# @rbs logger: ACP::Transport::_Logger
|
|
24
33
|
# @rbs return: void
|
|
25
|
-
def initialize(
|
|
34
|
+
def initialize(
|
|
35
|
+
transport:,
|
|
36
|
+
permission:,
|
|
37
|
+
read_text_file: nil,
|
|
38
|
+
write_text_file: nil,
|
|
39
|
+
updates: IGNORE,
|
|
40
|
+
extension_requests: {},
|
|
41
|
+
extension_notifications: {},
|
|
42
|
+
logger: ACP::Transport::StderrLogger.new
|
|
43
|
+
)
|
|
26
44
|
@transport = transport
|
|
27
45
|
@permission = permission
|
|
28
46
|
@read_text_file = read_text_file
|
|
29
47
|
@write_text_file = write_text_file
|
|
30
48
|
@updates = updates
|
|
31
49
|
@fs_capabilities = nil
|
|
50
|
+
@extension_requests = extension_requests
|
|
51
|
+
@extension_notifications = extension_notifications
|
|
52
|
+
extension_requests.each_key { |name| ACP::Extensions.validate_name(name) }
|
|
53
|
+
extension_notifications.each_key { |name| ACP::Extensions.validate_name(name) }
|
|
32
54
|
@lock = Mutex.new
|
|
33
55
|
@streams = {}
|
|
56
|
+
@pending_permissions = {}
|
|
34
57
|
@logger = logger
|
|
35
58
|
end
|
|
36
59
|
|
|
@@ -41,8 +64,8 @@ class ACP::ClientConnection
|
|
|
41
64
|
'session/request_permission' => method(:request_permission),
|
|
42
65
|
'fs/read_text_file' => method(:read_text_file),
|
|
43
66
|
'fs/write_text_file' => method(:write_text_file)
|
|
44
|
-
},
|
|
45
|
-
notifications: { 'session/update' => method(:dispatch) }
|
|
67
|
+
}.merge(@extension_requests),
|
|
68
|
+
notifications: { 'session/update' => method(:dispatch) }.merge(@extension_notifications)
|
|
46
69
|
)
|
|
47
70
|
end
|
|
48
71
|
|
|
@@ -118,10 +141,17 @@ class ACP::ClientConnection
|
|
|
118
141
|
parse(ACP::Types::DeleteSessionResponse, @transport.request('session/delete', request.to_h))
|
|
119
142
|
end
|
|
120
143
|
|
|
144
|
+
# The spec requires a pending session/request_permission to be answered with
|
|
145
|
+
# the cancelled outcome once the turn is cancelled, so every queue registered
|
|
146
|
+
# for the session gets one before this returns. The notification goes first
|
|
147
|
+
# so the wire order is cancel, then the cancelled replies.
|
|
148
|
+
#
|
|
121
149
|
# @rbs notification: ACP::Types::CancelNotification
|
|
122
150
|
# @rbs return: void
|
|
123
151
|
def session_cancel(notification)
|
|
124
152
|
@transport.notify('session/cancel', notification.to_h)
|
|
153
|
+
pending = @lock.synchronize { @pending_permissions.delete(notification.session_id) }
|
|
154
|
+
pending&.each { |replies| replies << [:value, CANCELLED] }
|
|
125
155
|
end
|
|
126
156
|
|
|
127
157
|
# @rbs request: ACP::Types::SetSessionModeRequest
|
|
@@ -151,6 +181,26 @@ class ACP::ClientConnection
|
|
|
151
181
|
parse(ACP::Types::LogoutResponse, @transport.request('logout', request.to_h))
|
|
152
182
|
end
|
|
153
183
|
|
|
184
|
+
# Sends an extension request, keyed by its raw `_`-prefixed wire name, and
|
|
185
|
+
# returns the agent's reply as-is: extension methods have no schema to
|
|
186
|
+
# parse the result into.
|
|
187
|
+
#
|
|
188
|
+
# @rbs method: String
|
|
189
|
+
# @rbs params: untyped
|
|
190
|
+
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
191
|
+
def ext_request(method, params = nil)
|
|
192
|
+
ACP::Extensions.validate_name(method)
|
|
193
|
+
@transport.request(method, params)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# @rbs method: String
|
|
197
|
+
# @rbs params: untyped
|
|
198
|
+
# @rbs return: void
|
|
199
|
+
def ext_notify(method, params = nil)
|
|
200
|
+
ACP::Extensions.validate_name(method)
|
|
201
|
+
@transport.notify(method, params)
|
|
202
|
+
end
|
|
203
|
+
|
|
154
204
|
private
|
|
155
205
|
|
|
156
206
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
@@ -216,6 +266,10 @@ class ACP::ClientConnection
|
|
|
216
266
|
@updates.call(notification) unless queue
|
|
217
267
|
end
|
|
218
268
|
|
|
269
|
+
# The handler runs on its own thread so a cancelled request is answered
|
|
270
|
+
# without waiting for it, and whatever it returns after that is pushed to a
|
|
271
|
+
# queue nobody reads.
|
|
272
|
+
#
|
|
219
273
|
# @rbs params: untyped
|
|
220
274
|
# @rbs return: (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
221
275
|
def request_permission(params)
|
|
@@ -223,7 +277,45 @@ class ACP::ClientConnection
|
|
|
223
277
|
rescue KeyError, TypeError, NoMethodError
|
|
224
278
|
ACP::RequestError.invalid_params
|
|
225
279
|
else
|
|
226
|
-
|
|
280
|
+
replies = Thread::Queue.new
|
|
281
|
+
register_permission(request.session_id, replies)
|
|
282
|
+
Thread.new do
|
|
283
|
+
# A raised StandardError is not a returned ACP::RequestError, so the two
|
|
284
|
+
# need distinct shapes on the queue.
|
|
285
|
+
replies << begin
|
|
286
|
+
[:value, @permission.call(request)]
|
|
287
|
+
rescue StandardError => e
|
|
288
|
+
[:raised, e]
|
|
289
|
+
end
|
|
290
|
+
ensure
|
|
291
|
+
unregister_permission(request.session_id, replies)
|
|
292
|
+
end
|
|
293
|
+
settled = replies.pop
|
|
294
|
+
# Re-raised so the transport's invoke turns it into an error reply, as it
|
|
295
|
+
# would if the handler ran on the serve thread itself.
|
|
296
|
+
raise settled[1] if settled[0] == :raise
|
|
297
|
+
|
|
298
|
+
settled[1]
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# @rbs session_id: String
|
|
302
|
+
# @rbs replies: Thread::Queue
|
|
303
|
+
# @rbs return: void
|
|
304
|
+
def register_permission(session_id, replies)
|
|
305
|
+
@lock.synchronize { (@pending_permissions[session_id] ||= []) << replies }
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# @rbs session_id: String
|
|
309
|
+
# @rbs replies: Thread::Queue
|
|
310
|
+
# @rbs return: void
|
|
311
|
+
def unregister_permission(session_id, replies)
|
|
312
|
+
@lock.synchronize do
|
|
313
|
+
pending = @pending_permissions[session_id]
|
|
314
|
+
if pending
|
|
315
|
+
pending.delete(replies)
|
|
316
|
+
@pending_permissions.delete(session_id) if pending.empty?
|
|
317
|
+
end
|
|
318
|
+
end
|
|
227
319
|
end
|
|
228
320
|
|
|
229
321
|
# The routes are registered at start, before connect records the advertised
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared by the connections' handler maps and send paths, so a plain method
|
|
4
|
+
# name cannot slip into the extension routes.
|
|
5
|
+
module ACP::Extensions
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# @rbs name: String
|
|
9
|
+
# @rbs return: void
|
|
10
|
+
def validate_name(name)
|
|
11
|
+
return if name.start_with?('_')
|
|
12
|
+
|
|
13
|
+
raise ArgumentError, "extension method names must start with '_' (got #{name.inspect})"
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/acp/version.rb
CHANGED
|
@@ -63,6 +63,20 @@ class ACP::AgentConnection::Client
|
|
|
63
63
|
# @rbs return: void
|
|
64
64
|
def complete_elicitation: (ACP::Types::CompleteElicitationNotification notification) -> void
|
|
65
65
|
|
|
66
|
+
# Sends an extension request, keyed by its raw `_`-prefixed wire name, and
|
|
67
|
+
# returns the client's reply as-is: extension methods have no schema to
|
|
68
|
+
# parse the result into.
|
|
69
|
+
#
|
|
70
|
+
# @rbs method: String
|
|
71
|
+
# @rbs params: untyped
|
|
72
|
+
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
73
|
+
def ext_request: (String method, ?untyped params) -> (Hash[String, untyped] | ACP::RequestError)
|
|
74
|
+
|
|
75
|
+
# @rbs method: String
|
|
76
|
+
# @rbs params: untyped
|
|
77
|
+
# @rbs return: void
|
|
78
|
+
def ext_notify: (String method, ?untyped params) -> void
|
|
79
|
+
|
|
66
80
|
private
|
|
67
81
|
|
|
68
82
|
# @rbs rpc_method: String
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
class ACP::AgentConnection
|
|
4
4
|
@logger: ACP::Transport::_Logger
|
|
5
5
|
|
|
6
|
+
@extension_notifications: Hash[String, ^(untyped) -> void]
|
|
7
|
+
|
|
8
|
+
@extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
9
|
+
|
|
6
10
|
@factory: ^(ACP::AgentConnection::Client) -> ACP::AgentConnection::_Agent
|
|
7
11
|
|
|
8
12
|
@auth_methods: Array[ACP::Types::AuthMethod::t]
|
|
@@ -26,10 +30,12 @@ class ACP::AgentConnection
|
|
|
26
30
|
# @rbs capabilities: ACP::Types::AgentCapabilities
|
|
27
31
|
# @rbs agent_info: ACP::Types::Implementation?
|
|
28
32
|
# @rbs auth_methods: Array[ACP::Types::AuthMethod::t]
|
|
33
|
+
# @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
34
|
+
# @rbs extension_notifications: Hash[String, ^(untyped) -> void]
|
|
29
35
|
# @rbs logger: ACP::Transport::_Logger
|
|
30
36
|
# @rbs &factory: (ACP::AgentConnection::Client) -> ACP::AgentConnection::_Agent
|
|
31
37
|
# @rbs return: void
|
|
32
|
-
def initialize: (transport: ACP::AgentConnection::_Transport, capabilities: ACP::Types::AgentCapabilities, ?agent_info: ACP::Types::Implementation?, ?auth_methods: Array[ACP::Types::AuthMethod::t], ?logger: ACP::Transport::_Logger) { (ACP::AgentConnection::Client) -> ACP::AgentConnection::_Agent } -> void
|
|
38
|
+
def initialize: (transport: ACP::AgentConnection::_Transport, capabilities: ACP::Types::AgentCapabilities, ?agent_info: ACP::Types::Implementation?, ?auth_methods: Array[ACP::Types::AuthMethod::t], ?extension_requests: Hash[String, ^(untyped) -> untyped], ?extension_notifications: Hash[String, ^(untyped) -> void], ?logger: ACP::Transport::_Logger) { (ACP::AgentConnection::Client) -> ACP::AgentConnection::_Agent } -> void
|
|
33
39
|
|
|
34
40
|
# @rbs return: Thread
|
|
35
41
|
def start: () -> Thread
|
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
class ACP::ClientConnection
|
|
4
4
|
@logger: ACP::Transport::_Logger
|
|
5
5
|
|
|
6
|
+
@pending_permissions: Hash[String, Array[Thread::Queue]]
|
|
7
|
+
|
|
6
8
|
@streams: Hash[String, Thread::Queue]
|
|
7
9
|
|
|
8
10
|
@lock: Thread::Mutex
|
|
9
11
|
|
|
12
|
+
@extension_notifications: Hash[String, ^(untyped) -> void]
|
|
13
|
+
|
|
14
|
+
@extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
15
|
+
|
|
10
16
|
@fs_capabilities: ACP::Types::FileSystemCapabilities?
|
|
11
17
|
|
|
12
18
|
@updates: ACP::ClientConnection::_UpdateHandler
|
|
@@ -23,14 +29,18 @@ class ACP::ClientConnection
|
|
|
23
29
|
|
|
24
30
|
IGNORE: ^(ACP::Types::SessionNotification) -> void
|
|
25
31
|
|
|
32
|
+
CANCELLED: ACP::Types::RequestPermissionResponse
|
|
33
|
+
|
|
26
34
|
# @rbs transport: ACP::AgentConnection::_Transport
|
|
27
35
|
# @rbs permission: ACP::ClientConnection::_PermissionHandler
|
|
28
36
|
# @rbs read_text_file: (^(ACP::Types::ReadTextFileRequest) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError))?
|
|
29
37
|
# @rbs write_text_file: (^(ACP::Types::WriteTextFileRequest) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError))?
|
|
30
38
|
# @rbs updates: ACP::ClientConnection::_UpdateHandler
|
|
39
|
+
# @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
40
|
+
# @rbs extension_notifications: Hash[String, ^(untyped) -> void]
|
|
31
41
|
# @rbs logger: ACP::Transport::_Logger
|
|
32
42
|
# @rbs return: void
|
|
33
|
-
def initialize: (transport: ACP::AgentConnection::_Transport, permission: ACP::ClientConnection::_PermissionHandler, ?read_text_file: (^(ACP::Types::ReadTextFileRequest) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError))?, ?write_text_file: (^(ACP::Types::WriteTextFileRequest) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError))?, ?updates: ACP::ClientConnection::_UpdateHandler, ?logger: ACP::Transport::_Logger) -> void
|
|
43
|
+
def initialize: (transport: ACP::AgentConnection::_Transport, permission: ACP::ClientConnection::_PermissionHandler, ?read_text_file: (^(ACP::Types::ReadTextFileRequest) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError))?, ?write_text_file: (^(ACP::Types::WriteTextFileRequest) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError))?, ?updates: ACP::ClientConnection::_UpdateHandler, ?extension_requests: Hash[String, ^(untyped) -> untyped], ?extension_notifications: Hash[String, ^(untyped) -> void], ?logger: ACP::Transport::_Logger) -> void
|
|
34
44
|
|
|
35
45
|
# @rbs return: Thread
|
|
36
46
|
def start: () -> Thread
|
|
@@ -75,6 +85,11 @@ class ACP::ClientConnection
|
|
|
75
85
|
# @rbs return: ACP::Types::DeleteSessionResponse | ACP::RequestError
|
|
76
86
|
def session_delete: (ACP::Types::DeleteSessionRequest request) -> (ACP::Types::DeleteSessionResponse | ACP::RequestError)
|
|
77
87
|
|
|
88
|
+
# The spec requires a pending session/request_permission to be answered with
|
|
89
|
+
# the cancelled outcome once the turn is cancelled, so every queue registered
|
|
90
|
+
# for the session gets one before this returns. The notification goes first
|
|
91
|
+
# so the wire order is cancel, then the cancelled replies.
|
|
92
|
+
#
|
|
78
93
|
# @rbs notification: ACP::Types::CancelNotification
|
|
79
94
|
# @rbs return: void
|
|
80
95
|
def session_cancel: (ACP::Types::CancelNotification notification) -> void
|
|
@@ -98,6 +113,20 @@ class ACP::ClientConnection
|
|
|
98
113
|
# @rbs return: ACP::Types::LogoutResponse | ACP::RequestError
|
|
99
114
|
def logout: (ACP::Types::LogoutRequest request) -> (ACP::Types::LogoutResponse | ACP::RequestError)
|
|
100
115
|
|
|
116
|
+
# Sends an extension request, keyed by its raw `_`-prefixed wire name, and
|
|
117
|
+
# returns the agent's reply as-is: extension methods have no schema to
|
|
118
|
+
# parse the result into.
|
|
119
|
+
#
|
|
120
|
+
# @rbs method: String
|
|
121
|
+
# @rbs params: untyped
|
|
122
|
+
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
123
|
+
def ext_request: (String method, ?untyped params) -> (Hash[String, untyped] | ACP::RequestError)
|
|
124
|
+
|
|
125
|
+
# @rbs method: String
|
|
126
|
+
# @rbs params: untyped
|
|
127
|
+
# @rbs return: void
|
|
128
|
+
def ext_notify: (String method, ?untyped params) -> void
|
|
129
|
+
|
|
101
130
|
private
|
|
102
131
|
|
|
103
132
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
@@ -124,10 +153,24 @@ class ACP::ClientConnection
|
|
|
124
153
|
# @rbs return: void
|
|
125
154
|
def dispatch: (untyped params) -> void
|
|
126
155
|
|
|
156
|
+
# The handler runs on its own thread so a cancelled request is answered
|
|
157
|
+
# without waiting for it, and whatever it returns after that is pushed to a
|
|
158
|
+
# queue nobody reads.
|
|
159
|
+
#
|
|
127
160
|
# @rbs params: untyped
|
|
128
161
|
# @rbs return: (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
129
162
|
def request_permission: (untyped params) -> (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
130
163
|
|
|
164
|
+
# @rbs session_id: String
|
|
165
|
+
# @rbs replies: Thread::Queue
|
|
166
|
+
# @rbs return: void
|
|
167
|
+
def register_permission: (String session_id, Thread::Queue replies) -> void
|
|
168
|
+
|
|
169
|
+
# @rbs session_id: String
|
|
170
|
+
# @rbs replies: Thread::Queue
|
|
171
|
+
# @rbs return: void
|
|
172
|
+
def unregister_permission: (String session_id, Thread::Queue replies) -> void
|
|
173
|
+
|
|
131
174
|
# The routes are registered at start, before connect records the advertised
|
|
132
175
|
# capabilities, so each one answers -32601 until then.
|
|
133
176
|
#
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Generated from lib/acp/extensions.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
# Shared by the connections' handler maps and send paths, so a plain method
|
|
4
|
+
# name cannot slip into the extension routes.
|
|
5
|
+
module ACP::Extensions
|
|
6
|
+
# @rbs name: String
|
|
7
|
+
# @rbs return: void
|
|
8
|
+
def validate_name: (String name) -> void
|
|
9
|
+
end
|
|
@@ -81,6 +81,8 @@ class ACP::AgentConnection
|
|
|
81
81
|
def release_terminal: (ACP::Types::ReleaseTerminalRequest) -> (ACP::Types::ReleaseTerminalResponse | ACP::RequestError)
|
|
82
82
|
def create_elicitation: (ACP::Types::CreateElicitationRequest request) -> (ACP::Types::CreateElicitationResponse | ACP::RequestError)
|
|
83
83
|
def complete_elicitation: (ACP::Types::CompleteElicitationNotification notification) -> void
|
|
84
|
+
def ext_request: (String method, ?untyped params) -> (Hash[String, untyped] | ACP::RequestError)
|
|
85
|
+
def ext_notify: (String method, ?untyped params) -> void
|
|
84
86
|
def capabilities: () -> ACP::Types::ClientCapabilities?
|
|
85
87
|
end
|
|
86
88
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: acp-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jake Bottrall
|
|
@@ -44,6 +44,7 @@ files:
|
|
|
44
44
|
- lib/acp/agent_connection/optional_method/resume_session.rb
|
|
45
45
|
- lib/acp/agent_process.rb
|
|
46
46
|
- lib/acp/client_connection.rb
|
|
47
|
+
- lib/acp/extensions.rb
|
|
47
48
|
- lib/acp/request_error.rb
|
|
48
49
|
- lib/acp/sdk.rb
|
|
49
50
|
- lib/acp/transport.rb
|
|
@@ -259,6 +260,7 @@ files:
|
|
|
259
260
|
- sig/generated/acp/agent_connection/optional_method/resume_session.rbs
|
|
260
261
|
- sig/generated/acp/agent_process.rbs
|
|
261
262
|
- sig/generated/acp/client_connection.rbs
|
|
263
|
+
- sig/generated/acp/extensions.rbs
|
|
262
264
|
- sig/generated/acp/request_error.rbs
|
|
263
265
|
- sig/generated/acp/sdk.rbs
|
|
264
266
|
- sig/generated/acp/transport.rbs
|
|
@@ -472,6 +474,7 @@ files:
|
|
|
472
474
|
- sig/manual/acp/agent_connection/optional_method/resume_session.rbs
|
|
473
475
|
- sig/manual/acp/agent_process.rbs
|
|
474
476
|
- sig/manual/acp/client_connection.rbs
|
|
477
|
+
- sig/manual/acp/extensions.rbs
|
|
475
478
|
- sig/manual/acp/transport.rbs
|
|
476
479
|
- sig/manual/acp/transport/stdio.rbs
|
|
477
480
|
homepage: https://github.com/bottrall/acp-sdk
|