acp_sdk_async 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/README.md +147 -0
- data/lib/acp/agent.rb +201 -0
- data/lib/acp/client.rb +213 -0
- data/lib/acp/connection.rb +341 -0
- data/lib/acp/exceptions.rb +32 -0
- data/lib/acp/meta.rb +58 -0
- data/lib/acp/router.rb +119 -0
- data/lib/acp/schema.rb +1365 -0
- data/lib/acp/schema_base.rb +440 -0
- data/lib/acp/stdio.rb +162 -0
- data/lib/acp/transport.rb +134 -0
- data/lib/acp/version.rb +5 -0
- data/lib/acp/wait.rb +129 -0
- data/lib/acp_sdk.rb +3 -0
- data/lib/acp_sdk_async.rb +14 -0
- data/schema/VERSION +1 -0
- data/schema/meta.json +52 -0
- data/schema/schema.json +10295 -0
- metadata +90 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "async/queue"
|
|
5
|
+
require_relative "exceptions"
|
|
6
|
+
|
|
7
|
+
module ACP
|
|
8
|
+
def self.memory_transport_pair
|
|
9
|
+
MemoryTransport.pair
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Transport
|
|
13
|
+
def send_message(_message)
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def receive_message
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def close
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class NdjsonTransport
|
|
27
|
+
include Transport
|
|
28
|
+
|
|
29
|
+
attr_reader :input, :output
|
|
30
|
+
|
|
31
|
+
def initialize(input, output, receive_timeout: nil)
|
|
32
|
+
@input = input
|
|
33
|
+
@output = output
|
|
34
|
+
@receive_timeout = receive_timeout
|
|
35
|
+
@write_mutex = Mutex.new
|
|
36
|
+
@closed = false
|
|
37
|
+
@output.sync = true if @output.respond_to?(:sync=)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def closed?
|
|
41
|
+
@closed
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def send_message(message)
|
|
45
|
+
line = JSON.generate(message)
|
|
46
|
+
@write_mutex.synchronize do
|
|
47
|
+
raise ConnectionError, "Transport is closed" if @closed
|
|
48
|
+
|
|
49
|
+
@output.write("#{line}\n")
|
|
50
|
+
@output.flush if @output.respond_to?(:flush)
|
|
51
|
+
end
|
|
52
|
+
rescue IOError, Errno::EPIPE, Errno::EBADF => e
|
|
53
|
+
raise ConnectionError, "Failed to write message: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def receive_message
|
|
57
|
+
loop do
|
|
58
|
+
line = read_line
|
|
59
|
+
return nil if line.nil?
|
|
60
|
+
|
|
61
|
+
line = line.scrub unless line.valid_encoding?
|
|
62
|
+
stripped = line.strip
|
|
63
|
+
next if stripped.empty?
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
return JSON.parse(stripped)
|
|
67
|
+
rescue JSON::ParserError => e
|
|
68
|
+
ACP.logger.warn("acp: skipping malformed JSON line: #{e.message}")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
rescue IOError, Errno::EBADF
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def close
|
|
76
|
+
@write_mutex.synchronize { @closed = true }
|
|
77
|
+
[@input, @output].each do |io|
|
|
78
|
+
io.close unless io.nil? || io.closed?
|
|
79
|
+
rescue IOError, Errno::EBADF
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def read_line
|
|
87
|
+
if @receive_timeout && @input.respond_to?(:wait_readable) && !@input.wait_readable(@receive_timeout)
|
|
88
|
+
raise TimeoutError, "No message received within #{@receive_timeout}s"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@input.gets
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class MemoryTransport
|
|
96
|
+
include Transport
|
|
97
|
+
|
|
98
|
+
def self.pair
|
|
99
|
+
a_to_b = ::Async::Queue.new
|
|
100
|
+
b_to_a = ::Async::Queue.new
|
|
101
|
+
[new(b_to_a, a_to_b), new(a_to_b, b_to_a)]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def initialize(inbox, outbox)
|
|
105
|
+
@inbox = inbox
|
|
106
|
+
@outbox = outbox
|
|
107
|
+
@closed = false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def closed?
|
|
111
|
+
@closed
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def send_message(message)
|
|
115
|
+
raise ConnectionError, "Transport is closed" if @closed
|
|
116
|
+
|
|
117
|
+
@outbox.push(JSON.parse(JSON.generate(message)))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def receive_message
|
|
121
|
+
return nil if @closed && @inbox.empty?
|
|
122
|
+
|
|
123
|
+
@inbox.pop
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def close
|
|
127
|
+
return if @closed
|
|
128
|
+
|
|
129
|
+
@closed = true
|
|
130
|
+
@outbox.push(nil)
|
|
131
|
+
@inbox.push(nil)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/acp/version.rb
ADDED
data/lib/acp/wait.rb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
require "async/promise"
|
|
5
|
+
require "logger"
|
|
6
|
+
require_relative "exceptions"
|
|
7
|
+
|
|
8
|
+
module ACP
|
|
9
|
+
def self.logger
|
|
10
|
+
@logger ||= Logger.new($stderr, progname: "acp").tap { |logger| logger.level = Logger::WARN }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.logger=(value)
|
|
14
|
+
@logger = value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Wait
|
|
18
|
+
def self.async?
|
|
19
|
+
!::Async::Task.current?.nil?
|
|
20
|
+
rescue RuntimeError
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.ensure_reactor!
|
|
25
|
+
return if async?
|
|
26
|
+
|
|
27
|
+
raise Error, "ACP must run inside an Async reactor; wrap your code in Sync { ... }"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.spawn(name = nil, &block)
|
|
31
|
+
ensure_reactor!
|
|
32
|
+
Async(annotation: name, &block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.alive?(worker)
|
|
36
|
+
return false if worker.nil?
|
|
37
|
+
|
|
38
|
+
worker.alive? && !worker.finished?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.current_worker
|
|
42
|
+
::Async::Task.current
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.current?(worker)
|
|
46
|
+
return false if worker.nil?
|
|
47
|
+
|
|
48
|
+
worker == ::Async::Task.current?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.stop(worker)
|
|
52
|
+
return if worker.nil? || worker.finished?
|
|
53
|
+
|
|
54
|
+
worker.stop
|
|
55
|
+
rescue ::Async::Cancel, StandardError
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.join(worker, timeout = nil)
|
|
60
|
+
return true if worker.nil? || worker.finished?
|
|
61
|
+
|
|
62
|
+
if timeout.nil?
|
|
63
|
+
worker.wait
|
|
64
|
+
else
|
|
65
|
+
worker.wait(timeout: timeout)
|
|
66
|
+
end
|
|
67
|
+
true
|
|
68
|
+
rescue ::Async::Cancel
|
|
69
|
+
true
|
|
70
|
+
rescue ::Async::TimeoutError, TimeoutError
|
|
71
|
+
false
|
|
72
|
+
rescue StandardError
|
|
73
|
+
worker.finished? || !alive?(worker)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Latch
|
|
77
|
+
def initialize
|
|
78
|
+
@promise = ::Async::Promise.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def open?
|
|
82
|
+
@promise.completed?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def open
|
|
86
|
+
@promise.resolve(true)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def wait(timeout = nil)
|
|
90
|
+
if timeout.nil?
|
|
91
|
+
@promise.wait
|
|
92
|
+
else
|
|
93
|
+
@promise.wait(timeout: timeout)
|
|
94
|
+
end
|
|
95
|
+
true
|
|
96
|
+
rescue ::Async::TimeoutError
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class Promise
|
|
102
|
+
def initialize
|
|
103
|
+
@promise = ::Async::Promise.new
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def settled?
|
|
107
|
+
@promise.resolved?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def resolve(value)
|
|
111
|
+
@promise.resolve(value)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def reject(error)
|
|
115
|
+
@promise.reject(error)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def wait(timeout = nil)
|
|
119
|
+
if timeout.nil?
|
|
120
|
+
@promise.wait
|
|
121
|
+
else
|
|
122
|
+
@promise.wait(timeout: timeout)
|
|
123
|
+
end
|
|
124
|
+
rescue ::Async::TimeoutError
|
|
125
|
+
raise TimeoutError, "Timed out after #{timeout}s"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/acp_sdk.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "acp/version"
|
|
4
|
+
require_relative "acp/exceptions"
|
|
5
|
+
require_relative "acp/wait"
|
|
6
|
+
require_relative "acp/meta"
|
|
7
|
+
require_relative "acp/schema_base"
|
|
8
|
+
require_relative "acp/schema"
|
|
9
|
+
require_relative "acp/transport"
|
|
10
|
+
require_relative "acp/connection"
|
|
11
|
+
require_relative "acp/router"
|
|
12
|
+
require_relative "acp/client"
|
|
13
|
+
require_relative "acp/agent"
|
|
14
|
+
require_relative "acp/stdio"
|
data/schema/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
refs/tags/schema-v1.21.0
|
data/schema/meta.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"agentMethods": {
|
|
4
|
+
"initialize": "initialize",
|
|
5
|
+
"authenticate": "authenticate",
|
|
6
|
+
"providers_list": "providers/list",
|
|
7
|
+
"providers_set": "providers/set",
|
|
8
|
+
"providers_disable": "providers/disable",
|
|
9
|
+
"session_new": "session/new",
|
|
10
|
+
"session_load": "session/load",
|
|
11
|
+
"session_set_mode": "session/set_mode",
|
|
12
|
+
"session_set_config_option": "session/set_config_option",
|
|
13
|
+
"session_prompt": "session/prompt",
|
|
14
|
+
"session_cancel": "session/cancel",
|
|
15
|
+
"mcp_message": "mcp/message",
|
|
16
|
+
"session_list": "session/list",
|
|
17
|
+
"session_delete": "session/delete",
|
|
18
|
+
"session_fork": "session/fork",
|
|
19
|
+
"session_resume": "session/resume",
|
|
20
|
+
"session_close": "session/close",
|
|
21
|
+
"logout": "logout",
|
|
22
|
+
"nes_start": "nes/start",
|
|
23
|
+
"nes_suggest": "nes/suggest",
|
|
24
|
+
"nes_accept": "nes/accept",
|
|
25
|
+
"nes_reject": "nes/reject",
|
|
26
|
+
"nes_close": "nes/close",
|
|
27
|
+
"document_did_open": "document/didOpen",
|
|
28
|
+
"document_did_change": "document/didChange",
|
|
29
|
+
"document_did_close": "document/didClose",
|
|
30
|
+
"document_did_save": "document/didSave",
|
|
31
|
+
"document_did_focus": "document/didFocus"
|
|
32
|
+
},
|
|
33
|
+
"clientMethods": {
|
|
34
|
+
"session_request_permission": "session/request_permission",
|
|
35
|
+
"session_update": "session/update",
|
|
36
|
+
"fs_write_text_file": "fs/write_text_file",
|
|
37
|
+
"fs_read_text_file": "fs/read_text_file",
|
|
38
|
+
"terminal_create": "terminal/create",
|
|
39
|
+
"terminal_output": "terminal/output",
|
|
40
|
+
"terminal_release": "terminal/release",
|
|
41
|
+
"terminal_wait_for_exit": "terminal/wait_for_exit",
|
|
42
|
+
"terminal_kill": "terminal/kill",
|
|
43
|
+
"mcp_connect": "mcp/connect",
|
|
44
|
+
"mcp_message": "mcp/message",
|
|
45
|
+
"mcp_disconnect": "mcp/disconnect",
|
|
46
|
+
"elicitation_create": "elicitation/create",
|
|
47
|
+
"elicitation_complete": "elicitation/complete"
|
|
48
|
+
},
|
|
49
|
+
"protocolMethods": {
|
|
50
|
+
"cancel_request": "$/cancel_request"
|
|
51
|
+
}
|
|
52
|
+
}
|