Module: AgentHarness::Providers::CodexModelDiscovery

Included in:
Codex
Defined in:
lib/agent_harness/providers/codex_model_discovery.rb

Overview

Runs the interactive protocol beside the CLI, including for executors that only expose execute (Docker/remote transports). Codex's npm installation already requires Node. No shell, host credentials, or host CLI is used.

Constant Summary collapse

SCRIPT =
"const { spawn } = require('node:child_process');\nconst child = spawn(process.argv[1], ['app-server', '--listen', 'stdio://'], {stdio: ['pipe', 'pipe', 'pipe']});\nlet buffer = '', bytes = 0, page = 0, done = false;\nconst models = [], cursors = new Set();\nconst send = value => child.stdin.write(JSON.stringify(value) + '\\n');\nconst finish = (code, value) => {\n  if (done) return;\n  done = true;\n  clearTimeout(timer);\n  if (value) process.stdout.write(JSON.stringify(value) + '\\n');\n  process.exitCode = code;\n  child.stdin.end();\n  child.kill('SIGTERM');\n  setTimeout(() => child.kill('SIGKILL'), 250).unref();\n};\nconst timer = setTimeout(() => finish(1), Number(process.argv[2]));\nchild.on('error', () => finish(1));\nchild.stdin.on('error', () => finish(1));\nchild.stderr.on('data', () => {});\nchild.on('close', () => { if (!done) finish(1); });\nchild.stdout.on('data', chunk => {\n  bytes += chunk.length;\n  if (bytes > 1048576) return finish(1);\n  buffer += chunk.toString();\n  let end;\n  while (!done && (end = buffer.indexOf('\\n')) >= 0) {\n    const line = buffer.slice(0, end); buffer = buffer.slice(end + 1);\n    let msg; try { msg = JSON.parse(line); } catch { continue; }\n    if (msg.id !== 1 && msg.id !== 2) continue;\n    if (msg.error) return finish(0, {id: 2, error: msg.error});\n    if (msg.id === 1) {\n      send({method: 'initialized', params: {}});\n      send({id: 2, method: 'model/list', params: {limit: 100}});\n    } else {\n      if (!Array.isArray(msg.result?.data)) return finish(1);\n      models.push(...msg.result.data);\n      const cursor = msg.result.nextCursor;\n      if (!cursor) return finish(0, {id: 2, result: {data: models}});\n      if (++page >= 10 || cursors.has(cursor)) return finish(1);\n      cursors.add(cursor);\n      send({id: 2, method: 'model/list', params: {limit: 100, cursor}});\n    }\n  }\n});\nsend({id: 1, method: 'initialize', params: {clientInfo: {name: 'agent-harness', version: '1.0.0'}}});\n"

Instance Method Summary collapse

Instance Method Details

#discover_available_models(env:, timeout: 15) ⇒ Object

Fresh account-local discovery. The caller owns selection policy and must still smoke-test its selected model; model/list is not execution proof.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/agent_harness/providers/codex_model_discovery.rb', line 59

def discover_available_models(env:, timeout: 15)
  result = execute_model_discovery(env: env, timeout: timeout)
  return unavailable_model_discovery(:app_server_failed) unless result.success?

  response = parse_app_server_response(result.stdout, 2)
  return unavailable_model_discovery(:model_list_missing_response) unless response
  return unavailable_model_discovery(:model_list_error) if response["error"]

  entries = Array(response.dig("result", "data")).filter_map { |entry| normalize_model_entry(entry) }
    .reject { |entry| entry[:hidden] }.uniq { |entry| entry[:id] }
  return unavailable_model_discovery(:no_compatible_model) if entries.empty?

  preferred = entries.find { |entry| entry[:is_default] } || entries.first
  self.class::ModelDiscovery.new(status: :available, models: entries,
    recommended_model_id: preferred[:id], source: :codex_app_server_model_list)
rescue TimeoutError
  unavailable_model_discovery(:app_server_timeout)
end