acp-sdk 0.9.0 → 0.11.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 +32 -0
- data/README.md +20 -8
- data/lib/acp/agent_connection/client.rb +31 -13
- data/lib/acp/agent_connection/optional_method/spec.rb +42 -0
- data/lib/acp/agent_connection/optional_method.rb +36 -0
- data/lib/acp/agent_connection.rb +67 -20
- data/lib/acp/agent_process.rb +0 -13
- data/lib/acp/client_connection/streams.rb +183 -0
- data/lib/acp/client_connection.rb +280 -338
- data/lib/acp/protocol.rb +33 -0
- data/lib/acp/request_error.rb +13 -13
- data/lib/acp/transport/cancellation.rb +13 -3
- data/lib/acp/transport/reply.rb +0 -2
- data/lib/acp/transport/stderr_logger.rb +1 -2
- data/lib/acp/transport/stdio.rb +25 -26
- data/lib/acp/types/audio_content.rb +3 -46
- data/lib/acp/types/auth_method_terminal.rb +3 -59
- data/lib/acp/types/available_commands_update.rb +3 -32
- data/lib/acp/types/boolean_property_schema.rb +3 -41
- data/lib/acp/types/check.rb +210 -0
- data/lib/acp/types/config_option_update.rb +3 -32
- data/lib/acp/types/content.rb +3 -29
- data/lib/acp/types/{elicitation_url_mode → create_elicitation_request/mode/form}/scope.rb +1 -1
- data/lib/acp/types/create_elicitation_request/mode/form.rb +3 -3
- data/lib/acp/types/{elicitation_form_mode → create_elicitation_request/mode/url}/scope.rb +1 -1
- data/lib/acp/types/create_elicitation_request/mode/url.rb +3 -3
- data/lib/acp/types/current_mode_update.rb +3 -29
- data/lib/acp/types/diff.rb +3 -41
- data/lib/acp/types/elicitation_accept_action.rb +3 -26
- data/lib/acp/types/elicitation_form_mode.rb +3 -33
- data/lib/acp/types/elicitation_url_mode.rb +3 -35
- data/lib/acp/types/embedded_resource.rb +3 -44
- data/lib/acp/types/image_content.rb +3 -58
- data/lib/acp/types/integer_property_schema.rb +3 -60
- data/lib/acp/types/mcp_server_http.rb +3 -44
- data/lib/acp/types/mcp_server_sse.rb +3 -44
- data/lib/acp/types/multi_select_property_schema.rb +3 -70
- data/lib/acp/types/number_property_schema.rb +3 -60
- data/lib/acp/types/parse_error.rb +37 -0
- data/lib/acp/types/plan.rb +3 -29
- data/lib/acp/types/resource_link.rb +3 -79
- data/lib/acp/types/selected_permission_outcome.rb +3 -26
- data/lib/acp/types/session_config_boolean.rb +3 -21
- data/lib/acp/types/session_config_select.rb +3 -33
- data/lib/acp/types/session_info_update.rb +3 -42
- data/lib/acp/types/string_multi_select_items.rb +3 -29
- data/lib/acp/types/string_property_schema.rb +3 -105
- data/lib/acp/types/terminal.rb +3 -29
- data/lib/acp/types/text_content.rb +3 -40
- data/lib/acp/types/tool_call.rb +3 -115
- data/lib/acp/types/unstable/compaction_summary_chunk.rb +3 -35
- data/lib/acp/types/unstable/compaction_update.rb +3 -56
- data/lib/acp/types/unstable/idle_state_update.rb +3 -40
- data/lib/acp/types/unstable/mcp_server_acp.rb +3 -35
- data/lib/acp/types/unstable/message_mcp_response/result.rb +1 -1
- data/lib/acp/types/unstable/nes_edit_suggestion.rb +3 -61
- data/lib/acp/types/unstable/nes_jump_suggestion.rb +3 -45
- data/lib/acp/types/unstable/nes_rename_suggestion.rb +3 -51
- data/lib/acp/types/unstable/nes_search_and_replace_suggestion.rb +3 -60
- data/lib/acp/types/unstable/notice.rb +3 -41
- data/lib/acp/types/unstable/plan_file.rb +3 -35
- data/lib/acp/types/unstable/plan_items.rb +3 -38
- data/lib/acp/types/unstable/plan_markdown.rb +3 -35
- data/lib/acp/types/unstable/plan_removed.rb +3 -26
- data/lib/acp/types/unstable/plan_update.rb +3 -33
- data/lib/acp/types/unstable/requires_action_state_update.rb +3 -21
- data/lib/acp/types/unstable/running_state_update.rb +3 -21
- data/lib/acp/types/unstable/session_message.rb +3 -56
- data/lib/acp/types/unstable/session_message_chunk.rb +3 -53
- data/lib/acp/types/unstable/subagent_update.rb +3 -65
- data/lib/acp/types/unstable/unknown_state_update.rb +3 -21
- data/lib/acp/types/unstable.rb +20 -20
- data/lib/acp/types/usage_update.rb +3 -41
- data/lib/acp/types.rb +7 -246
- data/lib/acp/version.rb +1 -1
- data/sig/generated/acp/agent_connection/client.rbs +25 -15
- data/sig/generated/acp/agent_connection/optional_method/spec.rbs +32 -0
- data/sig/generated/acp/agent_connection/optional_method.rbs +3 -0
- data/sig/generated/acp/agent_connection.rbs +34 -13
- data/sig/generated/acp/agent_process.rbs +0 -13
- data/sig/generated/acp/client_connection/streams.rbs +94 -0
- data/sig/generated/acp/client_connection.rbs +128 -131
- data/sig/generated/acp/protocol.rbs +9 -0
- data/sig/generated/acp/request_error.rbs +11 -13
- data/sig/generated/acp/transport/cancellation.rbs +9 -3
- data/sig/generated/acp/transport/reply.rbs +0 -2
- data/sig/generated/acp/transport/stderr_logger.rbs +1 -2
- data/sig/generated/acp/transport/stdio.rbs +14 -22
- data/sig/generated/acp/types/audio_content.rbs +1 -27
- data/sig/generated/acp/types/auth_method_terminal.rbs +1 -35
- data/sig/generated/acp/types/available_commands_update.rbs +1 -19
- data/sig/generated/acp/types/boolean_property_schema.rbs +1 -27
- data/sig/generated/acp/types/check.rbs +115 -0
- data/sig/generated/acp/types/config_option_update.rbs +1 -19
- data/sig/generated/acp/types/content.rbs +1 -19
- data/sig/generated/acp/types/{elicitation_url_mode → create_elicitation_request/mode/form}/scope.rbs +2 -2
- data/sig/generated/acp/types/create_elicitation_request/mode/form.rbs +3 -3
- data/sig/generated/acp/types/{elicitation_form_mode → create_elicitation_request/mode/url}/scope.rbs +2 -2
- data/sig/generated/acp/types/create_elicitation_request/mode/url.rbs +3 -3
- data/sig/generated/acp/types/current_mode_update.rbs +1 -19
- data/sig/generated/acp/types/diff.rbs +1 -27
- data/sig/generated/acp/types/elicitation_accept_action.rbs +1 -15
- data/sig/generated/acp/types/elicitation_form_mode.rbs +1 -19
- data/sig/generated/acp/types/elicitation_url_mode.rbs +1 -23
- data/sig/generated/acp/types/embedded_resource.rbs +1 -23
- data/sig/generated/acp/types/image_content.rbs +1 -31
- data/sig/generated/acp/types/integer_property_schema.rbs +1 -35
- data/sig/generated/acp/types/mcp_server_http.rbs +1 -27
- data/sig/generated/acp/types/mcp_server_sse.rbs +1 -27
- data/sig/generated/acp/types/multi_select_property_schema.rbs +1 -39
- data/sig/generated/acp/types/number_property_schema.rbs +1 -35
- data/sig/generated/acp/types/parse_error.rbs +22 -0
- data/sig/generated/acp/types/plan.rbs +1 -19
- data/sig/generated/acp/types/resource_link.rbs +1 -43
- data/sig/generated/acp/types/selected_permission_outcome.rbs +1 -19
- data/sig/generated/acp/types/session_config_boolean.rbs +1 -15
- data/sig/generated/acp/types/session_config_select.rbs +1 -19
- data/sig/generated/acp/types/session_info_update.rbs +1 -23
- data/sig/generated/acp/types/string_multi_select_items.rbs +1 -19
- data/sig/generated/acp/types/string_property_schema.rbs +1 -51
- data/sig/generated/acp/types/terminal.rbs +1 -19
- data/sig/generated/acp/types/text_content.rbs +1 -23
- data/sig/generated/acp/types/tool_call.rbs +1 -51
- data/sig/generated/acp/types/unstable/compaction_summary_chunk.rbs +1 -23
- data/sig/generated/acp/types/unstable/compaction_update.rbs +1 -31
- data/sig/generated/acp/types/unstable/idle_state_update.rbs +1 -23
- data/sig/generated/acp/types/unstable/mcp_server_acp.rbs +1 -23
- data/sig/generated/acp/types/unstable/nes_edit_suggestion.rbs +1 -31
- data/sig/generated/acp/types/unstable/nes_jump_suggestion.rbs +1 -27
- data/sig/generated/acp/types/unstable/nes_rename_suggestion.rbs +1 -31
- data/sig/generated/acp/types/unstable/nes_search_and_replace_suggestion.rbs +1 -35
- data/sig/generated/acp/types/unstable/notice.rbs +1 -27
- data/sig/generated/acp/types/unstable/plan_file.rbs +1 -23
- data/sig/generated/acp/types/unstable/plan_items.rbs +1 -23
- data/sig/generated/acp/types/unstable/plan_markdown.rbs +1 -23
- data/sig/generated/acp/types/unstable/plan_removed.rbs +1 -19
- data/sig/generated/acp/types/unstable/plan_update.rbs +1 -19
- data/sig/generated/acp/types/unstable/requires_action_state_update.rbs +1 -15
- data/sig/generated/acp/types/unstable/running_state_update.rbs +1 -15
- data/sig/generated/acp/types/unstable/session_message.rbs +1 -31
- data/sig/generated/acp/types/unstable/session_message_chunk.rbs +1 -31
- data/sig/generated/acp/types/unstable/subagent_update.rbs +1 -35
- data/sig/generated/acp/types/unstable/unknown_state_update.rbs +1 -15
- data/sig/generated/acp/types/usage_update.rbs +1 -27
- data/sig/generated/acp/types.rbs +6 -143
- data/sig/manual/acp/agent_connection.rbs +28 -28
- data/sig/manual/acp/client_connection.rbs +7 -2
- data/sig/manual/acp/{agent_connection/optional_method/logout.rbs → protocol.rbs} +3 -3
- data/sig/manual/acp/transport/stdio.rbs +0 -2
- data/sig/manual/acp/transport.rbs +25 -2
- metadata +17 -29
- data/lib/acp/agent_connection/optional_method/authenticate.rb +0 -21
- data/lib/acp/agent_connection/optional_method/close_session.rb +0 -21
- data/lib/acp/agent_connection/optional_method/delete_session.rb +0 -21
- data/lib/acp/agent_connection/optional_method/list_sessions.rb +0 -21
- data/lib/acp/agent_connection/optional_method/load_session.rb +0 -21
- data/lib/acp/agent_connection/optional_method/logout.rb +0 -21
- data/lib/acp/agent_connection/optional_method/resume_session.rb +0 -21
- data/lib/acp/types/content_chunk.rb +0 -40
- data/sig/generated/acp/agent_connection/optional_method/authenticate.rbs +0 -13
- data/sig/generated/acp/agent_connection/optional_method/close_session.rbs +0 -13
- data/sig/generated/acp/agent_connection/optional_method/delete_session.rbs +0 -13
- data/sig/generated/acp/agent_connection/optional_method/list_sessions.rbs +0 -13
- data/sig/generated/acp/agent_connection/optional_method/load_session.rbs +0 -13
- data/sig/generated/acp/agent_connection/optional_method/logout.rbs +0 -13
- data/sig/generated/acp/agent_connection/optional_method/resume_session.rbs +0 -13
- data/sig/generated/acp/types/content_chunk.rbs +0 -25
- data/sig/manual/acp/agent_connection/optional_method/authenticate.rbs +0 -6
- data/sig/manual/acp/agent_connection/optional_method/close_session.rbs +0 -6
- data/sig/manual/acp/agent_connection/optional_method/delete_session.rbs +0 -6
- data/sig/manual/acp/agent_connection/optional_method/list_sessions.rbs +0 -6
- data/sig/manual/acp/agent_connection/optional_method/load_session.rbs +0 -6
- data/sig/manual/acp/agent_connection/optional_method/resume_session.rbs +0 -6
data/lib/acp/protocol.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ACP::Protocol
|
|
4
|
+
PROTOCOL_VERSION = 1 #: Integer
|
|
5
|
+
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# @rbs capabilities: (ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities | Hash[String, untyped])
|
|
9
|
+
# @rbs return: bool
|
|
10
|
+
def mcp_acp_advertised?(capabilities)
|
|
11
|
+
# The stable McpCapabilities has no acp field, so the gate can only read it
|
|
12
|
+
# when the caller opted in with `require 'acp/types/unstable'`.
|
|
13
|
+
return false unless ACP::Types.unstable_enabled?
|
|
14
|
+
|
|
15
|
+
mcp =
|
|
16
|
+
case capabilities
|
|
17
|
+
when ACP::Types::AgentCapabilities, ACP::Types::Unstable::AgentCapabilities
|
|
18
|
+
capabilities.mcp_capabilities #: ACP::Types::McpCapabilities | ACP::Types::Unstable::McpCapabilities?
|
|
19
|
+
when Hash
|
|
20
|
+
# The client's stable parse of the initialize reply drops acp, so
|
|
21
|
+
# mcpCapabilities is re-parsed from the raw hash with the unstable
|
|
22
|
+
# type. Only that sub-object: the other unstable fields arrive
|
|
23
|
+
# unvalidated and must not decide whether MCP is advertised.
|
|
24
|
+
raw = capabilities['mcpCapabilities']
|
|
25
|
+
ACP::Types::Unstable::McpCapabilities.from_h(raw) if raw.is_a?(Hash)
|
|
26
|
+
end
|
|
27
|
+
return false unless mcp.is_a?(ACP::Types::Unstable::McpCapabilities)
|
|
28
|
+
|
|
29
|
+
mcp.acp ? true : false
|
|
30
|
+
rescue ACP::Types::ParseError
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/acp/request_error.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# RequestError: built by handlers and local refusals, and decoded from a
|
|
5
|
-
# peer's error replies by the transport.
|
|
3
|
+
# Named after the official SDKs' RequestError rather than the schema's Error.
|
|
6
4
|
class ACP::RequestError
|
|
7
5
|
PARSE_ERROR = -32_700 #: Integer
|
|
8
6
|
INVALID_REQUEST = -32_600 #: Integer
|
|
@@ -60,9 +58,8 @@ class ACP::RequestError
|
|
|
60
58
|
new(code: INVALID_PARAMS, message: 'Invalid params', data: errors && { errors: errors })
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
# The
|
|
64
|
-
#
|
|
65
|
-
# apart from an unknown method.
|
|
61
|
+
# The message names the capability so the refusal can be told apart from an
|
|
62
|
+
# unknown method, though both use the -32601 code.
|
|
66
63
|
#
|
|
67
64
|
# @rbs capability: String
|
|
68
65
|
# @rbs return: ACP::RequestError
|
|
@@ -70,11 +67,9 @@ class ACP::RequestError
|
|
|
70
67
|
new(code: METHOD_NOT_FOUND, message: "Client does not advertise #{capability}")
|
|
71
68
|
end
|
|
72
69
|
|
|
73
|
-
# The
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# mode in its params, so an unadvertised one is invalid params rather than
|
|
77
|
-
# method not found.
|
|
70
|
+
# The message names the mode so the refusal can be told apart from malformed
|
|
71
|
+
# params. The spec gives elicitation a mode in its params, so an unadvertised
|
|
72
|
+
# one is invalid params rather than method not found.
|
|
78
73
|
#
|
|
79
74
|
# @rbs mode: String
|
|
80
75
|
# @rbs return: ACP::RequestError
|
|
@@ -82,6 +77,12 @@ class ACP::RequestError
|
|
|
82
77
|
new(code: INVALID_PARAMS, message: "Client does not advertise #{mode}")
|
|
83
78
|
end
|
|
84
79
|
|
|
80
|
+
# @rbs capability: String
|
|
81
|
+
# @rbs return: ACP::RequestError
|
|
82
|
+
def self.unadvertised_agent(capability)
|
|
83
|
+
new(code: METHOD_NOT_FOUND, message: "Agent does not advertise #{capability}")
|
|
84
|
+
end
|
|
85
|
+
|
|
85
86
|
# @rbs return: ACP::RequestError
|
|
86
87
|
def self.internal_error
|
|
87
88
|
new(code: INTERNAL_ERROR, message: 'Internal error')
|
|
@@ -96,8 +97,7 @@ class ACP::RequestError
|
|
|
96
97
|
new(code: INTERNAL_ERROR, message: 'Invalid response', data: response)
|
|
97
98
|
end
|
|
98
99
|
|
|
99
|
-
#
|
|
100
|
-
# not speak; the message names both versions so logs can tell it from a
|
|
100
|
+
# The message names both versions so logs can tell this local refusal from a
|
|
101
101
|
# peer's error.
|
|
102
102
|
#
|
|
103
103
|
# @rbs requested: Integer
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Whether the peer cancelled one request. The transport creates one per served
|
|
4
|
-
# request, marks it when the peer's $/cancel_request arrives, and hands it to
|
|
5
|
-
# the serving thread, whose handler can observe it and end the request early.
|
|
6
3
|
class ACP::Transport::Cancellation
|
|
4
|
+
KEY = :acp_serving_cancellation #: Symbol
|
|
5
|
+
|
|
6
|
+
# @rbs return: ACP::Transport::Cancellation?
|
|
7
|
+
def self.current
|
|
8
|
+
Thread.current[KEY] #: ACP::Transport::Cancellation?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @rbs cancellation: ACP::Transport::Cancellation?
|
|
12
|
+
# @rbs return: void
|
|
13
|
+
def self.current=(cancellation)
|
|
14
|
+
Thread.current[KEY] = cancellation
|
|
15
|
+
end
|
|
16
|
+
|
|
7
17
|
# @rbs @lock: Thread::Mutex
|
|
8
18
|
# @rbs @cancelled: bool
|
|
9
19
|
|
data/lib/acp/transport/reply.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# A request handler's outcome plus a callback the transport runs once that
|
|
4
|
-
# outcome is on the wire, for messages the peer must only see after the reply.
|
|
5
3
|
class ACP::Transport::Reply
|
|
6
4
|
# @dynamic result
|
|
7
5
|
attr_reader :result #: ACP::Transport::Stdio::_ToH
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# The
|
|
4
|
-
# sanctions agents logging to stderr.
|
|
3
|
+
# The spec sanctions agents logging to stderr.
|
|
5
4
|
class ACP::Transport::StderrLogger
|
|
6
5
|
# @rbs message: String
|
|
7
6
|
# @rbs return: void
|
data/lib/acp/transport/stdio.rb
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# that arrive meanwhile.
|
|
5
|
+
# A reader thread dispatches inbound messages while outbound requests are
|
|
6
|
+
# pending, and each inbound request is served on its own thread, so a handler
|
|
7
|
+
# can make an outbound request mid-turn and still see notifications (e.g.
|
|
8
|
+
# session/cancel) that arrive meanwhile.
|
|
10
9
|
class ACP::Transport::Stdio
|
|
11
10
|
# @rbs @input: _Reader
|
|
12
11
|
# @rbs @output: _Writer
|
|
@@ -22,9 +21,6 @@ class ACP::Transport::Stdio
|
|
|
22
21
|
code: ACP::RequestError::INTERNAL_ERROR, message: 'Connection closed'
|
|
23
22
|
) #: ACP::RequestError
|
|
24
23
|
|
|
25
|
-
SERVING_CANCEL = :acp_serving_cancellation #: Symbol
|
|
26
|
-
private_constant :SERVING_CANCEL
|
|
27
|
-
|
|
28
24
|
# @rbs input: _Reader
|
|
29
25
|
# @rbs output: _Writer
|
|
30
26
|
# @rbs logger: ACP::Transport::_Logger
|
|
@@ -41,9 +37,6 @@ class ACP::Transport::Stdio
|
|
|
41
37
|
@logger = logger
|
|
42
38
|
end
|
|
43
39
|
|
|
44
|
-
# Returns the reader thread, which ends at EOF on input after releasing any
|
|
45
|
-
# pending outbound requests.
|
|
46
|
-
#
|
|
47
40
|
# @rbs requests: Hash[String, _Handler]
|
|
48
41
|
# @rbs notifications: Hash[String, _NotificationHandler]
|
|
49
42
|
# @rbs return: Thread
|
|
@@ -58,14 +51,16 @@ class ACP::Transport::Stdio
|
|
|
58
51
|
|
|
59
52
|
# @rbs method: String
|
|
60
53
|
# @rbs params: untyped
|
|
61
|
-
# @rbs return:
|
|
54
|
+
# @rbs return: untyped
|
|
62
55
|
def request(method, params = nil)
|
|
63
56
|
queue = Thread::Queue.new
|
|
64
57
|
id = register(queue)
|
|
65
58
|
return CONNECTION_CLOSED unless id
|
|
66
59
|
|
|
67
60
|
write({ 'jsonrpc' => '2.0', 'id' => id, 'method' => method, 'params' => params }.compact)
|
|
68
|
-
queue.pop
|
|
61
|
+
result = queue.pop
|
|
62
|
+
# A null result pops nil just as a closed queue does.
|
|
63
|
+
queue.closed? ? CONNECTION_CLOSED : result
|
|
69
64
|
end
|
|
70
65
|
|
|
71
66
|
# @rbs method: String
|
|
@@ -85,14 +80,17 @@ class ACP::Transport::Stdio
|
|
|
85
80
|
notify('$/cancel_request', { 'requestId' => id }) if pending
|
|
86
81
|
end
|
|
87
82
|
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# and can end early with ACP::RequestError.request_cancelled; whatever it
|
|
91
|
-
# returns, the transport sends exactly one response.
|
|
83
|
+
# Each id is re-checked against the pending table, so one that settled
|
|
84
|
+
# between the snapshot and its cancel is not cancelled.
|
|
92
85
|
#
|
|
86
|
+
# @rbs return: void
|
|
87
|
+
def cancel_requests
|
|
88
|
+
@lock.synchronize { @pending.keys }.each { |id| cancel(id) }
|
|
89
|
+
end
|
|
90
|
+
|
|
93
91
|
# @rbs return: ACP::Transport::Cancellation?
|
|
94
92
|
def cancellation
|
|
95
|
-
|
|
93
|
+
ACP::Transport::Cancellation.current
|
|
96
94
|
end
|
|
97
95
|
|
|
98
96
|
private
|
|
@@ -132,8 +130,12 @@ class ACP::Transport::Stdio
|
|
|
132
130
|
elsif error.is_a?(Hash) && error['code'].is_a?(Integer) && error['message'].is_a?(String)
|
|
133
131
|
code, text, data = error.values_at('code', 'message', 'data')
|
|
134
132
|
settle(message['id'], ACP::RequestError.new(code:, message: text, data:))
|
|
135
|
-
|
|
133
|
+
elsif message.key?('method')
|
|
136
134
|
reply(detectable_id(message), ACP::RequestError.invalid_request)
|
|
135
|
+
elsif !settle(message['id'], ACP::RequestError.invalid_response(message))
|
|
136
|
+
# JSON-RPC responses only answer requests, so a malformed one is never
|
|
137
|
+
# replied to: the error could settle an unrelated request on the peer.
|
|
138
|
+
@logger.warn('dropped malformed response: no pending request for its id')
|
|
137
139
|
end
|
|
138
140
|
end
|
|
139
141
|
|
|
@@ -167,7 +169,7 @@ class ACP::Transport::Stdio
|
|
|
167
169
|
def serve(handler, id, params, method)
|
|
168
170
|
cancellation = register_serving(id)
|
|
169
171
|
Thread.new do
|
|
170
|
-
|
|
172
|
+
ACP::Transport::Cancellation.current = cancellation
|
|
171
173
|
outcome = handler ? invoke(handler, params) : ACP::RequestError.method_not_found(method)
|
|
172
174
|
case outcome
|
|
173
175
|
when ACP::Transport::Reply
|
|
@@ -241,9 +243,6 @@ class ACP::Transport::Stdio
|
|
|
241
243
|
ACP::RequestError.internal_error
|
|
242
244
|
end
|
|
243
245
|
|
|
244
|
-
# The success arm is whatever the handler returned, which the transport only
|
|
245
|
-
# knows serializes with to_h; RequestError picks the error arm of the reply.
|
|
246
|
-
#
|
|
247
246
|
# @rbs id: untyped
|
|
248
247
|
# @rbs outcome: (_ToH | ACP::RequestError)
|
|
249
248
|
# @rbs return: void
|
|
@@ -268,10 +267,10 @@ class ACP::Transport::Stdio
|
|
|
268
267
|
end
|
|
269
268
|
|
|
270
269
|
# @rbs id: untyped
|
|
271
|
-
# @rbs result:
|
|
272
|
-
# @rbs return:
|
|
270
|
+
# @rbs result: untyped
|
|
271
|
+
# @rbs return: Thread::Queue?
|
|
273
272
|
def settle(id, result)
|
|
274
|
-
@lock.synchronize { @pending.delete(id) }&.push(result)
|
|
273
|
+
@lock.synchronize { @pending.delete(id) }&.tap { |queue| queue.push(result) }
|
|
275
274
|
end
|
|
276
275
|
|
|
277
276
|
# @rbs return: void
|
|
@@ -2,50 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/types from schema/schema.json. Do not edit.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_reader :annotations #: ACP::Types::Annotations?
|
|
5
|
+
# @rbs!
|
|
6
|
+
# class ACP::Types::AudioContent = ACP::Types::ContentBlock::Audio
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
attr_reader :data #: String
|
|
11
|
-
|
|
12
|
-
# @dynamic mime_type
|
|
13
|
-
attr_reader :mime_type #: String
|
|
14
|
-
|
|
15
|
-
# @dynamic meta
|
|
16
|
-
attr_reader :meta #: Hash[String, untyped]?
|
|
17
|
-
|
|
18
|
-
# @rbs annotations: ACP::Types::Annotations?
|
|
19
|
-
# @rbs data: String
|
|
20
|
-
# @rbs mime_type: String
|
|
21
|
-
# @rbs meta: Hash[String, untyped]?
|
|
22
|
-
# @rbs return: void
|
|
23
|
-
def initialize(data:, mime_type:, annotations: nil, meta: nil)
|
|
24
|
-
@annotations = annotations
|
|
25
|
-
@data = data
|
|
26
|
-
@mime_type = mime_type
|
|
27
|
-
@meta = meta
|
|
28
|
-
freeze
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# @rbs hash: Hash[String, untyped]
|
|
32
|
-
# @rbs return: ACP::Types::AudioContent
|
|
33
|
-
def self.from_h(hash)
|
|
34
|
-
new(
|
|
35
|
-
annotations: ACP::Types::Check.object(
|
|
36
|
-
hash['annotations'],
|
|
37
|
-
'annotations',
|
|
38
|
-
ACP::Types::Annotations,
|
|
39
|
-
allow_nil: true
|
|
40
|
-
),
|
|
41
|
-
data: ACP::Types::Check.string(ACP::Types::Check.key(hash, 'data'), 'data'),
|
|
42
|
-
mime_type: ACP::Types::Check.string(ACP::Types::Check.key(hash, 'mimeType'), 'mimeType'),
|
|
43
|
-
meta: hash['_meta']
|
|
44
|
-
)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# @rbs return: Hash[String, untyped]
|
|
48
|
-
def to_h
|
|
49
|
-
{ 'data' => data, 'mimeType' => mime_type, 'annotations' => annotations&.to_h, '_meta' => meta }.compact
|
|
50
|
-
end
|
|
51
|
-
end
|
|
8
|
+
ACP::Types::AudioContent = ACP::Types::ContentBlock::Audio
|
|
@@ -2,63 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/types from schema/schema.json. Do not edit.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_reader :id #: String
|
|
5
|
+
# @rbs!
|
|
6
|
+
# class ACP::Types::AuthMethodTerminal = ACP::Types::AuthMethod::Terminal
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
attr_reader :name #: String
|
|
11
|
-
|
|
12
|
-
# @dynamic description
|
|
13
|
-
attr_reader :description #: String?
|
|
14
|
-
|
|
15
|
-
# @dynamic args
|
|
16
|
-
attr_reader :args #: Array[String]?
|
|
17
|
-
|
|
18
|
-
# @dynamic env
|
|
19
|
-
attr_reader :env #: Hash[String, String]?
|
|
20
|
-
|
|
21
|
-
# @dynamic meta
|
|
22
|
-
attr_reader :meta #: Hash[String, untyped]?
|
|
23
|
-
|
|
24
|
-
# @rbs id: String
|
|
25
|
-
# @rbs name: String
|
|
26
|
-
# @rbs description: String?
|
|
27
|
-
# @rbs args: Array[String]?
|
|
28
|
-
# @rbs env: Hash[String, String]?
|
|
29
|
-
# @rbs meta: Hash[String, untyped]?
|
|
30
|
-
# @rbs return: void
|
|
31
|
-
def initialize(id:, name:, description: nil, args: nil, env: nil, meta: nil)
|
|
32
|
-
@id = id
|
|
33
|
-
@name = name
|
|
34
|
-
@description = description
|
|
35
|
-
@args = args
|
|
36
|
-
@env = env
|
|
37
|
-
@meta = meta
|
|
38
|
-
freeze
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# @rbs hash: Hash[String, untyped]
|
|
42
|
-
# @rbs return: ACP::Types::AuthMethodTerminal
|
|
43
|
-
def self.from_h(hash)
|
|
44
|
-
args = ACP::Types::Check.array(hash['args'], 'args', allow_nil: true) do |item, path|
|
|
45
|
-
ACP::Types::Check.string(item, path)
|
|
46
|
-
end
|
|
47
|
-
env = ACP::Types::Check.hash(hash['env'], 'env', allow_nil: true) do |value, path|
|
|
48
|
-
ACP::Types::Check.string(value, path)
|
|
49
|
-
end
|
|
50
|
-
new(
|
|
51
|
-
id: ACP::Types::Check.string(ACP::Types::Check.key(hash, 'id'), 'id'),
|
|
52
|
-
name: ACP::Types::Check.string(ACP::Types::Check.key(hash, 'name'), 'name'),
|
|
53
|
-
description: ACP::Types::Check.string(hash['description'], 'description', allow_nil: true),
|
|
54
|
-
args:,
|
|
55
|
-
env:,
|
|
56
|
-
meta: hash['_meta']
|
|
57
|
-
)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# @rbs return: Hash[String, untyped]
|
|
61
|
-
def to_h
|
|
62
|
-
{ 'id' => id, 'name' => name, 'description' => description, 'args' => args, 'env' => env, '_meta' => meta }.compact
|
|
63
|
-
end
|
|
64
|
-
end
|
|
8
|
+
ACP::Types::AuthMethodTerminal = ACP::Types::AuthMethod::Terminal
|
|
@@ -2,36 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/types from schema/schema.json. Do not edit.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_reader :available_commands #: Array[ACP::Types::AvailableCommand]
|
|
5
|
+
# @rbs!
|
|
6
|
+
# class ACP::Types::AvailableCommandsUpdate = ACP::Types::SessionUpdate::AvailableCommandsUpdate
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
attr_reader :meta #: Hash[String, untyped]?
|
|
11
|
-
|
|
12
|
-
# @rbs available_commands: Array[ACP::Types::AvailableCommand]
|
|
13
|
-
# @rbs meta: Hash[String, untyped]?
|
|
14
|
-
# @rbs return: void
|
|
15
|
-
def initialize(available_commands:, meta: nil)
|
|
16
|
-
@available_commands = available_commands
|
|
17
|
-
@meta = meta
|
|
18
|
-
freeze
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# @rbs hash: Hash[String, untyped]
|
|
22
|
-
# @rbs return: ACP::Types::AvailableCommandsUpdate
|
|
23
|
-
def self.from_h(hash)
|
|
24
|
-
available_commands = ACP::Types::Check.array(
|
|
25
|
-
ACP::Types::Check.key(hash, 'availableCommands'),
|
|
26
|
-
'availableCommands'
|
|
27
|
-
) do |item, path|
|
|
28
|
-
ACP::Types::Check.object(item, path, ACP::Types::AvailableCommand)
|
|
29
|
-
end
|
|
30
|
-
new(available_commands:, meta: hash['_meta'])
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# @rbs return: Hash[String, untyped]
|
|
34
|
-
def to_h
|
|
35
|
-
{ 'availableCommands' => available_commands.map(&:to_h), '_meta' => meta }.compact
|
|
36
|
-
end
|
|
37
|
-
end
|
|
8
|
+
ACP::Types::AvailableCommandsUpdate = ACP::Types::SessionUpdate::AvailableCommandsUpdate
|
|
@@ -2,45 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/types from schema/schema.json. Do not edit.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_reader :title #: String?
|
|
5
|
+
# @rbs!
|
|
6
|
+
# class ACP::Types::BooleanPropertySchema = ACP::Types::ElicitationPropertySchema::BooleanPropertySchema
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
attr_reader :description #: String?
|
|
11
|
-
|
|
12
|
-
# @dynamic default
|
|
13
|
-
attr_reader :default #: bool?
|
|
14
|
-
|
|
15
|
-
# @dynamic meta
|
|
16
|
-
attr_reader :meta #: Hash[String, untyped]?
|
|
17
|
-
|
|
18
|
-
# @rbs title: String?
|
|
19
|
-
# @rbs description: String?
|
|
20
|
-
# @rbs default: bool?
|
|
21
|
-
# @rbs meta: Hash[String, untyped]?
|
|
22
|
-
# @rbs return: void
|
|
23
|
-
def initialize(title: nil, description: nil, default: nil, meta: nil)
|
|
24
|
-
@title = title
|
|
25
|
-
@description = description
|
|
26
|
-
@default = default
|
|
27
|
-
@meta = meta
|
|
28
|
-
freeze
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# @rbs hash: Hash[String, untyped]
|
|
32
|
-
# @rbs return: ACP::Types::BooleanPropertySchema
|
|
33
|
-
def self.from_h(hash)
|
|
34
|
-
new(
|
|
35
|
-
title: ACP::Types::Check.string(hash['title'], 'title', allow_nil: true),
|
|
36
|
-
description: ACP::Types::Check.string(hash['description'], 'description', allow_nil: true),
|
|
37
|
-
default: ACP::Types::Check.boolean(hash['default'], 'default', allow_nil: true),
|
|
38
|
-
meta: hash['_meta']
|
|
39
|
-
)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# @rbs return: Hash[String, untyped]
|
|
43
|
-
def to_h
|
|
44
|
-
{ 'title' => title, 'description' => description, 'default' => default, '_meta' => meta }.compact
|
|
45
|
-
end
|
|
46
|
-
end
|
|
8
|
+
ACP::Types::BooleanPropertySchema = ACP::Types::ElicitationPropertySchema::BooleanPropertySchema
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Hand-authored support for the generated types: the generator neither emits
|
|
4
|
+
# nor deletes this file (see TypeGenerator::SUPPORT_FILES).
|
|
5
|
+
|
|
6
|
+
module ACP::Types::Check
|
|
7
|
+
# @rbs hash: Hash[String, untyped]
|
|
8
|
+
# @rbs key: String
|
|
9
|
+
# @rbs return: untyped
|
|
10
|
+
def self.key(hash, key)
|
|
11
|
+
hash.fetch(key)
|
|
12
|
+
rescue KeyError
|
|
13
|
+
raise ACP::Types::ParseError.new([key], 'is required')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs value: untyped
|
|
17
|
+
# @rbs path: String | Array[String | Integer]
|
|
18
|
+
# @rbs allow_nil: bool
|
|
19
|
+
# @rbs allow_unset: bool
|
|
20
|
+
# @rbs return: untyped
|
|
21
|
+
def self.string(value, path, allow_nil: false, allow_unset: false)
|
|
22
|
+
return value if value.is_a?(String) || skipped?(value, allow_nil, allow_unset)
|
|
23
|
+
|
|
24
|
+
raise ACP::Types::ParseError.new(segments(path), "expected String, got #{describe(value)}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @rbs value: untyped
|
|
28
|
+
# @rbs path: String | Array[String | Integer]
|
|
29
|
+
# @rbs allow_nil: bool
|
|
30
|
+
# @rbs allow_unset: bool
|
|
31
|
+
# @rbs return: untyped
|
|
32
|
+
def self.boolean(value, path, allow_nil: false, allow_unset: false)
|
|
33
|
+
return value if value == true || value == false || skipped?(value, allow_nil, allow_unset)
|
|
34
|
+
|
|
35
|
+
raise ACP::Types::ParseError.new(segments(path), "expected boolean, got #{describe(value)}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs value: untyped
|
|
39
|
+
# @rbs path: String | Array[String | Integer]
|
|
40
|
+
# @rbs min: Integer?
|
|
41
|
+
# @rbs max: Integer?
|
|
42
|
+
# @rbs allow_nil: bool
|
|
43
|
+
# @rbs allow_unset: bool
|
|
44
|
+
# @rbs return: untyped
|
|
45
|
+
def self.integer(value, path, min: nil, max: nil, allow_nil: false, allow_unset: false)
|
|
46
|
+
return value if (value.is_a?(Integer) && within?(value, min, max)) || skipped?(value, allow_nil, allow_unset)
|
|
47
|
+
|
|
48
|
+
raise ACP::Types::ParseError.new(segments(path), "expected #{bounded('Integer', min, max)}, got #{describe(value)}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs value: untyped
|
|
52
|
+
# @rbs path: String | Array[String | Integer]
|
|
53
|
+
# @rbs min: Integer?
|
|
54
|
+
# @rbs max: Integer?
|
|
55
|
+
# @rbs allow_nil: bool
|
|
56
|
+
# @rbs allow_unset: bool
|
|
57
|
+
# @rbs return: untyped
|
|
58
|
+
def self.number(value, path, min: nil, max: nil, allow_nil: false, allow_unset: false)
|
|
59
|
+
return value if (value.is_a?(Numeric) && within?(value, min, max)) || skipped?(value, allow_nil, allow_unset)
|
|
60
|
+
|
|
61
|
+
raise ACP::Types::ParseError.new(segments(path), "expected #{bounded('Numeric', min, max)}, got #{describe(value)}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @rbs value: untyped
|
|
65
|
+
# @rbs path: String | Array[String | Integer]
|
|
66
|
+
# @rbs values: Array[untyped]
|
|
67
|
+
# @rbs allow_nil: bool
|
|
68
|
+
# @rbs allow_unset: bool
|
|
69
|
+
# @rbs return: untyped
|
|
70
|
+
def self.enum(value, path, values, allow_nil: false, allow_unset: false)
|
|
71
|
+
return value if values.include?(value) || skipped?(value, allow_nil, allow_unset)
|
|
72
|
+
|
|
73
|
+
expected = values.map(&:inspect).join(', ')
|
|
74
|
+
raise ACP::Types::ParseError.new(segments(path), "expected one of #{expected}, got #{describe(value)}")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @rbs value: untyped
|
|
78
|
+
# @rbs path: String | Array[String | Integer]
|
|
79
|
+
# @rbs classes: Array[untyped]
|
|
80
|
+
# @rbs allow_nil: bool
|
|
81
|
+
# @rbs allow_unset: bool
|
|
82
|
+
# @rbs ?&items: (untyped, Array[String | Integer]) -> void
|
|
83
|
+
# @rbs return: untyped
|
|
84
|
+
def self.one_of(value, path, classes, allow_nil: false, allow_unset: false, &items)
|
|
85
|
+
return value if skipped?(value, allow_nil, allow_unset)
|
|
86
|
+
|
|
87
|
+
unless classes.any? { |klass| value.is_a?(klass) }
|
|
88
|
+
expected = classes.map(&:name).join(' | ')
|
|
89
|
+
raise ACP::Types::ParseError.new(segments(path), "expected #{expected}, got #{describe(value)}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
return value unless value.is_a?(Array) && items
|
|
93
|
+
|
|
94
|
+
value.each_with_index { |item, index| yield(item, [*segments(path), index]) }
|
|
95
|
+
value
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @rbs value: untyped
|
|
99
|
+
# @rbs path: String | Array[String | Integer]
|
|
100
|
+
# @rbs allow_nil: bool
|
|
101
|
+
# @rbs allow_unset: bool
|
|
102
|
+
# @rbs &check: (untyped, Array[String | Integer]) -> void
|
|
103
|
+
# @rbs return: untyped
|
|
104
|
+
def self.hash(value, path, allow_nil: false, allow_unset: false, &check)
|
|
105
|
+
return value if skipped?(value, allow_nil, allow_unset)
|
|
106
|
+
raise ACP::Types::ParseError.new(segments(path), "expected object, got #{describe(value)}") unless value.is_a?(Hash)
|
|
107
|
+
|
|
108
|
+
parsed = {} #: Hash[String, untyped]
|
|
109
|
+
value.each { |key, item| parsed[key] = yield(item, [*segments(path), key]) }
|
|
110
|
+
parsed
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @rbs value: untyped
|
|
114
|
+
# @rbs path: String | Array[String | Integer]
|
|
115
|
+
# @rbs allow_nil: bool
|
|
116
|
+
# @rbs allow_unset: bool
|
|
117
|
+
# @rbs &check: (untyped, Array[String | Integer]) -> void
|
|
118
|
+
# @rbs return: untyped
|
|
119
|
+
def self.array(value, path, allow_nil: false, allow_unset: false, &check)
|
|
120
|
+
return value if skipped?(value, allow_nil, allow_unset)
|
|
121
|
+
raise ACP::Types::ParseError.new(segments(path), "expected array, got #{describe(value)}") unless value.is_a?(Array)
|
|
122
|
+
|
|
123
|
+
value.each_with_index.map { |item, index| yield(item, [*segments(path), index]) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @rbs value: untyped
|
|
127
|
+
# @rbs path: String | Array[String | Integer]
|
|
128
|
+
# @rbs klass: untyped
|
|
129
|
+
# @rbs allow_nil: bool
|
|
130
|
+
# @rbs allow_unset: bool
|
|
131
|
+
# @rbs return: untyped
|
|
132
|
+
def self.object(value, path, klass, allow_nil: false, allow_unset: false)
|
|
133
|
+
return value if skipped?(value, allow_nil, allow_unset)
|
|
134
|
+
raise ACP::Types::ParseError.new(segments(path), "expected object, got #{describe(value)}") unless value.is_a?(Hash)
|
|
135
|
+
|
|
136
|
+
begin
|
|
137
|
+
klass.from_h(value)
|
|
138
|
+
rescue ACP::Types::ParseError => e
|
|
139
|
+
raise e.prefix(*segments(path))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @rbs value: untyped
|
|
144
|
+
# @rbs path: String | Array[String | Integer]
|
|
145
|
+
# @rbs klass: untyped
|
|
146
|
+
# @rbs allow_nil: bool
|
|
147
|
+
# @rbs allow_unset: bool
|
|
148
|
+
# @rbs return: untyped
|
|
149
|
+
def self.objects(value, path, klass, allow_nil: false, allow_unset: false)
|
|
150
|
+
return value if skipped?(value, allow_nil, allow_unset)
|
|
151
|
+
raise ACP::Types::ParseError.new(segments(path), "expected array, got #{describe(value)}") unless value.is_a?(Array)
|
|
152
|
+
|
|
153
|
+
value.each_with_index do |item, index|
|
|
154
|
+
next if item.is_a?(Hash)
|
|
155
|
+
|
|
156
|
+
raise ACP::Types::ParseError.new([*segments(path), index], "expected object, got #{describe(item)}")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
begin
|
|
160
|
+
klass.from_a(value)
|
|
161
|
+
rescue ACP::Types::ParseError => e
|
|
162
|
+
raise e.prefix(*segments(path))
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs value: untyped
|
|
167
|
+
# @rbs allow_nil: bool
|
|
168
|
+
# @rbs allow_unset: bool
|
|
169
|
+
# @rbs return: bool
|
|
170
|
+
def self.skipped?(value, allow_nil, allow_unset)
|
|
171
|
+
(value.nil? && allow_nil) || (allow_unset && value.equal?(:unset))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @rbs path: String | Array[String | Integer]
|
|
175
|
+
# @rbs return: Array[String | Integer]
|
|
176
|
+
def self.segments(path)
|
|
177
|
+
path.is_a?(Array) ? path : [path]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @rbs value: untyped
|
|
181
|
+
# @rbs return: String
|
|
182
|
+
def self.describe(value)
|
|
183
|
+
case value
|
|
184
|
+
when nil then 'null'
|
|
185
|
+
when true then 'true'
|
|
186
|
+
when false then 'false'
|
|
187
|
+
else value.class.name
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @rbs value: Numeric
|
|
192
|
+
# @rbs min: Integer?
|
|
193
|
+
# @rbs max: Integer?
|
|
194
|
+
# @rbs return: bool
|
|
195
|
+
def self.within?(value, min, max)
|
|
196
|
+
(min.nil? || value >= min) && (max.nil? || value <= max)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @rbs name: String
|
|
200
|
+
# @rbs min: Integer?
|
|
201
|
+
# @rbs max: Integer?
|
|
202
|
+
# @rbs return: String
|
|
203
|
+
def self.bounded(name, min, max)
|
|
204
|
+
if min && max then "#{name} between #{min} and #{max}"
|
|
205
|
+
elsif min then "#{name} >= #{min}"
|
|
206
|
+
elsif max then "#{name} <= #{max}"
|
|
207
|
+
else name
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|