Class: Harnex::Adapters::CodexAppServer
- Inherits:
-
Base
- Object
- Base
- Harnex::Adapters::CodexAppServer
show all
- Defined in:
- lib/harnex/adapters/codex_appserver.rb
Overview
Codex ‘app-server` adapter — JSON-RPC over stdio.
Talks to a spawned ‘codex app-server` subprocess by writing newline-delimited JSON-RPC messages on stdin and reading responses + notifications from stdout. Replaces the pane-scraping heuristics in `Adapters::Codex` (legacy, kept behind –legacy-pty).
Defined Under Namespace
Classes: JsonRpcClient
Constant Summary
collapse
- CLIENT_TITLE =
"harnex"
- CLIENT_NAME =
"harnex"
- OPT_OUT_NOTIFICATIONS =
%w[
item/agentMessage/delta
item/reasoning/summaryTextDelta
item/reasoning/summaryPartAdded
item/reasoning/textDelta
].freeze
- REQUEST_METHODS =
%w[
initialize thread/start turn/start turn/interrupt thread/resume
].freeze
- NOTIFICATION_METHODS =
%w[
thread/started turn/started turn/completed
item/started item/completed
thread/status/changed thread/tokenUsage/updated
thread/compacted account/rateLimits/updated
error
].freeze
- EVENTS =
%w[task_complete turn_started item_completed disconnected].freeze
Constants inherited
from Base
Base::PROMPT_PREFIXES
Instance Attribute Summary collapse
Attributes inherited from Base
#key
Instance Method Summary
collapse
-
#base_command ⇒ Object
-
#build_command ⇒ Object
-
#build_send_payload(text:, submit:, enter_only:, screen_text:, force: false) ⇒ Object
-
#close ⇒ Object
-
#describe ⇒ Object
-
#dispatch(prompt:, model: nil, effort: nil) ⇒ Object
-
#initialize(extra_args = []) ⇒ CodexAppServer
constructor
A new instance of CodexAppServer.
-
#inject_exit(_writer, **_kwargs) ⇒ Object
No-op: closing the subprocess is handled via #close.
-
#input_state(_screen_text = nil) ⇒ Object
Override: state is RPC-driven, screen text is ignored.
-
#interrupt(turn_id: nil) ⇒ Object
-
#on_disconnect(&block) ⇒ Object
-
#on_notification(&block) ⇒ Object
-
#pid ⇒ Object
-
#resume(thread_id:) ⇒ Object
-
#start_rpc(env: nil, cwd: nil, read_io: nil, write_io: nil, pid: nil) ⇒ Object
Start the JSON-RPC client.
-
#state ⇒ Object
-
#transport ⇒ Object
Methods inherited from Base
#infer_repo_path, #parse_session_summary, #send_wait_seconds, #wait_for_sendable, #wait_for_sendable_state?
Constructor Details
#initialize(extra_args = []) ⇒ CodexAppServer
Returns a new instance of CodexAppServer.
39
40
41
42
43
44
45
46
47
48
49
50
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 39
def initialize( = [])
super("codex", )
@initial_prompt = .join(" ").strip
@initial_prompt = nil if @initial_prompt.empty?
@client = nil
@thread_id = nil
@current_turn_id = nil
@state = :disconnected
@last_completed_at = nil
@notification_handler = nil
@disconnect_handler = nil
end
|
Instance Attribute Details
#current_turn_id ⇒ Object
Returns the value of attribute current_turn_id.
37
38
39
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 37
def current_turn_id
@current_turn_id
end
|
#initial_prompt ⇒ Object
Returns the value of attribute initial_prompt.
37
38
39
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 37
def initial_prompt
@initial_prompt
end
|
#last_completed_at ⇒ Object
Returns the value of attribute last_completed_at.
37
38
39
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 37
def last_completed_at
@last_completed_at
end
|
#thread_id ⇒ Object
Returns the value of attribute thread_id.
37
38
39
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 37
def thread_id
@thread_id
end
|
Instance Method Details
#base_command ⇒ Object
56
57
58
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 56
def base_command
["codex", "app-server"]
end
|
#build_command ⇒ Object
60
61
62
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 60
def build_command
base_command
end
|
#build_send_payload(text:, submit:, enter_only:, screen_text:, force: false) ⇒ Object
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 85
def build_send_payload(text:, submit:, enter_only:, screen_text:, force: false)
state = input_state(nil)
if !force && submit && !enter_only && state[:input_ready] != true
raise ArgumentError, blocked_message(state, enter_only: enter_only)
end
raise ArgumentError, "Codex app-server cannot stage input without submitting it" unless submit || enter_only
raise ArgumentError, "Codex app-server does not support submit-only input" if enter_only
{
dispatch: { prompt: text.to_s },
input_state: state,
force: force
}
end
|
#close ⇒ Object
163
164
165
166
167
168
169
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 163
def close
return unless @client
@client.close
@client = nil
@state = :disconnected
end
|
#describe ⇒ Object
64
65
66
67
68
69
70
71
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 64
def describe
{
transport: transport,
request_methods: REQUEST_METHODS,
notification_methods: NOTIFICATION_METHODS,
events: EVENTS
}
end
|
#dispatch(prompt:, model: nil, effort: nil) ⇒ Object
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 131
def dispatch(prompt:, model: nil, effort: nil)
ensure_open!
ensure_thread!
params = {
threadId: @thread_id,
input: { content: [{ type: "text", text: prompt.to_s }] }
}
params[:model] = model if model
params[:effort] = effort if effort
result = @client.request("turn/start", params)
@current_turn_id = result["turnId"] || result["turn_id"] || result["id"]
@state = :busy
@current_turn_id
end
|
#inject_exit(_writer, **_kwargs) ⇒ Object
No-op: closing the subprocess is handled via #close.
101
102
103
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 101
def inject_exit(_writer, **_kwargs)
nil
end
|
Override: state is RPC-driven, screen text is ignored.
78
79
80
81
82
83
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 78
def input_state(_screen_text = nil)
{
state: @state.to_s,
input_ready: @state == :prompt
}
end
|
#interrupt(turn_id: nil) ⇒ Object
147
148
149
150
151
152
153
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 147
def interrupt(turn_id: nil)
ensure_open!
target = turn_id || @current_turn_id
return nil if target.nil?
@client.request("turn/interrupt", { threadId: @thread_id, turnId: target })
end
|
#on_disconnect(&block) ⇒ Object
109
110
111
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 109
def on_disconnect(&block)
@disconnect_handler = block
end
|
#on_notification(&block) ⇒ Object
105
106
107
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 105
def on_notification(&block)
@notification_handler = block
end
|
#pid ⇒ Object
171
172
173
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 171
def pid
@client&.pid
end
|
#resume(thread_id:) ⇒ Object
155
156
157
158
159
160
161
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 155
def resume(thread_id:)
ensure_open!
result = @client.request("thread/resume", { threadId: thread_id })
@thread_id = thread_id
@state = :prompt
result
end
|
#start_rpc(env: nil, cwd: nil, read_io: nil, write_io: nil, pid: nil) ⇒ Object
Start the JSON-RPC client. In production, spawns the codex subprocess. In tests, callers may pass pre-built IO objects.
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 115
def start_rpc(env: nil, cwd: nil, read_io: nil, write_io: nil, pid: nil)
if read_io && write_io
@client = JsonRpcClient.new(read_io: read_io, write_io: write_io, pid: pid)
else
spawn_pid, child_stdin, child_stdout = spawn_subprocess(env, cwd)
@client = JsonRpcClient.new(read_io: child_stdout, write_io: child_stdin, pid: spawn_pid)
end
@client.on_notification { |msg| handle_notification(msg) }
@client.on_disconnect { |err| handle_disconnect(err) }
@client.start
perform_handshake
@state = :prompt
self
end
|
#state ⇒ Object
73
74
75
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 73
def state
@state
end
|
#transport ⇒ Object
52
53
54
|
# File 'lib/harnex/adapters/codex_appserver.rb', line 52
def transport
:stdio_jsonrpc
end
|