Skip to content

Commit 169e983

Browse files
committed
v0.13.0: docs, CHANGELOG, version bump
- docs/openai-proxy.md (new): canonical reference for the MVP. Scope, threat model, CLI surface, composition with cli-bridge (with the Phase 0 Q4 warning verbatim), trace surface, out-of-scope wall, worked Aider example. - docs/observability.md: proxy_request + proxy_response documented in the event table; schema version bump (4 to 5) noted. - docs/cli.md: openwar serve --openai-compat documented in the subcommand summary with the full flag surface and a pointer to docs/openai-proxy.md. - README.md: docs index gains an OpenAI-compatible proxy row. - CHANGELOG.md: full v0.13.0 entry covering the split rationale, the ten Phase 0 rulings, scope deferral to v0.13.1, and the constraints honored. - package.json: 0.12.1 to 0.13.0 (minor bump; new CLI subcommand, new HTTP server, new trace event types; no breaking changes). - package-lock.json regenerated under the new version. Gates: build clean, em-dash + sanity lint clean, 909/909 tests pass, coverage thresholds pass on every tracked directory.
1 parent efec618 commit 169e983

7 files changed

Lines changed: 231 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,64 @@
11
# Changelog
22

3+
## 0.13.0
4+
5+
OpenAI-compatible proxy server (MVP cut). `openwar serve --openai-compat` exposes a `POST /v1/chat/completions` endpoint plus `/v1/models` and `/healthz` over hand-rolled `node:http`. Any tool that speaks OpenAI's API (Aider, Continue, Cline, OpenAI SDKs, homegrown wrappers) can point at the server with one env-var change and consume OpenWar's discipline layer with zero modifications. The tool thinks it is talking to OpenAI; OpenWar routes the request through its phase machine, records a per-request trace, and dispatches the completion to whatever upstream adapter is configured.
6+
7+
This release is a **distribution trojan horse**, not a feature for existing OpenWar users. Existing operators keep using `openwar run` and `openwar chat`; the proxy is for the developer who already uses Aider against OpenAI and wants discipline without rewriting their workflow.
8+
9+
Originally scoped as one ship with the full `tools` surface. Phase 0 review caught that OpenAI Chat Completions has more gotchas than the brief acknowledged (`tool_choice` variants, `finish_reason` mapping, the `role: "tool"` + `tool_call_id` conversation pattern), and that bidirectional tool-call translation deserves its own ship. Split into v0.13.0 (HTTP + chat completions MVP without tools) and v0.13.1 (tool-call round-trip + PermissionBridge negotiation + cli-bridge composition polish + three-client integration examples). The split puts integration risk where it belongs and lets v0.13.0 ship a real distribution lever (any OpenAI-compat tool can hit the proxy for non-tool prompts today) while v0.13.1 closes the tool surface with v0.13.0 real-world usage signal informing the design. Five-for-five on the split pattern (v0.7 / v0.9 / v0.10 / v0.11 / v0.13).
10+
11+
### Added
12+
13+
- **`openwar serve` subcommand** with the `--openai-compat` flag. CLI surface: `--port` (default 1234, LM Studio convention), `--bind` (default 127.0.0.1; warns on 0.0.0.0), `--upstream-adapter` (auto-detected via the standard BYOK env-var precedence), `--upstream-model`, `--auth-token` (required unless `--no-auth`), `--no-auth` (warns every startup), `--workdir`, `--authorized-costs` (default `filesystem_read` only; startup banner explains the expansion pattern for agentic clients), `--max-concurrent` (default 4; excess returns 429), `--log-requests`.
14+
- **`src/serve/` directory** with eight modules: `types.ts` (ServeOptions + narrowed OpenAI types), `auth.ts` (constant-time bearer compare + OpenAI-shaped 401), `concurrency.ts` (atomic check-and-claim gate + OpenAI-shaped 429), `openai-parse.ts` (request body parsing + 400 mapping), `synthesize-brief.ts` (in-memory brief synthesis from an OpenAI request; mode=auto, scope_locked=true), `openai-translate.ts` (non-streaming response builder + denial refusal helper + trace-id header), `openai-streaming.ts` (hand-rolled SSE encoder with role / content / finish chunks and the `data: [DONE]\n\n` sentinel), `openai-router.ts` (the per-request dispatch loop), `server.ts` (bootstrap + graceful shutdown).
15+
- **`X-OpenWar-Trace-Id` response header** on every response so operators (and OpenWar-aware tooling) can `openwar inspect proxy-<uuid>` to audit a completed run.
16+
- **Per-request synthesized brief** at `~/.openwar/sessions/proxy-<uuid>.trace.ndjson`. The brief never touches disk under `~/.openwar/projects/<slug>/`. proxy sessions are project-less by design. Persistent permission grants from prior projects are NOT seeded into proxy sessions.
17+
- **Two new trace event variants** for proxy bookkeeping: `proxy_request` (start; carries request_id, client_addr, model, stream, tool_count, at, optional model_substituted_from) and `proxy_response` (end; carries request_id, status_code, duration_ms, bytes_written, cancelled, at). `TRACE_SCHEMA_VERSION` bumped 4 to 5, additive.
18+
- **`openwar inspect --trace` formatter** picks up the new event types with single-line shapes (`proxy_req` / `proxy_res`).
19+
- **Streaming SSE encoder** for streaming responses. Hand-rolled per the zero-new-deps constraint. Matches OpenAI's exact byte format including the `data: {json}\n\n` per-chunk shape and the terminal `data: [DONE]\n\n` sentinel that the OpenAI SDKs read for end-of-stream.
20+
- **Graceful shutdown on SIGINT.** First Ctrl-C drains in-flight requests for up to 5 seconds then closes; second Ctrl-C force-exits with code 130. Mirrors the chat REPL's escalation pattern from v0.11.1.
21+
- **60 new tests** across `tests/serve/auth.test.ts`, `tests/serve/concurrency.test.ts`, `tests/serve/openai-parse.test.ts`, `tests/serve/synthesize-brief.test.ts`, `tests/serve/openai-translate.test.ts` (covers both translate.ts and streaming.ts), `tests/serve/openai-router.test.ts` (in-process server on ephemeral port, fetch + MockAdapter end-to-end), `tests/serve/proxy-trace.test.ts` (round-trip of both new event types plus end-to-end trace file shape). Total 849 to 909 (60 new; brief estimated 25 for v0.13.0). Well above target because the OpenAI surface has more leaves than the brief estimated.
22+
23+
### Phase 0 rulings encoded
24+
25+
- **Q1 model substitution folded into `proxy_request.model_substituted_from?`** rather than a separate `proxy_model_substituted` event. Cleaner trace surface, same observability.
26+
- **Q2 mid-stream Phase 3 gate encoding = refusal text + `finish_reason: "content_filter"`.** The brief's original "tool_calls chunk with error result as the result" was shape-wise wrong (tool results come from the client in subsequent requests, not from the assistant in the same stream). v0.13.0 ships the encoding helpers (`denialRefusalText`, `encodeFinishChunk` with `content_filter`) ready for v0.13.1 when the tool surface lights up and Phase 3 actually fires in proxy mode.
27+
- **Q3 OpenWar tool name routing = `openwar:` prefix is the contract.** Bare tool names are client-owned and pass through to the upstream adapter. Aligns with the existing MCP namespace convention. Documented in `docs/openai-proxy.md`.
28+
- **Q4 cli-bridge upstream startup warning shipped verbatim.** Composition is supported, not silent; the operator gets explicit notice about per-request CLI spawn costs and the `--max-concurrent 1` recommendation.
29+
- **Q5 startup curl example shipped in the startup banner** per the brief's lean.
30+
- **Q6 `X-OpenWar-Trace-Id` response header** shipped per the brief's lean.
31+
- **Q7 429 shape = OpenAI `rate_limit_error`** with `code: "openwar_max_concurrent"` per the brief's lean.
32+
- **Q8 no legacy `prompt` field** support; documented.
33+
- **Q9 fixture strategy = fabricated for the automated suite.** Real-client end-to-end (Aider preferred) is a publisher pre-tag manual smoke check, documented in the Phase 4 handback.
34+
- **Q10 default port 1234** per the brief's lean.
35+
36+
### Out of scope for v0.13.0 (deferred to v0.13.1)
37+
38+
- Tool-call translation. v0.13.0 acknowledges `tools` at parse time and surfaces the count in `proxy_request.tool_count`, but does not yet round-trip tool calls. Plain-text Aider / Continue / Cline sessions work; agentic tool use does not.
39+
- PermissionBridge negotiation via `openwar:request_permission` tool_calls.
40+
- cli-bridge composition is structurally supported and warned about; agentic capability is gated on the tool surface.
41+
- Comprehensive three-client integration examples in `docs/openai-proxy.md`.
42+
43+
### Out of scope permanently
44+
45+
Legacy `/v1/completions`, embeddings, Assistants API, legacy `functions` field, WebSocket realtime, multi-tenant auth, TLS in-process (use a reverse proxy), persistent grants in proxy sessions, rate limiting beyond `--max-concurrent`. See `docs/openai-proxy.md` for the full scope wall.
46+
47+
### Changed
48+
49+
- **`docs/openai-proxy.md`** (new). canonical reference for the proxy: scope, threat model, CLI surface, composition with cli-bridge, trace surface, out-of-scope wall, Aider worked example.
50+
- **`docs/observability.md`**. two new event types documented; schema version bump noted.
51+
- **`docs/cli.md`**. `openwar serve` subcommand documented with the full flag surface and a pointer to `docs/openai-proxy.md`.
52+
- **`README.md`**. docs index gains an OpenAI-compatible proxy row.
53+
54+
### Constraints honored
55+
56+
- Zero new runtime deps. Hand-rolled `node:http`, hand-rolled SSE encoder. UUIDs from `node:crypto.randomUUID`. No Express, no Fastify, no body-parser.
57+
- TypeScript strict mode green.
58+
- Em-dash gate green. Sanity-regex gate green.
59+
- Backwards compatible. No existing CLI command, library export, or trace event shape changes. The serve subcommand, the new directory, and the two new trace events are all additive.
60+
- Localhost-default bind. Auth-required-by-default. Conservative `authorized_costs` default. All three security stances honored at startup; warnings emitted when the operator opts out.
61+
362
## 0.12.1
463

564
Squire structured-event adoption. v0.11.2 added no-op case arms so the build stayed compile-safe against Squire 1.1.0's four new event variants; v0.12.1 wires them up. The runtime now sees what a bridged CLI sees: every tool the bridged Claude Code or Gemini CLI invokes, every result, every thinking-mode token, and every usage report. The trace, the cost ledger, and `openwar inspect --tools` all surface the new data.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ If the agent skips the Confirmation Summary, the runtime asks it to restate befo
128128
| Adapters (Anthropic, OpenAI, Gemini, Grok, openai-compat, cli-bridge) | [`docs/adapters.md`](./docs/adapters.md) |
129129
| Native tools and MCP | [`docs/tools.md`](./docs/tools.md) |
130130
| PermissionBridge (v0.12+) | [`docs/permissions.md`](./docs/permissions.md) |
131+
| OpenAI-compatible proxy (v0.13+) | [`docs/openai-proxy.md`](./docs/openai-proxy.md) |
131132
| Observability and tracing (v0.8+) | [`docs/observability.md`](./docs/observability.md) |
132133
| Learning from run history (v0.9+) | [`docs/learning.md`](./docs/learning.md) |
133134
| Chat (v0.10+) | [`docs/chat.md`](./docs/chat.md) |

docs/cli.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ openwar roles # list registered roles
3535
openwar adapters
3636
openwar tools
3737
openwar mcp list | add <name> <cmd...> | remove <name> | test <name>
38+
openwar serve --openai-compat [--port <n>] [--bind <host>] # v0.13.0+
39+
[--upstream-adapter <id>] [--upstream-model <name>]
40+
[--auth-token <token>] [--no-auth]
41+
[--workdir <path>] [--authorized-costs <list>]
42+
[--max-concurrent <n>] [--log-requests]
3843
openwar version
3944
```
4045

46+
The `openwar serve --openai-compat` subcommand exposes OpenWar's runtime as an OpenAI Chat Completions HTTP server. Any tool that speaks OpenAI's API points at it and consumes OpenWar's discipline layer with zero changes on its end. v0.13.0 ships plain-text chat completions (streaming + non-streaming) with bearer-token auth, localhost-default bind, conservative `authorized_costs` defaults, and per-request trace files at `~/.openwar/sessions/proxy-<uuid>.trace.ndjson`. Tool surface lands in v0.13.1. See [`docs/openai-proxy.md`](./openai-proxy.md) for the full surface and worked examples.
47+
4148
## Common flags
4249

4350
| Flag | Where | Notes |

docs/observability.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ Event types in v0.8.0:
4747
| `bridged_tool_result` | v0.12.1+. Matching result for a prior `bridged_tool_call`. Carries `call_id`, `result`, `is_error`, `binary`, `at`. |
4848
| `bridged_thinking_delta` | v0.12.1+. Reasoning / thinking tokens emitted by the bridged CLI (e.g. Claude Code thinking blocks). Carries `delta`, `binary`, `at`. Separate from `text_delta` so consumers can filter or hide thinking independently from assistant-visible text. |
4949
| `bridged_usage` | v0.12.1+. Token-usage summary reported by the bridged CLI. Carries optional `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, plus `binary` and `at`. Also feeds the cost ledger when a multi-agent coordinator is running; emitted to the trace always so single-agent cli-bridge runs do not lose usage observability. **Budget arithmetic uses input + output only**; cache reads / writes are recorded for visibility but excluded from `tokens_used` to avoid tripping `--max-tokens` gates prematurely (cache reads bill at a fraction of normal input rates). |
50+
| `proxy_request` | v0.13.0+. A request hit the `openwar serve --openai-compat` HTTP server. Carries `request_id`, `client_addr`, `model`, `stream` (boolean), `tool_count`, `at`, optional `model_substituted_from` (when the client's requested model differed from the configured `--upstream-model` and the proxy fell back). |
51+
| `proxy_response` | v0.13.0+. The proxy finished responding to a request. Carries `request_id`, `status_code`, `duration_ms`, `bytes_written`, `cancelled` (boolean), `at`. The companion `proxy_request` event at session start is always present. |
5052
| `error` | Catchall for runtime exceptions surfaced at known seams. |
5153

52-
The schema is versioned. v0.8.0 shipped `version: 1`. v0.11.1 bumped to `version: 2` for the additive `tool_cancelled` event. v0.12.0 bumped to `version: 3` for the five additive `permission_*` events. v0.12.1 bumped to `version: 4` for the four additive `bridged_*` events. Each bump is forward-compatible; consumers should treat unknown event types as informational and ignore unknown optional fields.
54+
The schema is versioned. v0.8.0 shipped `version: 1`. v0.11.1 bumped to `version: 2` for the additive `tool_cancelled` event. v0.12.0 bumped to `version: 3` for the five additive `permission_*` events. v0.12.1 bumped to `version: 4` for the four additive `bridged_*` events. v0.13.0 bumped to `version: 5` for the two additive `proxy_*` events. Each bump is forward-compatible; consumers should treat unknown event types as informational and ignore unknown optional fields.
5355

5456
`openwar inspect <brief_id> --permissions` renders a per-grant audit row across the permission events. `openwar inspect <brief_id> --tools` groups output into a "Native tool calls" section (OpenWar's runtime) and a "Bridged CLI tool calls" section (events from inside a bridged CLI's own run, with the `binary` name). See [`docs/permissions.md`](./permissions.md) for the PermissionBridge surface and [`docs/adapters.md`](./adapters.md) for the cli-bridge surface.
5557

0 commit comments

Comments
 (0)