Skip to content

feat: add Claude CLI agent support alongside Codex#64

Open
mzhaom wants to merge 3 commits into
kxn:masterfrom
mzhaom:feat/claude-agent-support
Open

feat: add Claude CLI agent support alongside Codex#64
mzhaom wants to merge 3 commits into
kxn:masterfrom
mzhaom:feat/claude-agent-support

Conversation

@mzhaom

@mzhaom mzhaom commented Apr 9, 2026

Copy link
Copy Markdown

Introduce a translator.Translator interface so the wrapper can drive either Codex or Claude CLI as the underlying agent. The agent type is selected via config (agentType: "claude") or env var (CODEX_REMOTE_AGENT_TYPE=claude).

New packages:

  • internal/core/translator: defines the Translator interface and Result type
  • internal/adapter/claude: Claude CLI SDK protocol translator (NDJSON, stream events, control_request/response, permission flow)

Key changes:

  • Extract BootstrapFrames from hardcoded Codex headless init into the Translator interface so each agent handles its own startup handshake
  • Wrapper selects translator and binary based on AgentType config
  • Claude child process gets CLAUDE_CODE_ENTRYPOINT=sdk-go env var
  • Daemon passes CODEX_REMOTE_AGENT_TYPE to headless wrapper instances
  • Rename OutboundToCodex -> OutboundToAgent across the codebase

Claude v1 limitations: single thread per instance (no thread switching), no turn/steer support, no --resume session persistence.

mzhaom added 3 commits April 9, 2026 00:06
Introduce a translator.Translator interface so the wrapper can drive
either Codex or Claude CLI as the underlying agent.  The agent type is
selected via config (agentType: "claude") or env var
(CODEX_REMOTE_AGENT_TYPE=claude).

New packages:
- internal/core/translator: defines the Translator interface and Result type
- internal/adapter/claude: Claude CLI SDK protocol translator (NDJSON,
  stream events, control_request/response, permission flow)

Key changes:
- Extract BootstrapFrames from hardcoded Codex headless init into the
  Translator interface so each agent handles its own startup handshake
- Wrapper selects translator and binary based on AgentType config
- Claude child process gets CLAUDE_CODE_ENTRYPOINT=sdk-go env var
- Daemon passes CODEX_REMOTE_AGENT_TYPE to headless wrapper instances
- Rename OutboundToCodex -> OutboundToAgent across the codebase

Claude v1 limitations: single thread per instance (no thread switching),
no turn/steer support, no --resume session persistence.
- Integration test (go test -tags integration) spawns real Claude CLI,
  sends a prompt, and verifies the translator produces the correct
  agentproto event sequence (thread.discovered → turn.started →
  item.started → item.delta → item.completed → turn.completed)
- Records the full NDJSON session with privacy masking for replay
- clauderecord package: Entry/Recorder for capture, MaskEntries for
  privacy (session_id, uuid, email, org, paths, api keys), LoadFixture
  for replay
- Replay unit tests load the masked fixture and verify translator
  output without needing a live Claude CLI
- Fixture testdata/hello_simple.ndjson committed with all PII masked

Key finding during implementation: Claude CLI sends the system init
message only after the first user message is written to stdin, not
immediately after the initialize control_response.
…lti-turn

Add three new integration test scenarios against real Claude CLI:
- TestClaudeCLIToolUse: Bash tool invocation with input_json_delta
  streaming, tool result echo, and follow-up text response
- TestClaudeCLIThinking: reasoning_content blocks with thinking_delta
- TestClaudeCLIMultiTurn: two sequential turns with context retention

Fix translator block index tracking:
- Track blocks by protocol index field (map[int]*blockState) instead
  of a single activeBlockIndex counter. This correctly handles:
  - Multiple concurrent blocks (thinking at index 0, text at index 1)
  - assistant messages arriving before content_block_stop
  - signature_delta and unknown delta types (silently skipped)

Replay tests now cover all 4 fixtures with structural assertions:
- Block index uniqueness and start/complete pairing
- Multi-turn ID separation
- Tool metadata (toolName) propagation
- Thinking delta presence

Fixtures are privacy-masked (session_id, uuid, email, org, paths).
@kxn kxn marked this pull request as ready for review April 9, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant