mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add stage field for experimental flags. (#10793)
- [x] Add stage field for experimental flags.
This commit is contained in:
committed by
GitHub
Unverified
parent
dcea972db8
commit
729b016515
@@ -11231,37 +11231,52 @@
|
||||
"ExperimentalFeature": {
|
||||
"properties": {
|
||||
"announcement": {
|
||||
"description": "Announcement copy shown to users when the feature is introduced.",
|
||||
"type": "string"
|
||||
"description": "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"defaultEnabled": {
|
||||
"description": "Whether this feature is enabled by default.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"description": "Short summary describing what the feature does.",
|
||||
"type": "string"
|
||||
"description": "Short summary describing what the feature does. Null when this feature is not in beta.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"displayName": {
|
||||
"description": "User-facing display name shown in the experimental features UI.",
|
||||
"type": "string"
|
||||
"description": "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Whether this feature is currently enabled in the loaded config.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"flagName": {
|
||||
"name": {
|
||||
"description": "Stable key used in config.toml and CLI flag toggles.",
|
||||
"type": "string"
|
||||
},
|
||||
"stage": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ExperimentalFeatureStage"
|
||||
}
|
||||
],
|
||||
"description": "Lifecycle stage of this feature flag."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"announcement",
|
||||
"defaultEnabled",
|
||||
"description",
|
||||
"displayName",
|
||||
"enabled",
|
||||
"flagName"
|
||||
"name",
|
||||
"stage"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -11311,6 +11326,45 @@
|
||||
"title": "ExperimentalFeatureListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureStage": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Feature is available for user testing and feedback.",
|
||||
"enum": [
|
||||
"beta"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature is still being built and not ready for broad use.",
|
||||
"enum": [
|
||||
"underDevelopment"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature is production-ready.",
|
||||
"enum": [
|
||||
"stable"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature is deprecated and should be avoided.",
|
||||
"enum": [
|
||||
"deprecated"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature flag is retained only for backwards compatibility.",
|
||||
"enum": [
|
||||
"removed"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FeedbackUploadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
|
||||
@@ -4,39 +4,93 @@
|
||||
"ExperimentalFeature": {
|
||||
"properties": {
|
||||
"announcement": {
|
||||
"description": "Announcement copy shown to users when the feature is introduced.",
|
||||
"type": "string"
|
||||
"description": "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"defaultEnabled": {
|
||||
"description": "Whether this feature is enabled by default.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"description": "Short summary describing what the feature does.",
|
||||
"type": "string"
|
||||
"description": "Short summary describing what the feature does. Null when this feature is not in beta.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"displayName": {
|
||||
"description": "User-facing display name shown in the experimental features UI.",
|
||||
"type": "string"
|
||||
"description": "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Whether this feature is currently enabled in the loaded config.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"flagName": {
|
||||
"name": {
|
||||
"description": "Stable key used in config.toml and CLI flag toggles.",
|
||||
"type": "string"
|
||||
},
|
||||
"stage": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ExperimentalFeatureStage"
|
||||
}
|
||||
],
|
||||
"description": "Lifecycle stage of this feature flag."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"announcement",
|
||||
"defaultEnabled",
|
||||
"description",
|
||||
"displayName",
|
||||
"enabled",
|
||||
"flagName"
|
||||
"name",
|
||||
"stage"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureStage": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Feature is available for user testing and feedback.",
|
||||
"enum": [
|
||||
"beta"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature is still being built and not ready for broad use.",
|
||||
"enum": [
|
||||
"underDevelopment"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature is production-ready.",
|
||||
"enum": [
|
||||
"stable"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature is deprecated and should be avoided.",
|
||||
"enum": [
|
||||
"deprecated"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Feature flag is retained only for backwards compatibility.",
|
||||
"enum": [
|
||||
"removed"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ExperimentalFeatureStage } from "./ExperimentalFeatureStage";
|
||||
|
||||
export type ExperimentalFeature = {
|
||||
/**
|
||||
* Stable key used in config.toml and CLI flag toggles.
|
||||
*/
|
||||
flagName: string,
|
||||
name: string,
|
||||
/**
|
||||
* Lifecycle stage of this feature flag.
|
||||
*/
|
||||
stage: ExperimentalFeatureStage,
|
||||
/**
|
||||
* User-facing display name shown in the experimental features UI.
|
||||
* Null when this feature is not in beta.
|
||||
*/
|
||||
displayName: string,
|
||||
displayName: string | null,
|
||||
/**
|
||||
* Short summary describing what the feature does.
|
||||
* Null when this feature is not in beta.
|
||||
*/
|
||||
description: string,
|
||||
description: string | null,
|
||||
/**
|
||||
* Announcement copy shown to users when the feature is introduced.
|
||||
* Null when this feature is not in beta.
|
||||
*/
|
||||
announcement: string,
|
||||
announcement: string | null,
|
||||
/**
|
||||
* Whether this feature is currently enabled in the loaded config.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ExperimentalFeatureStage = "beta" | "underDevelopment" | "stable" | "deprecated" | "removed";
|
||||
@@ -55,6 +55,7 @@ export type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
|
||||
export type { ExperimentalFeature } from "./ExperimentalFeature";
|
||||
export type { ExperimentalFeatureListParams } from "./ExperimentalFeatureListParams";
|
||||
export type { ExperimentalFeatureListResponse } from "./ExperimentalFeatureListResponse";
|
||||
export type { ExperimentalFeatureStage } from "./ExperimentalFeatureStage";
|
||||
export type { FeedbackUploadParams } from "./FeedbackUploadParams";
|
||||
export type { FeedbackUploadResponse } from "./FeedbackUploadResponse";
|
||||
export type { FileChangeApprovalDecision } from "./FileChangeApprovalDecision";
|
||||
|
||||
@@ -1055,18 +1055,39 @@ pub struct ExperimentalFeatureListParams {
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ExperimentalFeatureStage {
|
||||
/// Feature is available for user testing and feedback.
|
||||
Beta,
|
||||
/// Feature is still being built and not ready for broad use.
|
||||
UnderDevelopment,
|
||||
/// Feature is production-ready.
|
||||
Stable,
|
||||
/// Feature is deprecated and should be avoided.
|
||||
Deprecated,
|
||||
/// Feature flag is retained only for backwards compatibility.
|
||||
Removed,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExperimentalFeature {
|
||||
/// Stable key used in config.toml and CLI flag toggles.
|
||||
pub flag_name: String,
|
||||
pub name: String,
|
||||
/// Lifecycle stage of this feature flag.
|
||||
pub stage: ExperimentalFeatureStage,
|
||||
/// User-facing display name shown in the experimental features UI.
|
||||
pub display_name: String,
|
||||
/// Null when this feature is not in beta.
|
||||
pub display_name: Option<String>,
|
||||
/// Short summary describing what the feature does.
|
||||
pub description: String,
|
||||
/// Null when this feature is not in beta.
|
||||
pub description: Option<String>,
|
||||
/// Announcement copy shown to users when the feature is introduced.
|
||||
pub announcement: String,
|
||||
/// Null when this feature is not in beta.
|
||||
pub announcement: Option<String>,
|
||||
/// Whether this feature is currently enabled in the loaded config.
|
||||
pub enabled: bool,
|
||||
/// Whether this feature is enabled by default.
|
||||
|
||||
@@ -99,7 +99,7 @@ Example (from OpenAI's official VSCode extension):
|
||||
- `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review.
|
||||
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
|
||||
- `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids).
|
||||
- `experimentalFeature/list` — list experimental feature flags with metadata (flag name, display name, description, announcement, enabled/default-enabled) and cursor pagination.
|
||||
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
|
||||
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination).
|
||||
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
|
||||
- `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**).
|
||||
@@ -896,6 +896,7 @@ Examples of descriptor strings:
|
||||
- `thread/start.mockExperimentalField` (field-level gate)
|
||||
|
||||
### For maintainers: Adding experimental fields and methods
|
||||
|
||||
Use this checklist when introducing a field/method that should only be available when the client opts into experimental APIs.
|
||||
|
||||
At runtime, clients must send `initialize` with `capabilities.experimentalApi = true` to use experimental methods or fields.
|
||||
@@ -916,7 +917,7 @@ At runtime, clients must send `initialize` with `capabilities.experimentalApi =
|
||||
# Include experimental API fields/methods in fixtures.
|
||||
just write-app-server-schema --experimental
|
||||
```
|
||||
|
||||
|
||||
5. Verify the protocol crate:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -37,6 +37,7 @@ use codex_app_server_protocol::ExecOneOffCommandResponse;
|
||||
use codex_app_server_protocol::ExperimentalFeature as ApiExperimentalFeature;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListParams;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListResponse;
|
||||
use codex_app_server_protocol::ExperimentalFeatureStage as ApiExperimentalFeatureStage;
|
||||
use codex_app_server_protocol::FeedbackUploadParams;
|
||||
use codex_app_server_protocol::FeedbackUploadResponse;
|
||||
use codex_app_server_protocol::ForkConversationParams;
|
||||
@@ -3262,23 +3263,40 @@ impl CodexMessageProcessor {
|
||||
|
||||
let data = FEATURES
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
let Stage::Experimental {
|
||||
name,
|
||||
menu_description,
|
||||
announcement,
|
||||
} = spec.stage
|
||||
else {
|
||||
return None;
|
||||
.map(|spec| {
|
||||
let (stage, display_name, description, announcement) = match spec.stage {
|
||||
Stage::Experimental {
|
||||
name,
|
||||
menu_description,
|
||||
announcement,
|
||||
} => (
|
||||
ApiExperimentalFeatureStage::Beta,
|
||||
Some(name.to_string()),
|
||||
Some(menu_description.to_string()),
|
||||
Some(announcement.to_string()),
|
||||
),
|
||||
Stage::UnderDevelopment => (
|
||||
ApiExperimentalFeatureStage::UnderDevelopment,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
Stage::Stable => (ApiExperimentalFeatureStage::Stable, None, None, None),
|
||||
Stage::Deprecated => {
|
||||
(ApiExperimentalFeatureStage::Deprecated, None, None, None)
|
||||
}
|
||||
Stage::Removed => (ApiExperimentalFeatureStage::Removed, None, None, None),
|
||||
};
|
||||
Some(ApiExperimentalFeature {
|
||||
flag_name: spec.key.to_string(),
|
||||
display_name: name.to_string(),
|
||||
description: menu_description.to_string(),
|
||||
announcement: announcement.to_string(),
|
||||
|
||||
ApiExperimentalFeature {
|
||||
name: spec.key.to_string(),
|
||||
stage,
|
||||
display_name,
|
||||
description,
|
||||
announcement,
|
||||
enabled: config.features.enabled(spec.id),
|
||||
default_enabled: spec.default_enabled,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -3317,7 +3335,7 @@ impl CodexMessageProcessor {
|
||||
if start > total {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
format!("cursor {start} exceeds total experimental features {total}"),
|
||||
format!("cursor {start} exceeds total feature flags {total}"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
|
||||
@@ -6,6 +6,7 @@ use app_test_support::to_response;
|
||||
use codex_app_server_protocol::ExperimentalFeature;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListParams;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListResponse;
|
||||
use codex_app_server_protocol::ExperimentalFeatureStage;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_core::features::FEATURES;
|
||||
@@ -17,7 +18,7 @@ use tokio::time::timeout;
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn experimental_feature_list_returns_experimental_feature_metadata() -> Result<()> {
|
||||
async fn experimental_feature_list_returns_feature_metadata_with_stage() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
|
||||
@@ -36,24 +37,35 @@ async fn experimental_feature_list_returns_experimental_feature_metadata() -> Re
|
||||
let actual = to_response::<ExperimentalFeatureListResponse>(response)?;
|
||||
let expected_data = FEATURES
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
let Stage::Experimental {
|
||||
name,
|
||||
menu_description,
|
||||
announcement,
|
||||
} = spec.stage
|
||||
else {
|
||||
return None;
|
||||
.map(|spec| {
|
||||
let (stage, display_name, description, announcement) = match spec.stage {
|
||||
Stage::Experimental {
|
||||
name,
|
||||
menu_description,
|
||||
announcement,
|
||||
} => (
|
||||
ExperimentalFeatureStage::Beta,
|
||||
Some(name.to_string()),
|
||||
Some(menu_description.to_string()),
|
||||
Some(announcement.to_string()),
|
||||
),
|
||||
Stage::UnderDevelopment => {
|
||||
(ExperimentalFeatureStage::UnderDevelopment, None, None, None)
|
||||
}
|
||||
Stage::Stable => (ExperimentalFeatureStage::Stable, None, None, None),
|
||||
Stage::Deprecated => (ExperimentalFeatureStage::Deprecated, None, None, None),
|
||||
Stage::Removed => (ExperimentalFeatureStage::Removed, None, None, None),
|
||||
};
|
||||
|
||||
Some(ExperimentalFeature {
|
||||
flag_name: spec.key.to_string(),
|
||||
display_name: name.to_string(),
|
||||
description: menu_description.to_string(),
|
||||
announcement: announcement.to_string(),
|
||||
ExperimentalFeature {
|
||||
name: spec.key.to_string(),
|
||||
stage,
|
||||
display_name,
|
||||
description,
|
||||
announcement,
|
||||
enabled: spec.default_enabled,
|
||||
default_enabled: spec.default_enabled,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let expected = ExperimentalFeatureListResponse {
|
||||
|
||||
Reference in New Issue
Block a user