Class: Ask::CodingProviders::Codex::Adapter

Inherits:
Adapter
  • Object
show all
Defined in:
lib/ask/coding_providers/codex/adapter.rb

Overview

Adapter implementation for the Codex app-server.

Communicates with codex app-server over stdio JSON-RPC 2.0. Supports thread (session) lifecycle and streaming turns.

Note: Codex does not speak ACP. Use this adapter for Codex. For agents that support ACP (Claude, OpenCode, Gemini CLI), use ACP::Adapter instead.

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Adapter

#handle_session_error, #list_sessions

Constructor Details

#initialize(cwd: ".", cli_path: nil, request_timeout: 60.0, model: nil, model_provider: nil) ⇒ Adapter

Returns a new instance of Adapter.



15
16
17
18
19
20
21
22
23
# File 'lib/ask/coding_providers/codex/adapter.rb', line 15

def initialize(cwd: ".", cli_path: nil, request_timeout: 60.0, model: nil, model_provider: nil)
  @cwd = cwd
  @cli_path = cli_path
  @request_timeout = request_timeout
  @model = model || ENV["CODEX_MODEL"]
  @model_provider = model_provider || ENV["CODEX_MODEL_PROVIDER"]
  @client = nil
  @codex_db = CodexDB.new
end

Class Method Details

.from_config(workspace_path: Dir.pwd, cli_path: nil, request_timeout: 600, model: nil, model_provider: nil) ⇒ Object

Build a Codex adapter from config.



148
149
150
151
# File 'lib/ask/coding_providers/codex/adapter.rb', line 148

def self.from_config(workspace_path: Dir.pwd, cli_path: nil, request_timeout: 600, model: nil, model_provider: nil, **)
  new(cwd: workspace_path, cli_path: cli_path, request_timeout: request_timeout,
      model: model, model_provider: model_provider)
end

Instance Method Details

#create_session(workspace_path, mode: nil, system_prompt: nil) ⇒ Object

Raises:



45
46
47
48
49
50
51
# File 'lib/ask/coding_providers/codex/adapter.rb', line 45

def create_session(workspace_path, mode: nil, system_prompt: nil, **)
  ensure_running
  thread = @client.thread_start(cwd: workspace_path)
  tid = thread["id"] || thread[:id]
  raise Error, "thread/start did not return thread id" unless tid
  tid
end

#find_recent_sessionObject



163
164
165
# File 'lib/ask/coding_providers/codex/adapter.rb', line 163

def find_recent_session
  @codex_db.find_recent_session
end

#find_recent_tui_session(workspace_path) ⇒ Object



167
168
169
# File 'lib/ask/coding_providers/codex/adapter.rb', line 167

def find_recent_tui_session(workspace_path)
  @codex_db.find_recent_tui_session(workspace_path)
end

#find_sessions(directory:, limit: 20) ⇒ Object



159
160
161
# File 'lib/ask/coding_providers/codex/adapter.rb', line 159

def find_sessions(directory:, limit: 20)
  @codex_db.find_sessions(directory: directory, limit: limit)
end

#get_events(session_id, after_seq:, limit: nil) ⇒ Object



131
132
133
134
# File 'lib/ask/coding_providers/codex/adapter.rb', line 131

def get_events(session_id, after_seq:, limit: nil)
  ensure_running
  {}
end

#get_workspace_state(workspace_path) ⇒ Object



140
141
142
143
144
145
# File 'lib/ask/coding_providers/codex/adapter.rb', line 140

def get_workspace_state(workspace_path)
  ensure_running
  @client.read_workspace_state
rescue => e
  {}
end

#list_projectsObject

── Session/project queries (delegated to CodexDB) ──



155
156
157
# File 'lib/ask/coding_providers/codex/adapter.rb', line 155

def list_projects
  @codex_db.list_projects
end

#recent_sessionsObject



179
180
181
# File 'lib/ask/coding_providers/codex/adapter.rb', line 179

def recent_sessions
  @codex_db.recent_sessions
end

#respond(request_id, result) ⇒ Object



136
137
138
# File 'lib/ask/coding_providers/codex/adapter.rb', line 136

def respond(request_id, result)
  @client&.respond(request_id, result)
end

#resume_session(session_id) ⇒ Object



53
54
55
56
57
58
# File 'lib/ask/coding_providers/codex/adapter.rb', line 53

def resume_session(session_id)
  ensure_running
  @client.thread_resume(session_id)
rescue => e
  {}
end

#running?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/ask/coding_providers/codex/adapter.rb', line 41

def running?
  @client&.running? || false
end

#send_and_stream(session_id, content, turn_timeout: 600.0, &block) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/ask/coding_providers/codex/adapter.rb', line 70

def send_and_stream(session_id, content, turn_timeout: 600.0, &block)
  return enum_for(:send_and_stream, session_id, content, turn_timeout: turn_timeout) unless block
  raise "send_and_stream requires app-server mode" unless @client

  ev_queue = Queue.new
  my_turn_id = nil
  done = false

  handler = ->(method, params, request_id) do
    case method
    when "turn/started"
      my_turn_id = params["turnId"] || params[:turnId]
      ev_queue << {
        type: "turn.started", seq: params["seq"] || params[:seq] || 0,
        payload: { "sessionId" => session_id }
      }
    when "item/agentMessage/delta"
      tid = params["turnId"] || params[:turnId]
      if my_turn_id.nil? || tid == my_turn_id
        ev_queue << {
          type: "model.streaming", seq: params["seq"] || params[:seq] || 0,
          payload: { "delta" => params["content"] || params[:content] || "", "sessionId" => session_id }
        }
      end
    when "turn/completed"
      tid = params["turnId"] || params[:turnId]
      if my_turn_id.nil? || tid == my_turn_id
        response = params.dig("response", "message") || params.dig("turn", "response") || ""
        ev_queue << {
          type: "turn.completed", seq: params["seq"] || params[:seq] || 0,
          payload: { "response" => response, "sessionId" => session_id,
                     "tokenCount" => params["tokenCount"] || params[:tokenCount] || 0 }
        }
        done = true
      end
    when "turn/failed"
      error_msg = params.dig("error", "message") || "Turn failed"
      ev_queue << {
        type: "turn.failed", seq: params["seq"] || params[:seq] || 0,
        payload: { "error" => { "message" => error_msg }, "sessionId" => session_id }
      }
      done = true
    end
  end

  @client.on_notification(&handler)
  begin
    @client.turn_start(session_id, content)
    deadline = Time.now + turn_timeout
    until done
      remaining = deadline - Time.now
      break if remaining <= 0
      begin
        block.call(ev_queue.pop(timeout: remaining))
      rescue ThreadError
        break
      end
    end
  end
end

#send_message(session_id, content) ⇒ Object



65
66
67
68
# File 'lib/ask/coding_providers/codex/adapter.rb', line 65

def send_message(session_id, content)
  ensure_running
  @client.turn_start(session_id, content)
end

#session_directory(session_id) ⇒ Object



171
172
173
# File 'lib/ask/coding_providers/codex/adapter.rb', line 171

def session_directory(session_id)
  @codex_db.session_directory(session_id)
end

#session_history(session_id, limit: 100) ⇒ Object



175
176
177
# File 'lib/ask/coding_providers/codex/adapter.rb', line 175

def session_history(session_id, limit: 100)
  @codex_db.session_history(session_id, limit: limit)
end

#startObject



25
26
27
28
29
30
31
32
33
34
# File 'lib/ask/coding_providers/codex/adapter.rb', line 25

def start
  return if @client
  @client = AppServerClient.new(cwd: @cwd, cli_path: @cli_path, request_timeout: @request_timeout,
                                 model: @model, model_provider: @model_provider)
  @client.on_notification { |method, params, request_id| handle_notification(method, params, request_id) }
  @client.start
  # Perform the initialize handshake
  @client.initialize!
  @client.send_initialized
end

#stopObject



36
37
38
39
# File 'lib/ask/coding_providers/codex/adapter.rb', line 36

def stop
  @client&.stop
  @client = nil
end

#subscribe(session_id, after_seq: 0) ⇒ Object



60
61
62
63
# File 'lib/ask/coding_providers/codex/adapter.rb', line 60

def subscribe(session_id, after_seq: 0)
  ensure_running
  { "eventSeq" => 0 }
end