acp-sdk 0.9.0 → 0.10.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 +11 -0
- data/README.md +6 -4
- data/lib/acp/agent_connection/client.rb +31 -1
- data/lib/acp/agent_connection/optional_method/disable_provider.rb +23 -0
- data/lib/acp/agent_connection/optional_method/fork_session.rb +23 -0
- data/lib/acp/agent_connection/optional_method/list_providers.rb +23 -0
- data/lib/acp/agent_connection/optional_method/set_provider.rb +23 -0
- data/lib/acp/agent_connection.rb +73 -3
- data/lib/acp/client_connection.rb +108 -9
- data/lib/acp/request_error.rb +10 -0
- data/lib/acp/transport/cancellation.rb +17 -0
- data/lib/acp/transport/stdio.rb +10 -5
- data/lib/acp/types/unstable/message_mcp_response/result.rb +1 -1
- data/lib/acp/version.rb +1 -1
- data/sig/generated/acp/agent_connection/client.rbs +20 -1
- data/sig/generated/acp/agent_connection/optional_method/disable_provider.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/fork_session.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/list_providers.rbs +13 -0
- data/sig/generated/acp/agent_connection/optional_method/set_provider.rbs +13 -0
- data/sig/generated/acp/agent_connection.rbs +29 -0
- data/sig/generated/acp/client_connection.rbs +57 -1
- data/sig/generated/acp/request_error.rbs +8 -0
- data/sig/generated/acp/transport/cancellation.rbs +13 -0
- data/sig/generated/acp/transport/stdio.rbs +6 -2
- data/sig/manual/acp/agent_connection/optional_method/disable_provider.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/fork_session.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/list_providers.rbs +6 -0
- data/sig/manual/acp/agent_connection/optional_method/set_provider.rbs +6 -0
- data/sig/manual/acp/agent_connection.rbs +30 -0
- metadata +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0d983006184ce11ac51e9361062aa6878c01d348645f41fe226423db5d72e8b
|
|
4
|
+
data.tar.gz: 0d50ff69cb0c6cc743d75dcd3fd943a9ddeaa4469a8f1e8d6e54be811e3c3b39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4296fbbf1fa985cb3913f2019528c06658fd8fcac285a238abf79073d2d5eb13d081e69da57f05476569967189f732c804f6dc851e291709c0b4052572b17d9f
|
|
7
|
+
data.tar.gz: 8f0a5bedfa32615f867111297c4fc593f2b567fd77a55e76dda4eabe57bd8494e225de93b470f1ac294f4b99a57797fda4d7f69704693fb8cd322e65fe650f59
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0](https://github.com/bottrall/acp-sdk/compare/v0.9.0...v0.10.0) (2026-10-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* MCP-over-ACP mcp/message (unstable) ([#89](https://github.com/bottrall/acp-sdk/issues/89)) ([3d6d30c](https://github.com/bottrall/acp-sdk/commit/3d6d30c6e31e88dd0fe78e08a5bc4308948cacdd))
|
|
9
|
+
* providers/list, providers/set and providers/disable (unstable) ([#88](https://github.com/bottrall/acp-sdk/issues/88)) ([eac16f7](https://github.com/bottrall/acp-sdk/commit/eac16f78809ea5a677835c6fe064063dcb64c162)), closes [#50](https://github.com/bottrall/acp-sdk/issues/50)
|
|
10
|
+
* request cancellation on the agent and client connections ([#85](https://github.com/bottrall/acp-sdk/issues/85)) ([58ae0ab](https://github.com/bottrall/acp-sdk/commit/58ae0abe6387e2887482ec14e361cfac91cd54ea))
|
|
11
|
+
* session/fork (unstable) ([#86](https://github.com/bottrall/acp-sdk/issues/86)) ([9868cb6](https://github.com/bottrall/acp-sdk/commit/9868cb666a8111c414bf3b041167d6aa022e1e95)), closes [#49](https://github.com/bottrall/acp-sdk/issues/49)
|
|
12
|
+
* support all currently supported Ruby versions ([#90](https://github.com/bottrall/acp-sdk/issues/90)) ([343d07b](https://github.com/bottrall/acp-sdk/commit/343d07b9e6d98b1c24201c5eef8b250858c0626b)), closes [#73](https://github.com/bottrall/acp-sdk/issues/73)
|
|
13
|
+
|
|
3
14
|
## [0.9.0](https://github.com/bottrall/acp-sdk/compare/v0.8.0...v0.9.0) (2026-10-07)
|
|
4
15
|
|
|
5
16
|
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Every ACP request, response and notification is a generated Ruby class under `AC
|
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
-
Requires Ruby 4
|
|
14
|
+
Requires Ruby 3.4 or later (CI tests 3.4 and 4.0).
|
|
15
15
|
|
|
16
16
|
Add the gem to your Gemfile:
|
|
17
17
|
|
|
@@ -48,11 +48,12 @@ connection.start.join
|
|
|
48
48
|
|
|
49
49
|
The agent's contract:
|
|
50
50
|
|
|
51
|
-
- It defines `new_session`, `prompt` and `cancel`. `load_session`, `list_sessions`, `resume_session`, `close_session` and `delete_session` are required only when `capabilities` advertises `load_session` or `session_capabilities.list`, `.resume`, `.close` or `.delete`, and `authenticate` only when `auth_methods` is non-empty, and `logout` only when `capabilities` advertises `auth.logout`; `start` raises `ArgumentError` when one is advertised but missing, and a method that is not advertised is answered with `-32601`. `change_session_mode` and `change_session_config_option` are optional, since modes and config options are offered per session rather than in `initialize`; `session/set_mode` and `session/set_config_option` are answered with `-32601` when the agent does not define them. `session_created`, if defined, runs right after the `session/new` reply is sent.
|
|
51
|
+
- It defines `new_session`, `prompt` and `cancel`. `load_session`, `list_sessions`, `resume_session`, `close_session` and `delete_session` are required only when `capabilities` advertises `load_session` or `session_capabilities.list`, `.resume`, `.close` or `.delete`, and `authenticate` only when `auth_methods` is non-empty, and `logout` only when `capabilities` advertises `auth.logout`; `start` raises `ArgumentError` when one is advertised but missing, and a method that is not advertised is answered with `-32601`. `change_session_mode` and `change_session_config_option` are optional, since modes and config options are offered per session rather than in `initialize`; `session/set_mode` and `session/set_config_option` are answered with `-32601` when the agent does not define them. `session_created`, if defined, runs right after the `session/new` reply is sent. `fork_session` is the same shape but unstable: `session/fork` routes only when the caller has opted into the unstable types with `require 'acp/types/unstable'` and `capabilities` advertises `session_capabilities.fork`, and is answered with `-32601` otherwise. `list_providers`, `set_provider` and `disable_provider` are the same shape but unstable too: `providers/list`, `providers/set` and `providers/disable` route only when the caller has opted into the unstable types with `require 'acp/types/unstable'` and `capabilities` advertises `providers`, and are answered with `-32601` otherwise.
|
|
52
52
|
- Each request method returns its response type or an `ACP::RequestError`, which is sent as the error reply. Params that do not match the schema are answered with `-32602` before the agent sees them, with `data` naming the failing path, e.g. `{ errors: ['sessionId: expected String, got Integer'] }`.
|
|
53
53
|
- A handler that raises is answered with `-32603` and the message `Internal error`. Nothing from the exception reaches the peer — its class and message are logged to the transport's logger instead, since messages can interpolate paths, queries or credentials. Return an `ACP::RequestError` from the handler when the peer should see a real error message.
|
|
54
|
-
- `cancel` runs on the transport's reader thread, so it must return quickly: set a flag and let the prompt notice it.
|
|
54
|
+
- `cancel` runs on the transport's reader thread, so it must return quickly: set a flag and let the prompt notice it, and call `client.cancel_requests` to cancel the connection's outstanding requests to the client — a blocked `request_permission` or `wait_for_terminal_exit`, say. That sends `$/cancel_request` for each one, and the blocked call returns once the client answers it, with its response or the `-32800` error.
|
|
55
55
|
- After a cancel, the agent must itself end the turn with `stopReason: cancelled`. `ACP::AgentConnection` does not enforce it.
|
|
56
|
+
- `client.cancelled?` is true in a handler when the client cancelled the request it is serving with `$/cancel_request`. The handler can end early by returning `ACP::RequestError.request_cancelled`, which is answered as `-32800`, or return a normal response, which is sent as-is — the transport sends exactly one response either way.
|
|
56
57
|
- `client.capabilities` is `nil` until the client sends `initialize`.
|
|
57
58
|
- `client.read_text_file` and `client.write_text_file` take an `ACP::Types::ReadTextFileRequest` or `ACP::Types::WriteTextFileRequest` and return the response type or an `ACP::RequestError`. Unless `client.capabilities` advertises `fs.read_text_file` or `fs.write_text_file`, they return `-32601` without sending anything to the client.
|
|
58
59
|
- `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.
|
|
@@ -64,7 +65,7 @@ Extensions are `_`-prefixed methods outside the spec. `AgentConnection` takes `e
|
|
|
64
65
|
|
|
65
66
|
## Driving an agent
|
|
66
67
|
|
|
67
|
-
`ACP::ClientConnection` is the other side of the same transport. Each method takes the request's generated `ACP::Types` object and returns its response type or the agent's `ACP::RequestError`. `connect` sends `initialize`, since Ruby reserves that name for the constructor, and `authenticate` and `logout` send their namesake methods. `session_set_mode` sends `session/set_mode`, and `session_set_config_option` sends `session/set_config_option` and returns the session's complete option list, which replaces any state held from an earlier reply. A handler that raises is answered with `-32603` and the message `Internal error`, and the exception is only logged to the transport's logger.
|
|
68
|
+
`ACP::ClientConnection` is the other side of the same transport. Each method takes the request's generated `ACP::Types` object and returns its response type or the agent's `ACP::RequestError`. `connect` sends `initialize`, since Ruby reserves that name for the constructor, and `authenticate` and `logout` send their namesake methods. `session_set_mode` sends `session/set_mode`, and `session_set_config_option` sends `session/set_config_option` and returns the session's complete option list, which replaces any state held from an earlier reply. `session_fork` sends `session/fork`; it is unstable, so the request and response types exist only under `ACP::Types::Unstable` and calling it before `require 'acp/types/unstable'` raises `NameError`. `providers_list`, `providers_set` and `providers_disable` send `providers/list`, `providers/set` and `providers/disable` on the same unstable terms. A handler that raises is answered with `-32603` and the message `Internal error`, and the exception is only logged to the transport's logger.
|
|
68
69
|
|
|
69
70
|
`ACP::AgentProcess.spawn` starts a command with args, env and cwd, wires its stdio to a new `ACP::ClientConnection` and starts it. The block receives the connection and the process handle (`pid`, plus `stderr` when captured), and the child is terminated — after its stdin is closed, with TERM and then KILL if it will not exit — when the block exits, so a raised block cannot leak the process. Without a block, `spawn` returns the process and calling `terminate` is yours. `stderr` is `:inherit` by default, so the child writes to your stderr; pass `:capture` to collect it on the process instead.
|
|
70
71
|
|
|
@@ -105,6 +106,7 @@ connection = ACP::ClientConnection.new(
|
|
|
105
106
|
|
|
106
107
|
- `session_prompt` and `session_load` yield the session's updates on the calling thread as they arrive and return once the agent replies. Updates outside those calls, such as the available commands after `session/new`, go to `updates`, which runs on the reader thread and must return quickly. `session_resume` does not take a block: the agent must not replay history on resume, so unlike `session_load` there is nothing to stream. `session_close` and `session_delete` end the session, discarding it on the agent, and return once the agent replies.
|
|
107
108
|
- `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.
|
|
109
|
+
- The agent may cancel any outstanding request with `$/cancel_request`: a handler that can end early checks `connection.cancelled?` — close over the connection when constructing it — and returns `ACP::RequestError.request_cancelled`, which is answered as `-32800`. The permission handler still sees the cancel on its own thread. Whatever the handler returns, the transport sends exactly one response.
|
|
108
110
|
- `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.
|
|
109
111
|
- `create_terminal`, `terminal_output`, `wait_for_terminal_exit`, `kill_terminal` and `release_terminal` answer the agent's `terminal/create`, `terminal/output`, `terminal/wait_for_exit`, `terminal/kill` and `terminal/release` with the matching `ACP::Types` response or an `ACP::RequestError`. They are routed by the `terminal` capability `connect` sends, the same way as `fs`: 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 `terminal` with any of the five missing. The transport serves each request on its own thread, so `wait_for_terminal_exit` may block without holding up the others.
|
|
110
112
|
- `elicitation` answers the agent's `elicitation/create` with an `ACP::Types::CreateElicitationResponse` or an `ACP::RequestError`, serving both form and url mode, and `complete_elicitation` receives the agent's `elicitation/complete` notification. They are routed by the `elicitation` capabilities `connect` sends: a request whose mode the client does not advertise is answered with `-32602` without reaching the handler, and `connect` raises `ArgumentError` — before sending `initialize` — when the request advertises `elicitation.form` or `elicitation.url` with no `elicitation` handler. The client tracks the elicitation ids of the url-mode requests it serves, so a completion for an unknown or already-completed id is ignored. `complete_elicitation` runs on the reader thread and must return quickly; it may be omitted, in which case completions are only tracked.
|
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
class ACP::AgentConnection::Client
|
|
4
4
|
# @rbs @peer: ACP::AgentConnection::_Peer
|
|
5
|
+
# @rbs @mcp_advertised: bool
|
|
5
6
|
|
|
6
7
|
# @dynamic capabilities, capabilities=
|
|
7
8
|
attr_accessor :capabilities #: ACP::Types::ClientCapabilities?
|
|
8
9
|
|
|
9
10
|
# @rbs peer: ACP::AgentConnection::_Peer
|
|
11
|
+
# @rbs mcp_advertised: bool
|
|
10
12
|
# @rbs return: void
|
|
11
|
-
def initialize(peer:)
|
|
13
|
+
def initialize(peer:, mcp_advertised: false)
|
|
12
14
|
@peer = peer
|
|
15
|
+
# The agent's own initialize advertisement of mcpCapabilities.acp, which
|
|
16
|
+
# licenses this side's mcp/message sends; the client's advertisement is
|
|
17
|
+
# the separate capabilities accessor.
|
|
18
|
+
@mcp_advertised = mcp_advertised
|
|
13
19
|
@capabilities = nil
|
|
14
20
|
end
|
|
15
21
|
|
|
@@ -36,6 +42,18 @@ class ACP::AgentConnection::Client
|
|
|
36
42
|
call('session/request_permission', ACP::Types::RequestPermissionResponse, request)
|
|
37
43
|
end
|
|
38
44
|
|
|
45
|
+
# @rbs return: void
|
|
46
|
+
def cancel_requests
|
|
47
|
+
@peer.cancel_requests
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Always false off a handler thread.
|
|
51
|
+
#
|
|
52
|
+
# @rbs return: bool
|
|
53
|
+
def cancelled?
|
|
54
|
+
!!@peer.cancellation&.cancelled?
|
|
55
|
+
end
|
|
56
|
+
|
|
39
57
|
# @rbs request: ACP::Types::ReadTextFileRequest
|
|
40
58
|
# @rbs return: ACP::Types::ReadTextFileResponse | ACP::RequestError
|
|
41
59
|
def read_text_file(request)
|
|
@@ -105,6 +123,18 @@ class ACP::AgentConnection::Client
|
|
|
105
123
|
@peer.notify('elicitation/complete', notification.to_h)
|
|
106
124
|
end
|
|
107
125
|
|
|
126
|
+
# Sends one inner MCP operation to the client's declared ACP MCP server. The
|
|
127
|
+
# reply's result may be null, and a reply matching neither carrier branch
|
|
128
|
+
# comes back as the raw hash.
|
|
129
|
+
#
|
|
130
|
+
# @rbs request: ACP::Types::Unstable::MessageMcpRequest
|
|
131
|
+
# @rbs return: (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
132
|
+
def mcp_message(request)
|
|
133
|
+
return ACP::RequestError.unadvertised_agent('mcpCapabilities.acp') unless @mcp_advertised
|
|
134
|
+
|
|
135
|
+
call('mcp/message', ACP::Types::Unstable::MessageMcpResponse, request)
|
|
136
|
+
end
|
|
137
|
+
|
|
108
138
|
# Sends an extension request, keyed by its raw `_`-prefixed wire name, and
|
|
109
139
|
# returns the client's reply as-is: extension methods have no schema to
|
|
110
140
|
# parse the result into.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::DisableProvider
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
# @rbs return: String
|
|
7
|
+
def rpc_method
|
|
8
|
+
'providers/disable'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @rbs return: Symbol
|
|
12
|
+
def agent_method
|
|
13
|
+
:disable_provider
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
17
|
+
# @rbs return: bool
|
|
18
|
+
def advertised?(initialize_response)
|
|
19
|
+
capabilities = initialize_response.agent_capabilities #: ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities?
|
|
20
|
+
providers = capabilities.is_a?(ACP::Types::Unstable::AgentCapabilities) ? capabilities.providers : nil #: ACP::Types::Unstable::ProvidersCapabilities?
|
|
21
|
+
providers ? true : false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::ForkSession
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
# @rbs return: String
|
|
7
|
+
def rpc_method
|
|
8
|
+
'session/fork'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @rbs return: Symbol
|
|
12
|
+
def agent_method
|
|
13
|
+
:fork_session
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
17
|
+
# @rbs return: bool
|
|
18
|
+
def advertised?(initialize_response)
|
|
19
|
+
session = initialize_response.agent_capabilities&.session_capabilities #: ACP::Types::SessionCapabilities | ACP::Types::Unstable::SessionCapabilities?
|
|
20
|
+
fork = session.is_a?(ACP::Types::Unstable::SessionCapabilities) ? session.fork : nil #: ACP::Types::Unstable::SessionForkCapabilities?
|
|
21
|
+
fork ? true : false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::ListProviders
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
# @rbs return: String
|
|
7
|
+
def rpc_method
|
|
8
|
+
'providers/list'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @rbs return: Symbol
|
|
12
|
+
def agent_method
|
|
13
|
+
:list_providers
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
17
|
+
# @rbs return: bool
|
|
18
|
+
def advertised?(initialize_response)
|
|
19
|
+
capabilities = initialize_response.agent_capabilities #: ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities?
|
|
20
|
+
providers = capabilities.is_a?(ACP::Types::Unstable::AgentCapabilities) ? capabilities.providers : nil #: ACP::Types::Unstable::ProvidersCapabilities?
|
|
21
|
+
providers ? true : false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::SetProvider
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
# @rbs return: String
|
|
7
|
+
def rpc_method
|
|
8
|
+
'providers/set'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @rbs return: Symbol
|
|
12
|
+
def agent_method
|
|
13
|
+
:set_provider
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs initialize_response: ACP::Types::InitializeResponse
|
|
17
|
+
# @rbs return: bool
|
|
18
|
+
def advertised?(initialize_response)
|
|
19
|
+
capabilities = initialize_response.agent_capabilities #: ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities?
|
|
20
|
+
providers = capabilities.is_a?(ACP::Types::Unstable::AgentCapabilities) ? capabilities.providers : nil #: ACP::Types::Unstable::ProvidersCapabilities?
|
|
21
|
+
providers ? true : false
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/acp/agent_connection.rb
CHANGED
|
@@ -61,15 +61,21 @@ class ACP::AgentConnection
|
|
|
61
61
|
|
|
62
62
|
# @rbs return: Thread
|
|
63
63
|
def start
|
|
64
|
-
client = ACP::AgentConnection::Client.new(peer: @transport)
|
|
64
|
+
client = ACP::AgentConnection::Client.new(peer: @transport, mcp_advertised: mcp_acp_advertised?)
|
|
65
65
|
agent = @factory.call(client)
|
|
66
66
|
# Routes are fixed before a client connects, so the check sees the
|
|
67
67
|
# unfiltered auth methods: a terminal-capable client still needs
|
|
68
68
|
# authenticate routed.
|
|
69
|
-
advertised, unadvertised =
|
|
69
|
+
advertised, unadvertised = optional_methods.partition do |method|
|
|
70
|
+
method.advertised?(initialize_response(@auth_methods))
|
|
71
|
+
end
|
|
70
72
|
missing = advertised.map(&:agent_method).reject { |name| agent.respond_to?(name) }
|
|
71
73
|
raise ArgumentError, "initialize advertises methods the agent lacks: #{missing.join(', ')}" unless missing.empty?
|
|
72
74
|
|
|
75
|
+
if mcp_acp_advertised? && !agent.respond_to?(:mcp_message_notification)
|
|
76
|
+
raise ArgumentError, 'initialize advertises mcpCapabilities.acp but the agent lacks mcp_message_notification'
|
|
77
|
+
end
|
|
78
|
+
|
|
73
79
|
unrouted = unadvertised.map(&:rpc_method) + PER_SESSION.reject { |_, name| agent.respond_to?(name) }.keys
|
|
74
80
|
@transport.start(
|
|
75
81
|
requests: requests(agent, client).except(*unrouted).merge(@extension_requests),
|
|
@@ -101,13 +107,49 @@ class ACP::AgentConnection
|
|
|
101
107
|
'session/set_config_option' => route(ACP::Types::SetSessionConfigOptionRequest) do |request|
|
|
102
108
|
full.change_session_config_option(request)
|
|
103
109
|
end
|
|
110
|
+
}.merge(unstable_requests(full))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The unstable request types are referenced while the hash is built, so
|
|
114
|
+
# without the guard start would raise NameError even for an agent that never
|
|
115
|
+
# advertises fork or providers.
|
|
116
|
+
#
|
|
117
|
+
# @rbs full: ACP::AgentConnection::_FullAgent
|
|
118
|
+
# @rbs return: Hash[String, ^(untyped) -> (ACP::AgentConnection::_Response | ACP::RequestError | ACP::Transport::Reply)]
|
|
119
|
+
def unstable_requests(full)
|
|
120
|
+
return {} unless defined?(ACP::Types::Unstable)
|
|
121
|
+
|
|
122
|
+
{
|
|
123
|
+
'session/fork' => route(ACP::Types::Unstable::ForkSessionRequest) { |request| full.fork_session(request) },
|
|
124
|
+
'providers/list' => route(ACP::Types::Unstable::ListProvidersRequest) { |request| full.list_providers(request) },
|
|
125
|
+
'providers/set' => route(ACP::Types::Unstable::SetProviderRequest) { |request| full.set_provider(request) },
|
|
126
|
+
'providers/disable' => route(ACP::Types::Unstable::DisableProviderRequest) { |request| full.disable_provider(request) }
|
|
104
127
|
}
|
|
105
128
|
end
|
|
106
129
|
|
|
130
|
+
# ForkSession's and the provider methods' advertised? reference the unstable
|
|
131
|
+
# types, so they can only be probed when the caller opted in with
|
|
132
|
+
# `require 'acp/types/unstable'`.
|
|
133
|
+
#
|
|
134
|
+
# @rbs return: Array[ACP::AgentConnection::_OptionalMethod]
|
|
135
|
+
def optional_methods
|
|
136
|
+
return OPTIONAL unless defined?(ACP::Types::Unstable)
|
|
137
|
+
|
|
138
|
+
OPTIONAL + [
|
|
139
|
+
ACP::AgentConnection::OptionalMethod::ForkSession,
|
|
140
|
+
ACP::AgentConnection::OptionalMethod::ListProviders,
|
|
141
|
+
ACP::AgentConnection::OptionalMethod::SetProvider,
|
|
142
|
+
ACP::AgentConnection::OptionalMethod::DisableProvider
|
|
143
|
+
]
|
|
144
|
+
end
|
|
145
|
+
|
|
107
146
|
# @rbs agent: ACP::AgentConnection::_Agent
|
|
108
147
|
# @rbs return: Hash[String, ^(untyped) -> void]
|
|
109
148
|
def notifications(agent)
|
|
110
|
-
{ 'session/cancel' => ->(params) { cancel(agent, params) } }
|
|
149
|
+
routes = { 'session/cancel' => ->(params) { cancel(agent, params) } }
|
|
150
|
+
return routes unless mcp_acp_advertised?
|
|
151
|
+
|
|
152
|
+
routes.merge('mcp/message' => ->(params) { mcp_message_notification(agent, params) })
|
|
111
153
|
end
|
|
112
154
|
|
|
113
155
|
# A notification has no reply to carry a parse failure.
|
|
@@ -123,6 +165,34 @@ class ACP::AgentConnection
|
|
|
123
165
|
agent.cancel(notification)
|
|
124
166
|
end
|
|
125
167
|
|
|
168
|
+
# The unstable gates reference the unstable types, so they can only be
|
|
169
|
+
# probed when the caller opted in with `require 'acp/types/unstable'`; the
|
|
170
|
+
# stable McpCapabilities has no acp field at all.
|
|
171
|
+
#
|
|
172
|
+
# @rbs return: bool
|
|
173
|
+
def mcp_acp_advertised?
|
|
174
|
+
return false unless defined?(ACP::Types::Unstable)
|
|
175
|
+
|
|
176
|
+
mcp = @capabilities.mcp_capabilities #: ACP::Types::McpCapabilities | ACP::Types::Unstable::McpCapabilities?
|
|
177
|
+
return false unless mcp.is_a?(ACP::Types::Unstable::McpCapabilities)
|
|
178
|
+
|
|
179
|
+
mcp.acp ? true : false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# A notification has no reply to carry a parse failure.
|
|
183
|
+
#
|
|
184
|
+
# @rbs agent: ACP::AgentConnection::_Agent
|
|
185
|
+
# @rbs params: untyped
|
|
186
|
+
# @rbs return: void
|
|
187
|
+
def mcp_message_notification(agent, params)
|
|
188
|
+
notification = ACP::Types::Unstable::MessageMcpNotification.from_h(params)
|
|
189
|
+
rescue ACP::Types::ParseError, KeyError, TypeError, NoMethodError => e
|
|
190
|
+
@logger.warn("dropped malformed mcp/message: #{e.class}: #{e.message}")
|
|
191
|
+
else
|
|
192
|
+
hook = agent #: ACP::AgentConnection::_Agent & ACP::AgentConnection::_McpMessageNotification
|
|
193
|
+
hook.mcp_message_notification(notification)
|
|
194
|
+
end
|
|
195
|
+
|
|
126
196
|
# Generated from_h raises on a missing key or a value of the wrong shape.
|
|
127
197
|
#
|
|
128
198
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
@@ -15,6 +15,9 @@ class ACP::ClientConnection
|
|
|
15
15
|
# (ACP::Types::CreateElicitationResponse | ACP::RequestError))?
|
|
16
16
|
# @rbs @complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?
|
|
17
17
|
# @rbs @updates: ACP::ClientConnection::_UpdateHandler
|
|
18
|
+
# @rbs @mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) ->
|
|
19
|
+
# (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?
|
|
20
|
+
# @rbs @mcp_advertised: bool?
|
|
18
21
|
# @rbs @fs_capabilities: ACP::Types::FileSystemCapabilities?
|
|
19
22
|
# @rbs @terminal: bool?
|
|
20
23
|
# @rbs @elicitation_capabilities: ACP::Types::ElicitationCapabilities?
|
|
@@ -48,6 +51,8 @@ class ACP::ClientConnection
|
|
|
48
51
|
# (ACP::Types::CreateElicitationResponse | ACP::RequestError))?
|
|
49
52
|
# @rbs complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?
|
|
50
53
|
# @rbs updates: ACP::ClientConnection::_UpdateHandler
|
|
54
|
+
# @rbs mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) ->
|
|
55
|
+
# (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?
|
|
51
56
|
# @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
52
57
|
# @rbs extension_notifications: Hash[String, ^(untyped) -> void]
|
|
53
58
|
# @rbs logger: ACP::Transport::_Logger
|
|
@@ -65,6 +70,7 @@ class ACP::ClientConnection
|
|
|
65
70
|
elicitation: nil,
|
|
66
71
|
complete_elicitation: nil,
|
|
67
72
|
updates: IGNORE,
|
|
73
|
+
mcp_message: nil,
|
|
68
74
|
extension_requests: {},
|
|
69
75
|
extension_notifications: {},
|
|
70
76
|
logger: ACP::Transport::StderrLogger.new
|
|
@@ -81,6 +87,8 @@ class ACP::ClientConnection
|
|
|
81
87
|
@elicitation = elicitation
|
|
82
88
|
@complete_elicitation = complete_elicitation
|
|
83
89
|
@updates = updates
|
|
90
|
+
@mcp_message = mcp_message
|
|
91
|
+
@mcp_advertised = nil
|
|
84
92
|
@fs_capabilities = nil
|
|
85
93
|
@terminal = nil
|
|
86
94
|
@elicitation_capabilities = nil
|
|
@@ -108,7 +116,7 @@ class ACP::ClientConnection
|
|
|
108
116
|
'terminal/kill' => method(:kill_terminal),
|
|
109
117
|
'terminal/release' => method(:release_terminal),
|
|
110
118
|
'elicitation/create' => method(:create_elicitation)
|
|
111
|
-
}.merge(@extension_requests),
|
|
119
|
+
}.merge(mcp_requests).merge(@extension_requests),
|
|
112
120
|
notifications: { 'session/update' => method(:dispatch),
|
|
113
121
|
'elicitation/complete' => method(:complete_elicitation) }.merge(@extension_notifications)
|
|
114
122
|
)
|
|
@@ -141,16 +149,17 @@ class ACP::ClientConnection
|
|
|
141
149
|
@fs_capabilities = capabilities
|
|
142
150
|
@terminal = terminal
|
|
143
151
|
@elicitation_capabilities = elicitation
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
result = @transport.request('initialize', request.to_h)
|
|
153
|
+
response = parse(ACP::Types::InitializeResponse, result)
|
|
154
|
+
return response if response.is_a?(ACP::RequestError)
|
|
146
155
|
|
|
147
|
-
|
|
156
|
+
@mcp_advertised = agent_mcp_advertised?(result)
|
|
157
|
+
return response if response.protocol_version == PROTOCOL_VERSION
|
|
148
158
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
end
|
|
159
|
+
ACP::RequestError.unsupported_protocol_version(
|
|
160
|
+
requested: request.protocol_version,
|
|
161
|
+
returned: response.protocol_version
|
|
162
|
+
)
|
|
154
163
|
end
|
|
155
164
|
|
|
156
165
|
# @rbs request: ACP::Types::NewSessionRequest
|
|
@@ -200,6 +209,30 @@ class ACP::ClientConnection
|
|
|
200
209
|
parse(ACP::Types::DeleteSessionResponse, @transport.request('session/delete', request.to_h))
|
|
201
210
|
end
|
|
202
211
|
|
|
212
|
+
# @rbs request: ACP::Types::Unstable::ForkSessionRequest
|
|
213
|
+
# @rbs return: (ACP::Types::Unstable::ForkSessionResponse | ACP::RequestError)
|
|
214
|
+
def session_fork(request)
|
|
215
|
+
parse(ACP::Types::Unstable::ForkSessionResponse, @transport.request('session/fork', request.to_h))
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# @rbs request: ACP::Types::Unstable::ListProvidersRequest
|
|
219
|
+
# @rbs return: (ACP::Types::Unstable::ListProvidersResponse | ACP::RequestError)
|
|
220
|
+
def providers_list(request)
|
|
221
|
+
parse(ACP::Types::Unstable::ListProvidersResponse, @transport.request('providers/list', request.to_h))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# @rbs request: ACP::Types::Unstable::SetProviderRequest
|
|
225
|
+
# @rbs return: (ACP::Types::Unstable::SetProviderResponse | ACP::RequestError)
|
|
226
|
+
def providers_set(request)
|
|
227
|
+
parse(ACP::Types::Unstable::SetProviderResponse, @transport.request('providers/set', request.to_h))
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# @rbs request: ACP::Types::Unstable::DisableProviderRequest
|
|
231
|
+
# @rbs return: (ACP::Types::Unstable::DisableProviderResponse | ACP::RequestError)
|
|
232
|
+
def providers_disable(request)
|
|
233
|
+
parse(ACP::Types::Unstable::DisableProviderResponse, @transport.request('providers/disable', request.to_h))
|
|
234
|
+
end
|
|
235
|
+
|
|
203
236
|
# The spec requires a pending session/request_permission to be answered with
|
|
204
237
|
# the cancelled outcome once the turn is cancelled, so every queue registered
|
|
205
238
|
# for the session gets one before this returns. The notification goes first
|
|
@@ -252,6 +285,13 @@ class ACP::ClientConnection
|
|
|
252
285
|
@transport.request(method, params)
|
|
253
286
|
end
|
|
254
287
|
|
|
288
|
+
# Always false off a handler thread.
|
|
289
|
+
#
|
|
290
|
+
# @rbs return: bool
|
|
291
|
+
def cancelled?
|
|
292
|
+
!!@transport.cancellation&.cancelled?
|
|
293
|
+
end
|
|
294
|
+
|
|
255
295
|
# @rbs method: String
|
|
256
296
|
# @rbs params: untyped
|
|
257
297
|
# @rbs return: void
|
|
@@ -260,8 +300,44 @@ class ACP::ClientConnection
|
|
|
260
300
|
@transport.notify(method, params)
|
|
261
301
|
end
|
|
262
302
|
|
|
303
|
+
# Unlike the mcp/message serve route, this send is not gated: a notification
|
|
304
|
+
# has no reply to carry a refusal, and the agent drops what it did not
|
|
305
|
+
# advertise.
|
|
306
|
+
#
|
|
307
|
+
# @rbs notification: ACP::Types::Unstable::MessageMcpNotification
|
|
308
|
+
# @rbs return: void
|
|
309
|
+
def mcp_message_notification(notification)
|
|
310
|
+
@transport.notify('mcp/message', notification.to_h)
|
|
311
|
+
end
|
|
312
|
+
|
|
263
313
|
private
|
|
264
314
|
|
|
315
|
+
# The route is only registered when a handler serves it, but the agent's
|
|
316
|
+
# advertisement is only known after connect, so an unadvertised request
|
|
317
|
+
# still reaches the route and is refused there.
|
|
318
|
+
#
|
|
319
|
+
# @rbs return: Hash[String, ACP::Transport::Stdio::_Handler]
|
|
320
|
+
def mcp_requests
|
|
321
|
+
return {} unless @mcp_message
|
|
322
|
+
|
|
323
|
+
{ 'mcp/message' => method(:mcp_message) }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# The stable McpCapabilities has no acp field, so the gate re-reads the raw
|
|
327
|
+
# initialize reply with the unstable types when they are opted into.
|
|
328
|
+
#
|
|
329
|
+
# @rbs result: (Hash[String, untyped] | ACP::RequestError)
|
|
330
|
+
# @rbs return: bool
|
|
331
|
+
def agent_mcp_advertised?(result)
|
|
332
|
+
return false unless defined?(ACP::Types::Unstable)
|
|
333
|
+
return false unless result.is_a?(Hash)
|
|
334
|
+
|
|
335
|
+
mcp = ACP::Types::Unstable::McpCapabilities.from_h(result['agentCapabilities']['mcpCapabilities'] || {})
|
|
336
|
+
mcp.acp ? true : false
|
|
337
|
+
rescue ACP::Types::ParseError, KeyError, TypeError, NoMethodError
|
|
338
|
+
false
|
|
339
|
+
end
|
|
340
|
+
|
|
265
341
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
266
342
|
# @rbs result: (Hash[String, untyped] | ACP::RequestError)
|
|
267
343
|
# @rbs return: untyped
|
|
@@ -340,7 +416,11 @@ class ACP::ClientConnection
|
|
|
340
416
|
else
|
|
341
417
|
replies = Thread::Queue.new
|
|
342
418
|
register_permission(request.session_id, replies)
|
|
419
|
+
# The handler runs on this connection's own thread, so the serving
|
|
420
|
+
# identity must be carried over for cancelled? to see the cancel there.
|
|
421
|
+
serving = @transport.cancellation
|
|
343
422
|
Thread.new do
|
|
423
|
+
ACP::Transport::Cancellation.current = serving
|
|
344
424
|
# A raised StandardError is not a returned ACP::RequestError, so the two
|
|
345
425
|
# need distinct shapes on the queue.
|
|
346
426
|
replies << begin
|
|
@@ -490,6 +570,25 @@ class ACP::ClientConnection
|
|
|
490
570
|
handler.call(request)
|
|
491
571
|
end
|
|
492
572
|
|
|
573
|
+
# The route is registered at start, before connect records the agent's
|
|
574
|
+
# advertisement, so an unadvertised request answers -32601 until then.
|
|
575
|
+
#
|
|
576
|
+
# @rbs params: untyped
|
|
577
|
+
# @rbs return: (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
578
|
+
def mcp_message(params)
|
|
579
|
+
return ACP::RequestError.unadvertised_agent('mcpCapabilities.acp') unless @mcp_advertised
|
|
580
|
+
|
|
581
|
+
request = ACP::Types::Unstable::MessageMcpRequest.from_h(params)
|
|
582
|
+
rescue ACP::Types::ParseError => e
|
|
583
|
+
ACP::RequestError.invalid_params([e.message])
|
|
584
|
+
rescue KeyError, TypeError, NoMethodError
|
|
585
|
+
ACP::RequestError.invalid_params
|
|
586
|
+
else
|
|
587
|
+
# Safe: the route is only registered when a handler serves it.
|
|
588
|
+
handler = @mcp_message #: ^(ACP::Types::Unstable::MessageMcpRequest) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
589
|
+
handler.call(request)
|
|
590
|
+
end
|
|
591
|
+
|
|
493
592
|
# The routes are registered at start, before connect records the advertised
|
|
494
593
|
# capabilities, so a request for an unadvertised mode answers -32602.
|
|
495
594
|
#
|
data/lib/acp/request_error.rb
CHANGED
|
@@ -82,6 +82,16 @@ class ACP::RequestError
|
|
|
82
82
|
new(code: INVALID_PARAMS, message: "Client does not advertise #{mode}")
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# unadvertised names a capability of the client's, this one a capability of
|
|
86
|
+
# the agent's: the same method-not-found code, with a message naming which
|
|
87
|
+
# side's advertisement is missing.
|
|
88
|
+
#
|
|
89
|
+
# @rbs capability: String
|
|
90
|
+
# @rbs return: ACP::RequestError
|
|
91
|
+
def self.unadvertised_agent(capability)
|
|
92
|
+
new(code: METHOD_NOT_FOUND, message: "Agent does not advertise #{capability}")
|
|
93
|
+
end
|
|
94
|
+
|
|
85
95
|
# @rbs return: ACP::RequestError
|
|
86
96
|
def self.internal_error
|
|
87
97
|
new(code: INTERNAL_ERROR, message: 'Internal error')
|
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
# request, marks it when the peer's $/cancel_request arrives, and hands it to
|
|
5
5
|
# the serving thread, whose handler can observe it and end the request early.
|
|
6
6
|
class ACP::Transport::Cancellation
|
|
7
|
+
KEY = :acp_serving_cancellation #: Symbol
|
|
8
|
+
|
|
9
|
+
# Connections read this to expose the serving state to their handlers and
|
|
10
|
+
# write it to carry serving identity onto a thread they spawn; nil off a
|
|
11
|
+
# serve thread.
|
|
12
|
+
#
|
|
13
|
+
# @rbs return: ACP::Transport::Cancellation?
|
|
14
|
+
def self.current
|
|
15
|
+
Thread.current[KEY] #: ACP::Transport::Cancellation?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @rbs cancellation: ACP::Transport::Cancellation?
|
|
19
|
+
# @rbs return: void
|
|
20
|
+
def self.current=(cancellation)
|
|
21
|
+
Thread.current[KEY] = cancellation
|
|
22
|
+
end
|
|
23
|
+
|
|
7
24
|
# @rbs @lock: Thread::Mutex
|
|
8
25
|
# @rbs @cancelled: bool
|
|
9
26
|
|
data/lib/acp/transport/stdio.rb
CHANGED
|
@@ -22,9 +22,6 @@ class ACP::Transport::Stdio
|
|
|
22
22
|
code: ACP::RequestError::INTERNAL_ERROR, message: 'Connection closed'
|
|
23
23
|
) #: ACP::RequestError
|
|
24
24
|
|
|
25
|
-
SERVING_CANCEL = :acp_serving_cancellation #: Symbol
|
|
26
|
-
private_constant :SERVING_CANCEL
|
|
27
|
-
|
|
28
25
|
# @rbs input: _Reader
|
|
29
26
|
# @rbs output: _Writer
|
|
30
27
|
# @rbs logger: ACP::Transport::_Logger
|
|
@@ -85,6 +82,14 @@ class ACP::Transport::Stdio
|
|
|
85
82
|
notify('$/cancel_request', { 'requestId' => id }) if pending
|
|
86
83
|
end
|
|
87
84
|
|
|
85
|
+
# Each id is re-checked against the pending table, so one that settled
|
|
86
|
+
# between the snapshot and its cancel is not cancelled.
|
|
87
|
+
#
|
|
88
|
+
# @rbs return: void
|
|
89
|
+
def cancel_requests
|
|
90
|
+
@lock.synchronize { @pending.keys }.each { |id| cancel(id) }
|
|
91
|
+
end
|
|
92
|
+
|
|
88
93
|
# The cancellation of the request the calling thread is serving, or nil off
|
|
89
94
|
# a serve thread. A handler observes the peer's $/cancel_request through it
|
|
90
95
|
# and can end early with ACP::RequestError.request_cancelled; whatever it
|
|
@@ -92,7 +97,7 @@ class ACP::Transport::Stdio
|
|
|
92
97
|
#
|
|
93
98
|
# @rbs return: ACP::Transport::Cancellation?
|
|
94
99
|
def cancellation
|
|
95
|
-
|
|
100
|
+
ACP::Transport::Cancellation.current
|
|
96
101
|
end
|
|
97
102
|
|
|
98
103
|
private
|
|
@@ -167,7 +172,7 @@ class ACP::Transport::Stdio
|
|
|
167
172
|
def serve(handler, id, params, method)
|
|
168
173
|
cancellation = register_serving(id)
|
|
169
174
|
Thread.new do
|
|
170
|
-
|
|
175
|
+
ACP::Transport::Cancellation.current = cancellation
|
|
171
176
|
outcome = handler ? invoke(handler, params) : ACP::RequestError.method_not_found(method)
|
|
172
177
|
case outcome
|
|
173
178
|
when ACP::Transport::Reply
|
data/lib/acp/version.rb
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# Generated from lib/acp/agent_connection/client.rb with RBS::Inline
|
|
2
2
|
|
|
3
3
|
class ACP::AgentConnection::Client
|
|
4
|
+
@mcp_advertised: bool
|
|
5
|
+
|
|
4
6
|
@peer: ACP::AgentConnection::_Peer
|
|
5
7
|
|
|
6
8
|
# @dynamic capabilities, capabilities=
|
|
7
9
|
attr_accessor capabilities: ACP::Types::ClientCapabilities?
|
|
8
10
|
|
|
9
11
|
# @rbs peer: ACP::AgentConnection::_Peer
|
|
12
|
+
# @rbs mcp_advertised: bool
|
|
10
13
|
# @rbs return: void
|
|
11
|
-
def initialize: (peer: ACP::AgentConnection::_Peer) -> void
|
|
14
|
+
def initialize: (peer: ACP::AgentConnection::_Peer, ?mcp_advertised: bool) -> void
|
|
12
15
|
|
|
13
16
|
# @rbs session_id: String
|
|
14
17
|
# @rbs update: ACP::Types::SessionUpdate::t
|
|
@@ -27,6 +30,14 @@ class ACP::AgentConnection::Client
|
|
|
27
30
|
# @rbs return: ACP::Types::RequestPermissionResponse | ACP::RequestError
|
|
28
31
|
def request_permission: (ACP::Types::RequestPermissionRequest request) -> (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
29
32
|
|
|
33
|
+
# @rbs return: void
|
|
34
|
+
def cancel_requests: () -> void
|
|
35
|
+
|
|
36
|
+
# Always false off a handler thread.
|
|
37
|
+
#
|
|
38
|
+
# @rbs return: bool
|
|
39
|
+
def cancelled?: () -> bool
|
|
40
|
+
|
|
30
41
|
# @rbs request: ACP::Types::ReadTextFileRequest
|
|
31
42
|
# @rbs return: ACP::Types::ReadTextFileResponse | ACP::RequestError
|
|
32
43
|
def read_text_file: (ACP::Types::ReadTextFileRequest request) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError)
|
|
@@ -63,6 +74,14 @@ class ACP::AgentConnection::Client
|
|
|
63
74
|
# @rbs return: void
|
|
64
75
|
def complete_elicitation: (ACP::Types::CompleteElicitationNotification notification) -> void
|
|
65
76
|
|
|
77
|
+
# Sends one inner MCP operation to the client's declared ACP MCP server. The
|
|
78
|
+
# reply's result may be null, and a reply matching neither carrier branch
|
|
79
|
+
# comes back as the raw hash.
|
|
80
|
+
#
|
|
81
|
+
# @rbs request: ACP::Types::Unstable::MessageMcpRequest
|
|
82
|
+
# @rbs return: (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
83
|
+
def mcp_message: (ACP::Types::Unstable::MessageMcpRequest request) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
84
|
+
|
|
66
85
|
# Sends an extension request, keyed by its raw `_`-prefixed wire name, and
|
|
67
86
|
# returns the client's reply as-is: extension methods have no schema to
|
|
68
87
|
# parse the result into.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated from lib/acp/agent_connection/optional_method/disable_provider.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::DisableProvider
|
|
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/fork_session.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::ForkSession
|
|
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_providers.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::ListProviders
|
|
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/set_provider.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ACP::AgentConnection::OptionalMethod::SetProvider
|
|
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
|
|
@@ -47,6 +47,21 @@ class ACP::AgentConnection
|
|
|
47
47
|
# @rbs return: Hash[String, ^(untyped) -> (ACP::AgentConnection::_Response | ACP::RequestError | ACP::Transport::Reply)]
|
|
48
48
|
def requests: (ACP::AgentConnection::_Agent agent, ACP::AgentConnection::Client client) -> Hash[String, ^(untyped) -> (ACP::AgentConnection::_Response | ACP::RequestError | ACP::Transport::Reply)]
|
|
49
49
|
|
|
50
|
+
# The unstable request types are referenced while the hash is built, so
|
|
51
|
+
# without the guard start would raise NameError even for an agent that never
|
|
52
|
+
# advertises fork or providers.
|
|
53
|
+
#
|
|
54
|
+
# @rbs full: ACP::AgentConnection::_FullAgent
|
|
55
|
+
# @rbs return: Hash[String, ^(untyped) -> (ACP::AgentConnection::_Response | ACP::RequestError | ACP::Transport::Reply)]
|
|
56
|
+
def unstable_requests: (ACP::AgentConnection::_FullAgent full) -> Hash[String, ^(untyped) -> (ACP::AgentConnection::_Response | ACP::RequestError | ACP::Transport::Reply)]
|
|
57
|
+
|
|
58
|
+
# ForkSession's and the provider methods' advertised? reference the unstable
|
|
59
|
+
# types, so they can only be probed when the caller opted in with
|
|
60
|
+
# `require 'acp/types/unstable'`.
|
|
61
|
+
#
|
|
62
|
+
# @rbs return: Array[ACP::AgentConnection::_OptionalMethod]
|
|
63
|
+
def optional_methods: () -> Array[ACP::AgentConnection::_OptionalMethod]
|
|
64
|
+
|
|
50
65
|
# @rbs agent: ACP::AgentConnection::_Agent
|
|
51
66
|
# @rbs return: Hash[String, ^(untyped) -> void]
|
|
52
67
|
def notifications: (ACP::AgentConnection::_Agent agent) -> Hash[String, ^(untyped) -> void]
|
|
@@ -58,6 +73,20 @@ class ACP::AgentConnection
|
|
|
58
73
|
# @rbs return: void
|
|
59
74
|
def cancel: (ACP::AgentConnection::_Agent agent, untyped params) -> void
|
|
60
75
|
|
|
76
|
+
# The unstable gates reference the unstable types, so they can only be
|
|
77
|
+
# probed when the caller opted in with `require 'acp/types/unstable'`; the
|
|
78
|
+
# stable McpCapabilities has no acp field at all.
|
|
79
|
+
#
|
|
80
|
+
# @rbs return: bool
|
|
81
|
+
def mcp_acp_advertised?: () -> bool
|
|
82
|
+
|
|
83
|
+
# A notification has no reply to carry a parse failure.
|
|
84
|
+
#
|
|
85
|
+
# @rbs agent: ACP::AgentConnection::_Agent
|
|
86
|
+
# @rbs params: untyped
|
|
87
|
+
# @rbs return: void
|
|
88
|
+
def mcp_message_notification: (ACP::AgentConnection::_Agent agent, untyped params) -> void
|
|
89
|
+
|
|
61
90
|
# Generated from_h raises on a missing key or a value of the wrong shape.
|
|
62
91
|
#
|
|
63
92
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
@@ -25,6 +25,10 @@ class ACP::ClientConnection
|
|
|
25
25
|
|
|
26
26
|
@updates: ACP::ClientConnection::_UpdateHandler
|
|
27
27
|
|
|
28
|
+
@mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?
|
|
29
|
+
|
|
30
|
+
@mcp_advertised: bool?
|
|
31
|
+
|
|
28
32
|
@fs_capabilities: ACP::Types::FileSystemCapabilities?
|
|
29
33
|
|
|
30
34
|
@terminal: bool?
|
|
@@ -65,11 +69,13 @@ class ACP::ClientConnection
|
|
|
65
69
|
# (ACP::Types::CreateElicitationResponse | ACP::RequestError))?
|
|
66
70
|
# @rbs complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?
|
|
67
71
|
# @rbs updates: ACP::ClientConnection::_UpdateHandler
|
|
72
|
+
# @rbs mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) ->
|
|
73
|
+
# (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?
|
|
68
74
|
# @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
|
|
69
75
|
# @rbs extension_notifications: Hash[String, ^(untyped) -> void]
|
|
70
76
|
# @rbs logger: ACP::Transport::_Logger
|
|
71
77
|
# @rbs return: void
|
|
72
|
-
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))?, ?create_terminal: (^(ACP::Types::CreateTerminalRequest) -> (ACP::Types::CreateTerminalResponse | ACP::RequestError))?, ?terminal_output: (^(ACP::Types::TerminalOutputRequest) -> (ACP::Types::TerminalOutputResponse | ACP::RequestError))?, ?wait_for_terminal_exit: (^(ACP::Types::WaitForTerminalExitRequest) -> (ACP::Types::WaitForTerminalExitResponse | ACP::RequestError))?, ?kill_terminal: (^(ACP::Types::KillTerminalRequest) -> (ACP::Types::KillTerminalResponse | ACP::RequestError))?, ?release_terminal: (^(ACP::Types::ReleaseTerminalRequest) -> (ACP::Types::ReleaseTerminalResponse | ACP::RequestError))?, ?elicitation: (^(ACP::Types::CreateElicitationRequest) -> (ACP::Types::CreateElicitationResponse | ACP::RequestError))?, ?complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?, ?updates: ACP::ClientConnection::_UpdateHandler, ?extension_requests: Hash[String, ^(untyped) -> untyped], ?extension_notifications: Hash[String, ^(untyped) -> void], ?logger: ACP::Transport::_Logger) -> void
|
|
78
|
+
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))?, ?create_terminal: (^(ACP::Types::CreateTerminalRequest) -> (ACP::Types::CreateTerminalResponse | ACP::RequestError))?, ?terminal_output: (^(ACP::Types::TerminalOutputRequest) -> (ACP::Types::TerminalOutputResponse | ACP::RequestError))?, ?wait_for_terminal_exit: (^(ACP::Types::WaitForTerminalExitRequest) -> (ACP::Types::WaitForTerminalExitResponse | ACP::RequestError))?, ?kill_terminal: (^(ACP::Types::KillTerminalRequest) -> (ACP::Types::KillTerminalResponse | ACP::RequestError))?, ?release_terminal: (^(ACP::Types::ReleaseTerminalRequest) -> (ACP::Types::ReleaseTerminalResponse | ACP::RequestError))?, ?elicitation: (^(ACP::Types::CreateElicitationRequest) -> (ACP::Types::CreateElicitationResponse | ACP::RequestError))?, ?complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?, ?updates: ACP::ClientConnection::_UpdateHandler, ?mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?, ?extension_requests: Hash[String, ^(untyped) -> untyped], ?extension_notifications: Hash[String, ^(untyped) -> void], ?logger: ACP::Transport::_Logger) -> void
|
|
73
79
|
|
|
74
80
|
# @rbs return: Thread
|
|
75
81
|
def start: () -> Thread
|
|
@@ -114,6 +120,22 @@ class ACP::ClientConnection
|
|
|
114
120
|
# @rbs return: ACP::Types::DeleteSessionResponse | ACP::RequestError
|
|
115
121
|
def session_delete: (ACP::Types::DeleteSessionRequest request) -> (ACP::Types::DeleteSessionResponse | ACP::RequestError)
|
|
116
122
|
|
|
123
|
+
# @rbs request: ACP::Types::Unstable::ForkSessionRequest
|
|
124
|
+
# @rbs return: (ACP::Types::Unstable::ForkSessionResponse | ACP::RequestError)
|
|
125
|
+
def session_fork: (ACP::Types::Unstable::ForkSessionRequest request) -> (ACP::Types::Unstable::ForkSessionResponse | ACP::RequestError)
|
|
126
|
+
|
|
127
|
+
# @rbs request: ACP::Types::Unstable::ListProvidersRequest
|
|
128
|
+
# @rbs return: (ACP::Types::Unstable::ListProvidersResponse | ACP::RequestError)
|
|
129
|
+
def providers_list: (ACP::Types::Unstable::ListProvidersRequest request) -> (ACP::Types::Unstable::ListProvidersResponse | ACP::RequestError)
|
|
130
|
+
|
|
131
|
+
# @rbs request: ACP::Types::Unstable::SetProviderRequest
|
|
132
|
+
# @rbs return: (ACP::Types::Unstable::SetProviderResponse | ACP::RequestError)
|
|
133
|
+
def providers_set: (ACP::Types::Unstable::SetProviderRequest request) -> (ACP::Types::Unstable::SetProviderResponse | ACP::RequestError)
|
|
134
|
+
|
|
135
|
+
# @rbs request: ACP::Types::Unstable::DisableProviderRequest
|
|
136
|
+
# @rbs return: (ACP::Types::Unstable::DisableProviderResponse | ACP::RequestError)
|
|
137
|
+
def providers_disable: (ACP::Types::Unstable::DisableProviderRequest request) -> (ACP::Types::Unstable::DisableProviderResponse | ACP::RequestError)
|
|
138
|
+
|
|
117
139
|
# The spec requires a pending session/request_permission to be answered with
|
|
118
140
|
# the cancelled outcome once the turn is cancelled, so every queue registered
|
|
119
141
|
# for the session gets one before this returns. The notification goes first
|
|
@@ -151,13 +173,40 @@ class ACP::ClientConnection
|
|
|
151
173
|
# @rbs return: (Hash[String, untyped] | ACP::RequestError)
|
|
152
174
|
def ext_request: (String method, ?untyped params) -> (Hash[String, untyped] | ACP::RequestError)
|
|
153
175
|
|
|
176
|
+
# Always false off a handler thread.
|
|
177
|
+
#
|
|
178
|
+
# @rbs return: bool
|
|
179
|
+
def cancelled?: () -> bool
|
|
180
|
+
|
|
154
181
|
# @rbs method: String
|
|
155
182
|
# @rbs params: untyped
|
|
156
183
|
# @rbs return: void
|
|
157
184
|
def ext_notify: (String method, ?untyped params) -> void
|
|
158
185
|
|
|
186
|
+
# Unlike the mcp/message serve route, this send is not gated: a notification
|
|
187
|
+
# has no reply to carry a refusal, and the agent drops what it did not
|
|
188
|
+
# advertise.
|
|
189
|
+
#
|
|
190
|
+
# @rbs notification: ACP::Types::Unstable::MessageMcpNotification
|
|
191
|
+
# @rbs return: void
|
|
192
|
+
def mcp_message_notification: (ACP::Types::Unstable::MessageMcpNotification notification) -> void
|
|
193
|
+
|
|
159
194
|
private
|
|
160
195
|
|
|
196
|
+
# The route is only registered when a handler serves it, but the agent's
|
|
197
|
+
# advertisement is only known after connect, so an unadvertised request
|
|
198
|
+
# still reaches the route and is refused there.
|
|
199
|
+
#
|
|
200
|
+
# @rbs return: Hash[String, ACP::Transport::Stdio::_Handler]
|
|
201
|
+
def mcp_requests: () -> Hash[String, ACP::Transport::Stdio::_Handler]
|
|
202
|
+
|
|
203
|
+
# The stable McpCapabilities has no acp field, so the gate re-reads the raw
|
|
204
|
+
# initialize reply with the unstable types when they are opted into.
|
|
205
|
+
#
|
|
206
|
+
# @rbs result: (Hash[String, untyped] | ACP::RequestError)
|
|
207
|
+
# @rbs return: bool
|
|
208
|
+
def agent_mcp_advertised?: (Hash[String, untyped] | ACP::RequestError result) -> bool
|
|
209
|
+
|
|
161
210
|
# @rbs type: ACP::AgentConnection::_Parser
|
|
162
211
|
# @rbs result: (Hash[String, untyped] | ACP::RequestError)
|
|
163
212
|
# @rbs return: untyped
|
|
@@ -237,6 +286,13 @@ class ACP::ClientConnection
|
|
|
237
286
|
# @rbs return: (ACP::Types::ReleaseTerminalResponse | ACP::RequestError)
|
|
238
287
|
def release_terminal: (untyped params) -> (ACP::Types::ReleaseTerminalResponse | ACP::RequestError)
|
|
239
288
|
|
|
289
|
+
# The route is registered at start, before connect records the agent's
|
|
290
|
+
# advertisement, so an unadvertised request answers -32601 until then.
|
|
291
|
+
#
|
|
292
|
+
# @rbs params: untyped
|
|
293
|
+
# @rbs return: (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
294
|
+
def mcp_message: (untyped params) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
295
|
+
|
|
240
296
|
# The routes are registered at start, before connect records the advertised
|
|
241
297
|
# capabilities, so a request for an unadvertised mode answers -32602.
|
|
242
298
|
#
|
|
@@ -70,6 +70,14 @@ class ACP::RequestError
|
|
|
70
70
|
# @rbs return: ACP::RequestError
|
|
71
71
|
def self.unadvertised_mode: (String mode) -> ACP::RequestError
|
|
72
72
|
|
|
73
|
+
# unadvertised names a capability of the client's, this one a capability of
|
|
74
|
+
# the agent's: the same method-not-found code, with a message naming which
|
|
75
|
+
# side's advertisement is missing.
|
|
76
|
+
#
|
|
77
|
+
# @rbs capability: String
|
|
78
|
+
# @rbs return: ACP::RequestError
|
|
79
|
+
def self.unadvertised_agent: (String capability) -> ACP::RequestError
|
|
80
|
+
|
|
73
81
|
# @rbs return: ACP::RequestError
|
|
74
82
|
def self.internal_error: () -> ACP::RequestError
|
|
75
83
|
|
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
# request, marks it when the peer's $/cancel_request arrives, and hands it to
|
|
5
5
|
# the serving thread, whose handler can observe it and end the request early.
|
|
6
6
|
class ACP::Transport::Cancellation
|
|
7
|
+
KEY: Symbol
|
|
8
|
+
|
|
9
|
+
# Connections read this to expose the serving state to their handlers and
|
|
10
|
+
# write it to carry serving identity onto a thread they spawn; nil off a
|
|
11
|
+
# serve thread.
|
|
12
|
+
#
|
|
13
|
+
# @rbs return: ACP::Transport::Cancellation?
|
|
14
|
+
def self.current: () -> ACP::Transport::Cancellation?
|
|
15
|
+
|
|
16
|
+
# @rbs cancellation: ACP::Transport::Cancellation?
|
|
17
|
+
# @rbs return: void
|
|
18
|
+
def self.current=: (ACP::Transport::Cancellation? cancellation) -> void
|
|
19
|
+
|
|
7
20
|
@lock: Thread::Mutex
|
|
8
21
|
|
|
9
22
|
@cancelled: bool
|
|
@@ -26,8 +26,6 @@ class ACP::Transport::Stdio
|
|
|
26
26
|
|
|
27
27
|
CONNECTION_CLOSED: ACP::RequestError
|
|
28
28
|
|
|
29
|
-
SERVING_CANCEL: Symbol
|
|
30
|
-
|
|
31
29
|
# @rbs input: _Reader
|
|
32
30
|
# @rbs output: _Writer
|
|
33
31
|
# @rbs logger: ACP::Transport::_Logger
|
|
@@ -59,6 +57,12 @@ class ACP::Transport::Stdio
|
|
|
59
57
|
# @rbs return: void
|
|
60
58
|
def cancel: (untyped id) -> void
|
|
61
59
|
|
|
60
|
+
# Each id is re-checked against the pending table, so one that settled
|
|
61
|
+
# between the snapshot and its cancel is not cancelled.
|
|
62
|
+
#
|
|
63
|
+
# @rbs return: void
|
|
64
|
+
def cancel_requests: () -> void
|
|
65
|
+
|
|
62
66
|
# The cancellation of the request the calling thread is serving, or nil off
|
|
63
67
|
# a serve thread. A handler observes the peer's $/cancel_request through it
|
|
64
68
|
# and can end early with ACP::RequestError.request_cancelled; whatever it
|
|
@@ -36,6 +36,22 @@ class ACP::AgentConnection
|
|
|
36
36
|
def logout: (ACP::Types::LogoutRequest) -> (ACP::Types::LogoutResponse | ACP::RequestError)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
interface _ForkSession
|
|
40
|
+
def fork_session: (ACP::Types::Unstable::ForkSessionRequest) -> (ACP::Types::Unstable::ForkSessionResponse | ACP::RequestError)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
interface _ListProviders
|
|
44
|
+
def list_providers: (ACP::Types::Unstable::ListProvidersRequest) -> (ACP::Types::Unstable::ListProvidersResponse | ACP::RequestError)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
interface _SetProvider
|
|
48
|
+
def set_provider: (ACP::Types::Unstable::SetProviderRequest) -> (ACP::Types::Unstable::SetProviderResponse | ACP::RequestError)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
interface _DisableProvider
|
|
52
|
+
def disable_provider: (ACP::Types::Unstable::DisableProviderRequest) -> (ACP::Types::Unstable::DisableProviderResponse | ACP::RequestError)
|
|
53
|
+
end
|
|
54
|
+
|
|
39
55
|
interface _ChangeSessionMode
|
|
40
56
|
def change_session_mode: (ACP::Types::SetSessionModeRequest) -> (ACP::Types::SetSessionModeResponse | ACP::RequestError)
|
|
41
57
|
end
|
|
@@ -53,6 +69,10 @@ class ACP::AgentConnection
|
|
|
53
69
|
include _CloseSession
|
|
54
70
|
include _DeleteSession
|
|
55
71
|
include _Logout
|
|
72
|
+
include _ForkSession
|
|
73
|
+
include _ListProviders
|
|
74
|
+
include _SetProvider
|
|
75
|
+
include _DisableProvider
|
|
56
76
|
include _ChangeSessionMode
|
|
57
77
|
include _ChangeSessionConfigOption
|
|
58
78
|
end
|
|
@@ -67,11 +87,18 @@ class ACP::AgentConnection
|
|
|
67
87
|
def session_created: (ACP::Types::NewSessionResponse) -> void
|
|
68
88
|
end
|
|
69
89
|
|
|
90
|
+
interface _McpMessageNotification
|
|
91
|
+
# Runs on the transport's reader thread, so it must return quickly.
|
|
92
|
+
def mcp_message_notification: (ACP::Types::Unstable::MessageMcpNotification notification) -> void
|
|
93
|
+
end
|
|
94
|
+
|
|
70
95
|
# What an agent sees of ACP::AgentConnection::Client, so tests can pass a fake.
|
|
71
96
|
interface _Client
|
|
72
97
|
def update: (String session_id, ACP::Types::SessionUpdate::t update) -> void
|
|
73
98
|
def available_commands: (String session_id, Array[ACP::Types::AvailableCommand] commands) -> void
|
|
74
99
|
def request_permission: (ACP::Types::RequestPermissionRequest) -> (ACP::Types::RequestPermissionResponse | ACP::RequestError)
|
|
100
|
+
def cancel_requests: () -> void
|
|
101
|
+
def cancelled?: () -> bool
|
|
75
102
|
def read_text_file: (ACP::Types::ReadTextFileRequest) -> (ACP::Types::ReadTextFileResponse | ACP::RequestError)
|
|
76
103
|
def write_text_file: (ACP::Types::WriteTextFileRequest) -> (ACP::Types::WriteTextFileResponse | ACP::RequestError)
|
|
77
104
|
def create_terminal: (ACP::Types::CreateTerminalRequest) -> (ACP::Types::CreateTerminalResponse | ACP::RequestError)
|
|
@@ -81,6 +108,7 @@ class ACP::AgentConnection
|
|
|
81
108
|
def release_terminal: (ACP::Types::ReleaseTerminalRequest) -> (ACP::Types::ReleaseTerminalResponse | ACP::RequestError)
|
|
82
109
|
def create_elicitation: (ACP::Types::CreateElicitationRequest request) -> (ACP::Types::CreateElicitationResponse | ACP::RequestError)
|
|
83
110
|
def complete_elicitation: (ACP::Types::CompleteElicitationNotification notification) -> void
|
|
111
|
+
def mcp_message: (ACP::Types::Unstable::MessageMcpRequest request) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
|
|
84
112
|
def ext_request: (String method, ?untyped params) -> (Hash[String, untyped] | ACP::RequestError)
|
|
85
113
|
def ext_notify: (String method, ?untyped params) -> void
|
|
86
114
|
def capabilities: () -> ACP::Types::ClientCapabilities?
|
|
@@ -89,6 +117,8 @@ class ACP::AgentConnection
|
|
|
89
117
|
interface _Peer
|
|
90
118
|
def request: (String method, ?untyped params) -> (Hash[String, untyped] | ACP::RequestError)
|
|
91
119
|
def notify: (String method, ?untyped params) -> void
|
|
120
|
+
def cancellation: () -> ACP::Transport::Cancellation?
|
|
121
|
+
def cancel_requests: () -> void
|
|
92
122
|
end
|
|
93
123
|
|
|
94
124
|
interface _Transport
|
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.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jake Bottrall
|
|
@@ -38,10 +38,14 @@ files:
|
|
|
38
38
|
- lib/acp/agent_connection/optional_method/authenticate.rb
|
|
39
39
|
- lib/acp/agent_connection/optional_method/close_session.rb
|
|
40
40
|
- lib/acp/agent_connection/optional_method/delete_session.rb
|
|
41
|
+
- lib/acp/agent_connection/optional_method/disable_provider.rb
|
|
42
|
+
- lib/acp/agent_connection/optional_method/fork_session.rb
|
|
43
|
+
- lib/acp/agent_connection/optional_method/list_providers.rb
|
|
41
44
|
- lib/acp/agent_connection/optional_method/list_sessions.rb
|
|
42
45
|
- lib/acp/agent_connection/optional_method/load_session.rb
|
|
43
46
|
- lib/acp/agent_connection/optional_method/logout.rb
|
|
44
47
|
- lib/acp/agent_connection/optional_method/resume_session.rb
|
|
48
|
+
- lib/acp/agent_connection/optional_method/set_provider.rb
|
|
45
49
|
- lib/acp/agent_process.rb
|
|
46
50
|
- lib/acp/client_connection.rb
|
|
47
51
|
- lib/acp/extensions.rb
|
|
@@ -398,10 +402,14 @@ files:
|
|
|
398
402
|
- sig/generated/acp/agent_connection/optional_method/authenticate.rbs
|
|
399
403
|
- sig/generated/acp/agent_connection/optional_method/close_session.rbs
|
|
400
404
|
- sig/generated/acp/agent_connection/optional_method/delete_session.rbs
|
|
405
|
+
- sig/generated/acp/agent_connection/optional_method/disable_provider.rbs
|
|
406
|
+
- sig/generated/acp/agent_connection/optional_method/fork_session.rbs
|
|
407
|
+
- sig/generated/acp/agent_connection/optional_method/list_providers.rbs
|
|
401
408
|
- sig/generated/acp/agent_connection/optional_method/list_sessions.rbs
|
|
402
409
|
- sig/generated/acp/agent_connection/optional_method/load_session.rbs
|
|
403
410
|
- sig/generated/acp/agent_connection/optional_method/logout.rbs
|
|
404
411
|
- sig/generated/acp/agent_connection/optional_method/resume_session.rbs
|
|
412
|
+
- sig/generated/acp/agent_connection/optional_method/set_provider.rbs
|
|
405
413
|
- sig/generated/acp/agent_process.rbs
|
|
406
414
|
- sig/generated/acp/client_connection.rbs
|
|
407
415
|
- sig/generated/acp/extensions.rbs
|
|
@@ -756,10 +764,14 @@ files:
|
|
|
756
764
|
- sig/manual/acp/agent_connection/optional_method/authenticate.rbs
|
|
757
765
|
- sig/manual/acp/agent_connection/optional_method/close_session.rbs
|
|
758
766
|
- sig/manual/acp/agent_connection/optional_method/delete_session.rbs
|
|
767
|
+
- sig/manual/acp/agent_connection/optional_method/disable_provider.rbs
|
|
768
|
+
- sig/manual/acp/agent_connection/optional_method/fork_session.rbs
|
|
769
|
+
- sig/manual/acp/agent_connection/optional_method/list_providers.rbs
|
|
759
770
|
- sig/manual/acp/agent_connection/optional_method/list_sessions.rbs
|
|
760
771
|
- sig/manual/acp/agent_connection/optional_method/load_session.rbs
|
|
761
772
|
- sig/manual/acp/agent_connection/optional_method/logout.rbs
|
|
762
773
|
- sig/manual/acp/agent_connection/optional_method/resume_session.rbs
|
|
774
|
+
- sig/manual/acp/agent_connection/optional_method/set_provider.rbs
|
|
763
775
|
- sig/manual/acp/agent_process.rbs
|
|
764
776
|
- sig/manual/acp/client_connection.rbs
|
|
765
777
|
- sig/manual/acp/extensions.rbs
|
|
@@ -779,7 +791,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
779
791
|
requirements:
|
|
780
792
|
- - ">="
|
|
781
793
|
- !ruby/object:Gem::Version
|
|
782
|
-
version: '4
|
|
794
|
+
version: '3.4'
|
|
783
795
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
784
796
|
requirements:
|
|
785
797
|
- - ">="
|