|
| 1 | +## Context |
| 2 | + |
| 3 | +The SDK currently has three independent integration channels: |
| 4 | + |
| 5 | +1. HTTP REST through `OpenCodeHttpClient`. |
| 6 | +2. SSE through `OpenCodeSseClient`. |
| 7 | +3. Local CLI through `OpenCodeCli` and `OpenCodeCliExecutor`. |
| 8 | + |
| 9 | +The public surface is broad, but all CLI commands ultimately use one synchronous capture executor. This is suitable for short commands but not for event streaming, interactive sessions, or long-running servers. The HTTP chat facade also treats more than one event shape as append-only text, while upstream exposes explicit delta events. |
| 10 | + |
| 11 | +## Goals |
| 12 | + |
| 13 | +- Make the modeled Java API match upstream OpenCode command and event contracts. |
| 14 | +- Provide safe process execution for short-lived automation. |
| 15 | +- Provide streaming process execution without waiting for exit. |
| 16 | +- Provide lifecycle-managed long-running OpenCode processes. |
| 17 | +- Preserve independent HTTP, SSE, and CLI channels. |
| 18 | +- Keep old branch compatibility constraints explicit rather than hiding them behind one implementation. |
| 19 | + |
| 20 | +## Non-Goals |
| 21 | + |
| 22 | +- Reimplement the OpenCode Web UI in Java. |
| 23 | +- Make ACP a full protocol client in this change; this change provides a correct managed launcher boundary. A full ACP protocol client can be a separate capability. |
| 24 | +- Automatically abort a server-side session whenever a local SSE subscriber disconnects. |
| 25 | +- Default-enable `--auto` or any permission-bypassing option. |
| 26 | +- Promise multi-tenant security solely from per-process config or environment isolation. |
| 27 | + |
| 28 | +## Decisions |
| 29 | + |
| 30 | +### 1. Separate three CLI execution modes |
| 31 | + |
| 32 | +```text |
| 33 | +OpenCodeCli |
| 34 | + | |
| 35 | + +-- execute(...) short command, bounded capture |
| 36 | + | |
| 37 | + +-- stream(...) incremental stdout/stderr + exit future |
| 38 | + | |
| 39 | + +-- startManaged(...) long-running process handle |
| 40 | +``` |
| 41 | + |
| 42 | +The existing synchronous `execute` path remains the default for short commands. |
| 43 | + |
| 44 | +### 2. Concurrency belongs to the executor instance |
| 45 | + |
| 46 | +`maxConcurrentExecutions > 0` is enforced by a semaphore owned by `OpenCodeCliExecutor`. A permit is acquired before spawn and released after process termination or spawn failure. |
| 47 | + |
| 48 | +This prevents configuration that appears to limit concurrency while having no runtime effect. |
| 49 | + |
| 50 | +### 3. Output capture is bounded independently from debug-log truncation |
| 51 | + |
| 52 | +Debug log truncation only protects log messages; it does not bound process capture memory. |
| 53 | + |
| 54 | +Add explicit capture limits: |
| 55 | + |
| 56 | +- `maxStdoutBytes` |
| 57 | +- `maxStderrBytes` |
| 58 | + |
| 59 | +When the limit is exceeded, the result MUST mark the stream as truncated. Streaming consumers still receive data according to their configured mode, while retained tail/head policy is deterministic. |
| 60 | + |
| 61 | +### 4. Environment configuration is per executor invocation |
| 62 | + |
| 63 | +Add an execution context with: |
| 64 | + |
| 65 | +- inherit parent environment: boolean |
| 66 | +- environment overrides: map |
| 67 | +- working directory override |
| 68 | +- optional stdin mode |
| 69 | + |
| 70 | +Sensitive values MUST NOT be copied into debug output. |
| 71 | + |
| 72 | +### 5. Long-running processes have no ordinary command timeout |
| 73 | + |
| 74 | +A managed process uses separate time concepts: |
| 75 | + |
| 76 | +- startup timeout: time to become ready |
| 77 | +- graceful shutdown timeout: time allowed after stop request |
| 78 | +- lifetime: unbounded unless the caller explicitly sets one |
| 79 | + |
| 80 | +`OpenCodeServerHandle` exposes process state, actual address when known, exit future, stop/kill operations, and bounded log tail. |
| 81 | + |
| 82 | +### 6. Server readiness is protocol-based when possible |
| 83 | + |
| 84 | +For `serve` and `web`, readiness SHOULD be established via HTTP health probing against the resolved address instead of assuming that process spawn means service readiness. |
| 85 | + |
| 86 | +For random ports, the launcher MUST obtain the actual bound address from a stable OpenCode output contract or a supplied fixed port. If no reliable random-port discovery is available, the managed API MUST require an explicit port rather than parse arbitrary human-oriented text heuristically. |
| 87 | + |
| 88 | +### 7. CLI option arity is modeled exactly |
| 89 | + |
| 90 | +`--share` is a boolean switch. |
| 91 | + |
| 92 | +`--auto` is a boolean switch and defaults to false. |
| 93 | + |
| 94 | +Interactive mode is modeled separately from the ordinary non-interactive result API; adding `--interactive` to an options object alone does not imply full PTY support. |
| 95 | + |
| 96 | +### 8. SSE chat consumes deltas, snapshots update state |
| 97 | + |
| 98 | +Text accumulation is driven by delta events such as `message.part.delta` and the version-specific canonical text-delta event. |
| 99 | + |
| 100 | +`message.part.updated` is treated as a snapshot/state update and MUST NOT be blindly appended to accumulated text. |
| 101 | + |
| 102 | +### 9. SSE subscription readiness is explicit |
| 103 | + |
| 104 | +A subscription exposes: |
| 105 | + |
| 106 | +- connected/readiness future |
| 107 | +- terminal failure future or callback |
| 108 | +- cancellation |
| 109 | + |
| 110 | +The chat flow becomes: |
| 111 | + |
| 112 | +```text |
| 113 | +ensure session |
| 114 | + -> create subscription |
| 115 | + -> await SSE connected |
| 116 | + -> submit prompt_async |
| 117 | + -> consume deltas |
| 118 | + -> idle => complete |
| 119 | + -> session error / transport error => fail |
| 120 | +``` |
| 121 | + |
| 122 | +### 10. Client-side unsubscribe and server-side abort remain separate |
| 123 | + |
| 124 | +Cancelling a local stream MUST close its local subscription and timers. |
| 125 | + |
| 126 | +Aborting the OpenCode session is an explicit operation because another client may still be observing or relying on that server-side execution. |
| 127 | + |
| 128 | +A future convenience API may opt into “cancel and abort”, but it must be named explicitly. |
| 129 | + |
| 130 | +### 11. Configuration model preserves unknown fields |
| 131 | + |
| 132 | +The typed configuration model gains a `server` field. |
| 133 | + |
| 134 | +Unknown properties MUST be captured using Jackson's any-setter / any-getter mechanism appropriate to each branch's Jackson major version. This allows read-modify-write flows without silently discarding newly introduced upstream configuration fields. |
| 135 | + |
| 136 | +## Compatibility Strategy |
| 137 | + |
| 138 | +### feature/3.0.x |
| 139 | + |
| 140 | +- Java 21 |
| 141 | +- Jackson 3 `tools.jackson.*` |
| 142 | +- primary implementation and integration tests |
| 143 | + |
| 144 | +### feature/2.0.x |
| 145 | + |
| 146 | +- Java 17 |
| 147 | +- Jackson 2 |
| 148 | +- same public behavior, compatibility-specific imports/APIs |
| 149 | + |
| 150 | +### feature/1.0.x |
| 151 | + |
| 152 | +The branch metadata currently conflicts: CI describes a JDK 8 line while its POM compiler release is 17. Before backporting, the branch baseline MUST be corrected or explicitly redefined. Runtime hardening MUST NOT be declared complete on this branch until its build baseline is resolved and verified. |
| 153 | + |
| 154 | +## Failure Model |
| 155 | + |
| 156 | +Short CLI execution MUST distinguish at least: |
| 157 | + |
| 158 | +- success |
| 159 | +- non-zero exit |
| 160 | +- spawn failure |
| 161 | +- timeout |
| 162 | +- cancellation |
| 163 | + |
| 164 | +Streaming/managed execution MUST surface terminal process failure separately from individual stderr lines. |
| 165 | + |
| 166 | +SSE streaming MUST distinguish: |
| 167 | + |
| 168 | +- OpenCode session error event |
| 169 | +- transport failure |
| 170 | +- JSON/event decoding failure according to configured strictness |
| 171 | +- local cancellation |
| 172 | +- completion by idle event |
| 173 | + |
| 174 | +## Security |
| 175 | + |
| 176 | +- `--auto` MUST default to false. |
| 177 | +- passwords and environment secrets MUST never appear in SDK debug output. |
| 178 | +- exposing Web/Serve on non-loopback interfaces SHOULD require explicit caller configuration. |
| 179 | +- CORS configuration and authentication are independent concerns and MUST NOT be conflated. |
| 180 | + |
| 181 | +## Open Questions |
| 182 | + |
| 183 | +No unresolved question blocks P0 implementation. |
| 184 | + |
| 185 | +A full ACP Java protocol client is intentionally deferred to a separate proposal. |
0 commit comments