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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ACP
4
+ VERSION = "0.2.0"
5
+ end
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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "acp_sdk_async"
@@ -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
+ }