Skip to content

Commit a226e3d

Browse files
Merge pull request #218 from SNodeC/codex/phase-a1-0-typed-foundation
Establish Codex typed API foundation and operation contracts
2 parents 6c03965 + cfc129e commit a226e3d

374 files changed

Lines changed: 144932 additions & 645 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tools/codex/app-server-protocol-source/** -text linguist-vendored

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
- 'tests/**'
1111
- 'tools/**'
1212
- 'docs/ai/openai/codex/**'
13-
- '.github/workflows/ci.yml'
13+
- '.github/workflows/**'
1414
pull_request:
1515
paths:
1616
- 'CMakeLists.txt'
@@ -19,7 +19,7 @@ on:
1919
- 'tests/**'
2020
- 'tools/**'
2121
- 'docs/ai/openai/codex/**'
22-
- '.github/workflows/ci.yml'
22+
- '.github/workflows/**'
2323

2424
jobs:
2525
gcc-debug:

cmake/Packing.cmake

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,36 @@ set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
6565
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
6666
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")
6767

68+
# Source packages are reproducibility artifacts, so they retain vendored
69+
# protocol evidence, schemas, fixtures, generators, tests, and documentation.
70+
# Keep local build products and execution-environment metadata out of those
71+
# archives. CPack's default ignore list does not exclude an in-tree build.
72+
set(
73+
CPACK_SOURCE_IGNORE_FILES
74+
"/CVS/"
75+
"/\\.svn/"
76+
"/\\.bzr/"
77+
"/\\.hg/"
78+
"/\\.git/"
79+
"/\\.agents/"
80+
"/\\.codex/"
81+
"/\\.cache/"
82+
"/\\.kdev4/"
83+
"/\\.qtcreator/"
84+
"/\\.vscode/"
85+
"/_CPack_Packages/"
86+
"/build[^/]*/"
87+
"/softwipe_build/"
88+
"/test1-cppcheck-build-dir/"
89+
"/__pycache__/"
90+
"\\.kdev4$"
91+
"\\.py[cod]$"
92+
"\\.swp$"
93+
"\\.#"
94+
"/#"
95+
"~$"
96+
)
97+
6898
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
6999
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
70100
set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON)

docs/ai/openai/codex/a1-typed-foundation.md

Lines changed: 436 additions & 0 deletions
Large diffs are not rendered by default.

docs/ai/openai/codex/app-server-client.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,25 @@ void setOnStateChanged(ai::openai::codex::Callbacks::StateChanged callback);
4040
void setOnDiagnostic(ai::openai::codex::Callbacks::DiagnosticReceived callback);
4141

4242
ai::openai::codex::AppServerClient::RawProtocol& raw() noexcept;
43+
ai::openai::codex::typed::Client& typed() noexcept;
4344
std::optional<ai::openai::codex::InitializeResult> getInitializeResult() const;
4445
```
4546
47+
Const overloads are available for both `raw()` and `typed()`. Include
48+
`ai/openai/codex/typed/Client.h` when calling grouped typed accessors:
49+
50+
```cpp
51+
client.typed().threads();
52+
client.typed().turns();
53+
client.typed().events();
54+
client.typed().requests();
55+
```
56+
57+
`typed::Client` delegates to the four facade objects attached to the same raw
58+
protocol engine. The legacy direct `threads()`, `turns()`, `events()`, and
59+
`requests()` accessors remain deprecated source-compatible forwarders; no
60+
future domain facade is added directly to `AppServerClient`.
61+
4662
The default constructor runs:
4763

4864
```text

docs/ai/openai/codex/backend-core.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ contexts, JSONL framing, Qt, WebSocket, or browser code. In particular, a Unix
2323
socket path is not backend state. Concrete listener and framing code belongs in
2424
`src/apps/codex-backend`.
2525

26-
## Phase A0 census boundary
26+
## Phase A0 census and A1 compatibility boundary
2727

2828
Phase A0 pins the Codex CLI 0.144.6 stable and experimental App Server schemas
2929
and registers every mechanically discovered protocol entry in the private
@@ -40,6 +40,16 @@ commands and state transitions after A1 completes the typed App Server layer.
4040
Unknown and future input continues through the existing bounded extension
4141
records in the meantime.
4242

43+
A1.0 migrates BackendCore to the grouped `client.typed()` accessors and adds no
44+
domain command or canonical-state meaning. A single production preservation
45+
helper converts typed-but-A2-unmodeled events into the existing
46+
`CodexExtensionReceived` path. It retains the surface identity, bounded raw
47+
payload, legacy optional decode error, and structured unknown-versus-malformed
48+
classification. Modeled events continue through their existing reducer cases.
49+
The structured classification is internal compatibility metadata; Frontend
50+
Protocol v1, its schema, snapshots, and remotely callable operations are
51+
unchanged.
52+
4353
## Ownership and construction
4454

4555
`BackendCore<ClientT>` directly owns exactly one concrete client:
@@ -146,7 +156,9 @@ not create duplicate state.
146156
The default reducer retains 64 diagnostics and 64 Codex extensions.
147157
Individual diagnostic messages are capped at 16 KiB. Canonical extension
148158
records cap the method at 4 KiB, the serialized payload at 64 KiB, and a
149-
decoding error at 16 KiB. Model reroutes are capped at 64 per turn, and each
159+
decoding error at 16 KiB. Structured decode-diagnostic surface, field path, and
160+
message text use the corresponding method/error bounds and retain saturated
161+
original-size accounting when truncated. Model reroutes are capped at 64 per turn, and each
150162
accumulated item-content stream at 4 MiB. When accumulated content exceeds its bound, the reducer retains the
151163
newest suffix and increments `droppedContentBytes`. Snapshots expose both the
152164
dropped byte count and `contentTruncated`, so a consumer never mistakes a
@@ -187,8 +199,9 @@ valid location but no stable ID remains observable through the bounded
187199
`codex/item-without-id` extension fallback. Unknown events, or malformed future
188200
item events that cannot identify an owning thread and turn, remain observable
189201
as bounded `CodexExtensionReceived` records with their original method or
190-
deliberate extension name, payload, and optional decoding error. They do not
191-
fail the backend and are not silently discarded.
202+
deliberate extension name, payload, optional decoding error, and structured
203+
forward-compatibility or protocol-warning classification. They do not fail the
204+
backend and are not silently discarded.
192205

193206
The immutable public snapshot retains the newest 64 extension records under
194207
stricter frontend-safe bounds: 1 KiB of UTF-8 method, 32 KiB of serialized

docs/ai/openai/codex/typed-api.md

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ Include `ai/openai/codex/typed/Client.h` and use the facades returned by an
1010
existing `AppServerClient`:
1111

1212
```cpp
13-
client.threads().start({.cwd = "/tmp/project"}, handler);
14-
client.threads().resume(threadId, {}, handler);
15-
client.threads().list({}, handler);
16-
client.threads().read(threadId, handler);
17-
client.turns().start(threadId, {typed::TextInput{"Hello"}}, {}, handler);
18-
client.turns().interrupt(threadId, turnId, handler);
13+
client.typed().threads().start({.cwd = "/tmp/project"}, handler);
14+
client.typed().threads().resume(threadId, {}, handler);
15+
client.typed().threads().list({}, handler);
16+
client.typed().threads().read(threadId, handler);
17+
client.typed().turns().start(threadId, {typed::TextInput{"Hello"}}, {}, handler);
18+
client.typed().turns().interrupt(threadId, turnId, handler);
1919
```
2020
21+
`typed::Client` is the installed, PIMPL-backed grouped facade. Its current
22+
accessors return the one existing `Threads`, `Turns`, `Events`, and `Requests`
23+
object owned by the `AppServerClient`; they do not allocate a second protocol
24+
engine. The old direct accessors remain source-compatible deprecated
25+
forwarders, for example `client.threads()` forwards to
26+
`client.typed().threads()`. New code should use the grouped form.
27+
2128
Unsupported or newly introduced operations remain available through
2229
`client.raw()`.
2330
@@ -37,13 +44,16 @@ including stability membership, lives in
3744
[coverage report](app-server-api-coverage.md) records inventory and
3845
implementation coverage as separate metrics. A private production
3946
`ProtocolSurfaceRegistry` is both the local disposition inventory checked by
40-
the coverage guard and the source used by runtime method and discriminator
41-
dispatch. Registration in that registry does not claim a typed implementation.
47+
the coverage guard and the source used by runtime method, top-level message,
48+
and implemented nested-union dispatch. A1.0 extends those same rows with
49+
offline authoritative request/result contracts, fixed domain slices, and
50+
mechanically derived completeness evidence. Registration alone still does not
51+
claim a typed implementation or schema completeness.
4252

43-
This A0 census does not expand the typed API described below. Unimplemented
44-
entries remain raw- or opaque-preserved according to their runtime disposition.
45-
Adding the owner-frozen stable typed request, notification, server-request,
46-
item, and delta surface is A1 work.
53+
The grouped API described below remains the pre-A1 request surface plus the
54+
A1.0 cross-cutting error model. Other stable operations remain raw- or
55+
opaque-preserved according to their registry disposition until their fixed
56+
A1.1–A1.4 domain slice.
4757

4858
The typed operation set is `thread/start`, `thread/resume`, `thread/list`,
4959
`thread/read`, `turn/start`, and `turn/interrupt`.
@@ -76,6 +86,16 @@ It also retains the client request ID and the original raw result JSON. A typed
7686
decode failure affects only that operation; it is not promoted to a transport
7787
or connection failure.
7888

89+
For a remote JSON-RPC error, `remoteError` remains the authoritative raw
90+
`ProtocolError`, including its code, message, and optional data. When that
91+
data has the pinned `TurnError` shape, the adapter additionally exposes
92+
`codexErrorInfo` and `codexErrorDiagnostic`. Failure to decode this structured
93+
supplement never changes the remote-error classification or the connection.
94+
The placement is proven by pinned production source: the
95+
`ActiveTurnNotSteerable` handler serializes a `TurnError` and assigns it to the
96+
JSON-RPC error's `data`; the otherwise unconstrained data field is not decoded
97+
by name similarity.
98+
7999
Submission failures are returned synchronously through the unchanged
80100
`RawProtocol::Submission` and do not invoke the completion. Accepted
81101
completions remain asynchronous. Typed adapters add neither a pending registry
@@ -87,8 +107,8 @@ handlers remain installed across an explicit stop/start.
87107

88108
## Events and items
89109

90-
`client.events().setOnEvent()` receives an `Event` variant. The typed methods
91-
currently include:
110+
`client.typed().events().setOnEvent()` receives an `Event` variant. The typed
111+
methods currently include:
92112

93113
- thread started and thread status changed;
94114
- turn started, completed, and failed completion classification;
@@ -118,6 +138,11 @@ complete `UserMessageItem::content` value held by the typed layer.
118138
completeness marker and any future string; an omitted marker has the
119139
schema-defined `full` default.
120140

141+
The current public item union is named `ThreadItem`, with `Item` retained as a
142+
source-compatible alias. `ResponseItem` is deliberately a distinct public type
143+
direction; A1.0 does not combine its pinned alternatives with `ThreadItem` or
144+
invent the A1.1 alternatives.
145+
121146
Unknown notification methods become `UnknownEvent`. Unknown item
122147
discriminators become `UnknownItem`. Unknown items retain any valid common item
123148
ID plus the thread and turn IDs supplied by their notification or parent turn.
@@ -126,9 +151,43 @@ A malformed item-local field also degrades to `UnknownItem` with a
126151
envelope becomes `UnknownEvent`. These values retain their original raw JSON
127152
and do not fail the connection.
128153

154+
## Structured decode diagnostics and Codex errors
155+
156+
Typed forward-compatibility handling has a structured diagnostic in addition
157+
to the temporarily retained optional string error:
158+
159+
```cpp
160+
struct DecodeDiagnostic {
161+
DecodeIssueKind kind;
162+
DecodeIssueSeverity severity;
163+
std::string surface;
164+
std::string fieldPath;
165+
std::string message;
166+
};
167+
```
168+
169+
`UnknownMethod`, `UnknownDiscriminator`, and `UnknownEnumValue` use
170+
`ForwardCompatibility`. A known method or discriminator with an invalid
171+
payload uses `MalformedKnownPayload` and `ProtocolWarning`. Diagnostic text
172+
contains protocol identities and paths, not payload values; the complete raw
173+
JSON is retained separately. Neither class alone fails the connection.
174+
175+
`CodexErrorInfo` has one public alternative for each of the 16 pinned known
176+
discriminators and an explicit `UnknownCodexErrorInfo` alternative. The four
177+
HTTP alternatives distinguish an omitted status from explicit `null` and a
178+
numeric value. `activeTurnNotSteerable.turnKind` is an open string-backed enum
179+
with helpers for `review` and `compact`; a future value remains typed and
180+
receives an `UnknownEnumValue` diagnostic. Every alternative retains its
181+
complete raw JSON.
182+
183+
The `error` notification preserves its legacy raw `TurnError` JSON and adds a
184+
structured `TurnError` view. That view distinguishes omitted, null, and value
185+
semantics for `additionalDetails` and `codexErrorInfo`. Malformed known error
186+
payloads degrade to the raw-preserving compatibility path.
187+
129188
## Server requests
130189
131-
`client.requests().setOnRequest()` currently classifies:
190+
`client.typed().requests().setOnRequest()` currently classifies:
132191
133192
- `item/commandExecution/requestApproval`;
134193
- `item/fileChange/requestApproval`;
@@ -184,23 +243,25 @@ through the event loop.
184243

185244
## ABI and forward compatibility
186245

187-
Task 3 adds public methods to `AppServerClient`, private implementation state,
188-
and new exported typed C++ classes. The `AppServerClient` object still
189-
contains only its existing `Impl` pointer, so its object layout is unchanged.
190-
`Protocol::ServerRequest` gains a `ServerRequestToken` field for occurrence
191-
ownership, which is a public raw-protocol type layout change. The library's
192-
normal C++ ABI/versioning policy applies to that change and to the new exported
193-
symbols and types.
246+
A1 is one deliberate in-progress C++ rebuild boundary. A1.0 adds
247+
`AppServerClient::typed()`, the one-pointer-PIMPL `typed::Client`, structured
248+
diagnostics, and public error variants. `AppServerClient` itself still contains
249+
only its existing `Impl` pointer; all grouped facades use the same raw engine.
250+
Public `std::variant` and aggregate layouts changed where the typed model
251+
required it, and this documentation does not claim binary compatibility for
252+
already-built consumers.
253+
254+
SOVERSION remains unchanged in A1.0. The single A1 SOVERSION action is deferred
255+
to A1 closure in A1.4.
194256

195257
Typed public headers are installed. Decoder headers under `detail/` remain
196258
private. Raw JSON on results, threads, turns, items, events, and server requests
197259
is the forward-compatibility escape hatch, and `client.raw()` remains
198260
available for protocol additions not yet represented by the typed layer.
199261

200-
Adding `UserMessageItem` extends the public `Item` variant, and adding partial
201-
common metadata enlarges the public `UnknownItem` structure. Existing variant
202-
indices are retained and the new `UnknownItem` member is appended, preserving
203-
ordinary field access and existing three-field aggregate initializers at source
204-
level. The layouts are not binary-compatible, however, and exhaustive item
205-
visitors must handle the new alternative; already-built C++ consumers must be
206-
rebuilt.
262+
The legacy direct facade accessors remain available at source level with
263+
deprecation diagnostics. `Item` remains an alias of `ThreadItem`. Existing
264+
optional string decode errors remain while structured classification becomes
265+
authoritative. Raw JSON on results, threads, turns, items, events, server
266+
requests, and Codex error alternatives remains the escape hatch for protocol
267+
growth.

0 commit comments

Comments
 (0)