Skip to content

Commit b589379

Browse files
committed
feat(appserver): 新增 Codex app-server WebSocket 长连接路线
JSON-RPC 2.0 over WebSocket 客户端,与既有 CLI 子进程路线并列: - CodexAppServerClient:runTurn/runTurnAsync,逐 turn 建连、共享 HttpClient,线程安全;失败统一折叠为 CodexAppServerException - 协议序列:thread/start(sessionKey 有映射走 thread/resume)→ turn/start(input:[{type:text}]) → item/completed(仅 agent 消息 出 delta)→ turn/completed;turn/failed、error → 异常;未知通知 只记 debug - ThreadMappingCache:sessionKey→threadId 访问序 LRU,上限默认 1000,淘汰退化为新建线程 - CodexAppServerConfig:baseUrl(ws/wss/http/https 自动映射)/ token(Bearer 握手)/connectTimeoutMillis(5s)/readTimeoutMillis(120s), 与 RuntimeClientProperties.CodexEndpoint 字段同名同义 - 测试:31 个新用例全绿(总 206),含 4 个对进程内 RFC6455 假 app-server 的端到端契约测试(bearer 握手、全序列、delta 顺序、 resume 复用与跨 sessionKey 隔离) - README 双语同步双路线描述,修正"无测试/无 CI"失真描述
1 parent b27ecae commit b589379

14 files changed

Lines changed: 1892 additions & 52 deletions

‎README.md‎

Lines changed: 89 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
[![Java](https://img.shields.io/badge/Java-21-orange)](https://github.com/easy-4-java/codex-java-sdk) [![License](https://img.shields.io/badge/license-Apache%202.0-green)](https://www.apache.org/licenses/LICENSE-2.0.txt)
66

7-
> Java SDK for the [Codex CLI](https://github.com/openai/codex): subprocess
8-
> integration that drives the local `codex` agent (exec, interactive sessions,
9-
> session resume / fork / archive, doctor, review) from Java.
7+
> Java SDK for the [Codex CLI](https://github.com/openai/codex) with two
8+
> integration routes: a subprocess wrapper that drives the local `codex`
9+
> agent (exec, interactive sessions, session resume / fork / archive, doctor,
10+
> review), and a JSON-RPC 2.0 over WebSocket client for a remote Codex
11+
> app-server (`thread/start` → `turn/start` → notification stream).
1012
1113
## Table of Contents
1214

@@ -24,10 +26,14 @@
2426

2527
## 1. Project Overview
2628

27-
`codex-java-sdk` lets Java applications run the
28-
[Codex CLI](https://github.com/openai/codex) agent (`codex`) as a local subprocess.
29-
It is a **CLI wrapper**, not a direct OpenAI API client — every call maps to a real
30-
`codex` command line invocation.
29+
`codex-java-sdk` lets Java applications integrate the
30+
[Codex CLI](https://github.com/openai/codex) agent (`codex`) through two
31+
routes. Neither route is a direct OpenAI API client.
32+
33+
- **CLI route (local subprocess)** — every call maps to a real `codex`
34+
command line invocation.
35+
- **App-server route (remote long connection)** — a JSON-RPC 2.0 over
36+
WebSocket client for a running Codex app-server.
3137

3238
The SDK covers:
3339

@@ -38,11 +44,16 @@ The SDK covers:
3844
- **Parsed models** — `CodexEvent` (JSONL events), `CodexSession`, `CodexDoctorReport`.
3945
- **Utilities** — `doctor`, `review`, `login` / `logout`, MCP management, `update`,
4046
`features`, shell `completion`.
47+
- **App-server WebSocket route** — `CodexAppServerClient` with per-turn
48+
connections, `thread/start` / `thread/resume` reuse via a bounded
49+
`sessionKey → threadId` LRU, streaming agent-message deltas and
50+
`turn/completed` finalization.
4151

4252
What it is **not**:
4353

4454
- Not an OpenAI API client (no direct HTTP calls to the OpenAI API).
45-
- Not a replacement for the `codex` binary — the CLI must be installed and runnable.
55+
- Not a replacement for the `codex` binary — the CLI must be installed and runnable
56+
(local route), or a Codex app-server must be reachable (WebSocket route).
4657

4758
Typical scenarios:
4859

@@ -53,6 +64,7 @@ Typical scenarios:
5364
| Long-running interactive agent | `startSession(prompt)` / `resumeSession(sessionId)` |
5465
| Reproduce a session in a sandbox | `forkSession(sessionId)` / `execResume(sessionId, prompt)` |
5566
| Environment diagnostics | `doctorSummary()` / `doctorJson()` |
67+
| Remote agent with session continuity | `CodexAppServerClient.runTurn(request)` with `sessionKey` |
5668

5769
## 2. Features & Status
5870

@@ -65,18 +77,20 @@ Typical scenarios:
6577
| Session lifecycle | Active development | `resumeSession`, `resumeLastSession`, `forkSession`, `forkLastSession`, `archiveSession`, `unarchiveSession`, `execResume` |
6678
| Doctor & review | Active development | `doctor`, `doctorJson`, `doctorSummary`, `review`, `reviewCommit`, `reviewBase` |
6779
| Auth / MCP / misc | Active development | `login`, `logout`, `mcpList` / `mcpAdd` / `mcpGet` / `mcpRemove`, `update`, `features`, `completion`, `app` |
68-
| Config model | Active development | `CodexClientConfig` POJO (plain, Spring-bindable) |
80+
| App-server WebSocket route | Active development | `CodexAppServerClient.runTurn` / `runTurnAsync`, `thread/start` / `thread/resume`, agent-message deltas, `sessionKey → threadId` LRU (1000) |
81+
| Config model | Active development | `CodexClientConfig` POJO (plain, Spring-bindable), `CodexAppServerConfig` POJO |
6982

70-
> **Assumption**: the capability statuses above reflect the current state of the
71-
> 1.0.x branch; the module is under active development.
83+
> **Assumption**: the capability statuses above reflect the current state of
84+
> the active branch; the module is under active development.
7285
7386
## 3. Requirements & Compatibility
7487

7588
| Requirement | Version / Notes |
7689
| :--- | :--- |
7790
| JDK | 21+ |
7891
| Maven | 3.0+ (enforced; Maven Wrapper `./mvnw` included) |
79-
| Codex CLI | `codex` must be installed and available (`localExecutable` configures the path) |
92+
| Codex CLI | Local route: `codex` must be installed and available (`localExecutable` configures the path) |
93+
| Codex app-server | WebSocket route only: a reachable app-server (`baseUrl` accepts ws/wss/http/https) |
8094

8195
Version lines:
8296

@@ -89,35 +103,39 @@ Version lines:
89103
## 4. Architecture & Modules
90104

91105
```text
92-
+------------------+ +------------------------------------------+
93-
| Java application | | codex-java-sdk |
94-
| |-->| CodexClient (facade) |
95-
| prompt / options | | | CodexCli (command mapping) |
96-
| | | | | CodexCliExecutor |
97-
| | | | | `codex` child process |
98-
| | | | CodexCliResult |
99-
+------------------+ | | CodexEvent/CodexSession/DoctorReport|
100-
+-------------------+----------------------+
106+
+------------------+ +---------------------------------------------+
107+
| Java application | | codex-java-sdk |
108+
| |-->| Route 1 (local): CodexClient (facade) |
109+
| prompt / options | | | CodexCli (command mapping) |
110+
| | | | | CodexCliExecutor |
111+
| | | | | `codex` child process |
112+
| | | | CodexCliResult |
113+
| | | Route 2 (remote): CodexAppServerClient |
114+
| | | | JSON-RPC 2.0 over WebSocket |
115+
| | | | thread/start -> turn/start -> events |
116+
| | | CodexEvent/CodexSession/CodexDoctorReport |
117+
+------------------+ +-------------------+-------------------------+
101118
|
102119
v
103120
+-------------------------------------------+
104-
| Local `codex` CLI (exec, session, doctor, |
105-
| review, login, ...) |
121+
| Local `codex` CLI (route 1) or remote |
122+
| Codex app-server (route 2) |
106123
+-------------------------------------------+
107124
```
108125

109126
Single-module Maven project (`packaging: jar`). No child modules.
110127

111128
| Artifact | Responsibility |
112129
| :--- | :--- |
113-
| `io.github.easy4j:codex-java-sdk` | CLI facade, command mapping, subprocess executor, result & parsed models |
130+
| `io.github.easy4j:codex-java-sdk` | CLI facade, command mapping, subprocess executor, WebSocket app-server client, results & parsed models |
114131

115132
Key packages:
116133

117-
| Package | Content |
134+
| Package | Contents |
118135
| :--- | :--- |
119136
| `io.github.easy4j.codex` | `CodexClient`, `CodexClientConfig` |
120137
| `io.github.easy4j.codex.cli` | `CodexCli`, `CodexCliExecutor`, `CodexCliResult` |
138+
| `io.github.easy4j.codex.appserver` | `CodexAppServerClient`, `CodexAppServerConfig`, `AppServerTurnRequest`, `AppServerTurnResult`, `ThreadMappingCache`, `CodexAppServerException` |
121139
| `io.github.easy4j.codex.model` | `CodexEvent`, `CodexSession`, `CodexDoctorReport` |
122140

123141
## 5. Installation
@@ -197,6 +215,19 @@ There is no configuration file of its own. Key fields:
197215
| `strictConfig` | boolean | `false` | Fail on unknown config fields |
198216
| `enable` / `disable` | String[] | - | Features to enable / disable |
199217

218+
### 7.1 `CodexAppServerConfig` (app-server WebSocket route)
219+
220+
Plain POJO (Spring `@ConfigurationProperties`-bindable). Field names mirror the
221+
commonly used `CodexEndpoint` binding:
222+
223+
| Field | Type | Default | Description |
224+
| :--- | :--- | :--- | :--- |
225+
| `baseUrl` | String | - | App-server base URL (`ws://`/`wss://` as-is, `http://`/`https://` upgraded) |
226+
| `token` | String | - | Bearer token sent as `Authorization: Bearer <token>` on the handshake |
227+
| `connectTimeoutMillis` | int | `5000` | TCP/TLS + WebSocket handshake timeout |
228+
| `readTimeoutMillis` | int | `120000` | Upper bound for a whole turn (connect → `turn/completed`) |
229+
| `maxSessionMappings` | int | `1000` | Bound of the `sessionKey → threadId` LRU; evicted sessions start fresh threads |
230+
200231
## 8. Core Usage / API
201232

202233
### 8.1 JSONL events
@@ -222,6 +253,36 @@ try (CodexClient client = new CodexClient(config)) {
222253
}
223254
```
224255

256+
### 8.3 App-server WebSocket route (remote Codex)
257+
258+
```java
259+
import io.github.easy4j.codex.appserver.AppServerTurnRequest;
260+
import io.github.easy4j.codex.appserver.AppServerTurnResult;
261+
import io.github.easy4j.codex.appserver.CodexAppServerClient;
262+
import io.github.easy4j.codex.appserver.CodexAppServerConfig;
263+
264+
CodexAppServerConfig config = new CodexAppServerConfig();
265+
config.setBaseUrl("ws://codex-host:8081"); // http(s) is upgraded to ws(s) automatically
266+
config.setToken("capability-token");
267+
config.setReadTimeoutMillis(120_000);
268+
269+
try (CodexAppServerClient client = new CodexAppServerClient(config)) {
270+
AppServerTurnResult result = client.runTurn(AppServerTurnRequest.builder()
271+
.prompt("Fix the failing test")
272+
.sessionKey("chat-42") // enables thread/resume reuse
273+
.onDelta(delta -> System.out.print(delta)) // agentMessage deltas, in order
274+
.build());
275+
System.out.println(result.getThreadId() + " -> " + result.getContent());
276+
}
277+
```
278+
279+
The turn maps to `thread/start` (or `thread/resume` when `sessionKey` already
280+
maps to a thread id) → `turn/start` → `item/completed` (only agent messages
281+
surface) → `turn/completed`. Unknown notifications are logged at debug level
282+
and never interrupt the turn. Failures — connection, JSON-RPC error,
283+
`turn/failed`, `error`, premature close or read timeout — surface as
284+
`CodexAppServerException`.
285+
225286
## 9. Testing & Build
226287

227288
```bash
@@ -230,9 +291,9 @@ try (CodexClient client = new CodexClient(config)) {
230291

231292
- The build is configured with the JaCoCo Maven plugin (report + `check` goal with a
232293
90% line-coverage rule bound to the `verify` phase; `haltOnFailure=false`).
233-
- **Assumption**: the 1.0.x branch currently checks in no test sources under
234-
`src/test`; coverage thresholds are therefore enforced only when tests exist.
235-
- No CI workflow files are present under `.github/` in this worktree.
294+
- The active branch ships a full test suite (206 tests on `feature/3.0.x`), including
295+
end-to-end WebSocket contract tests against an in-process fake app-server.
296+
- CI workflow: `.github/workflows/ci.yml`.
236297

237298
## 10. Versioning & Branches
238299

0 commit comments

Comments
 (0)