feat: experimental flags (#10231)

## Problem being solved
- We need a single, reliable way to mark app-server API surface as
experimental so that:
  1. the runtime can reject experimental usage unless the client opts in
2. generated TS/JSON schemas can exclude experimental methods/fields for
stable clients.

Right now that’s easy to drift or miss when done ad-hoc.

## How to declare experimental methods and fields
- **Experimental method**: add `#[experimental("method/name")]` to the
`ClientRequest` variant in `client_request_definitions!`.
- **Experimental field**: on the params struct, derive `ExperimentalApi`
and annotate the field with `#[experimental("method/name.field")]` + set
`inspect_params: true` for the method variant so
`ClientRequest::experimental_reason()` inspects params for experimental
fields.

## How the macro solves it
- The new derive macro lives in
`codex-rs/codex-experimental-api-macros/src/lib.rs` and is used via
`#[derive(ExperimentalApi)]` plus `#[experimental("reason")]`
attributes.
- **Structs**:
- Generates `ExperimentalApi::experimental_reason(&self)` that checks
only annotated fields.
  - The “presence” check is type-aware:
    - `Option<T>`: `is_some_and(...)` recursively checks inner.
    - `Vec`/`HashMap`/`BTreeMap`: must be non-empty.
    - `bool`: must be `true`.
    - Other types: considered present (returns `true`).
- Registers each experimental field in an `inventory` with `(type_name,
serialized field name, reason)` and exposes `EXPERIMENTAL_FIELDS` for
that type. Field names are converted from `snake_case` to `camelCase`
for schema/TS filtering.
- **Enums**:
- Generates an exhaustive `match` returning `Some(reason)` for annotated
variants and `None` otherwise (no wildcard arm).
- **Wiring**:
- Runtime gating uses `ExperimentalApi::experimental_reason()` in
`codex-rs/app-server/src/message_processor.rs` to reject requests unless
`InitializeParams.capabilities.experimental_api == true`.
- Schema/TS export filters use the inventory list and
`EXPERIMENTAL_CLIENT_METHODS` from `client_request_definitions!` to
strip experimental methods/fields when `experimental_api` is false.
This commit is contained in:
jif-oai
2026-02-02 12:06:50 +01:00
committed by GitHub
parent 9513f18bfe
commit 3cc9122ee2
37 changed files with 2682 additions and 1076 deletions

View File

@@ -942,31 +942,6 @@
"title": "Model/listRequest",
"type": "object"
},
{
"description": "EXPERIMENTAL - list collaboration mode presets.",
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"collaborationMode/list"
],
"title": "CollaborationMode/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/CollaborationModeListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "CollaborationMode/listRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -5463,9 +5438,30 @@
],
"type": "object"
},
"InitializeCapabilities": {
"description": "Client-declared capabilities negotiated during initialize.",
"properties": {
"experimentalApi": {
"default": false,
"description": "Opt into receiving experimental API methods and fields.",
"type": "boolean"
}
},
"type": "object"
},
"InitializeParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"capabilities": {
"anyOf": [
{
"$ref": "#/definitions/InitializeCapabilities"
},
{
"type": "null"
}
]
},
"clientInfo": {
"$ref": "#/definitions/ClientInfo"
}
@@ -10408,29 +10404,6 @@
],
"type": "object"
},
"CollaborationModeListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - list collaboration mode presets.",
"title": "CollaborationModeListParams",
"type": "object"
},
"CollaborationModeListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - collaboration mode presets response.",
"properties": {
"data": {
"items": {
"$ref": "#/definitions/v2/CollaborationModeMask"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "CollaborationModeListResponse",
"type": "object"
},
"CollaborationModeMask": {
"description": "A mask for collaboration mode settings, allowing partial updates. All fields except `name` are optional, enabling selective updates.",
"properties": {
@@ -15319,15 +15292,6 @@
"null"
]
},
"dynamicTools": {
"items": {
"$ref": "#/definitions/v2/DynamicToolSpec"
},
"type": [
"array",
"null"
]
},
"ephemeral": {
"type": [
"boolean",