app-server: Add platform os and family to init response (#14527)

This allows the client to pick os-specific behavior while interacting
with the app server, e.g. to use proper path separators.
This commit is contained in:
Ruslan Nigmatullin 2026-03-13 12:07:54 -07:00 committed by GitHub
parent 3aabce9e0a
commit 50558e6507
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 59 additions and 5 deletions

View file

@ -2171,11 +2171,21 @@
"InitializeResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"platformFamily": {
"description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.",
"type": "string"
},
"platformOs": {
"description": "Operating system for the running app-server target, for example `\"macos\"`, `\"linux\"`, or `\"windows\"`.",
"type": "string"
},
"userAgent": {
"type": "string"
}
},
"required": [
"platformFamily",
"platformOs",
"userAgent"
],
"title": "InitializeResponse",

View file

@ -1,11 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"platformFamily": {
"description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.",
"type": "string"
},
"platformOs": {
"description": "Operating system for the running app-server target, for example `\"macos\"`, `\"linux\"`, or `\"windows\"`.",
"type": "string"
},
"userAgent": {
"type": "string"
}
},
"required": [
"platformFamily",
"platformOs",
"userAgent"
],
"title": "InitializeResponse",

View file

@ -2,4 +2,14 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InitializeResponse = { userAgent: string, };
export type InitializeResponse = { userAgent: string,
/**
* Platform family for the running app-server target, for example
* `"unix"` or `"windows"`.
*/
platformFamily: string,
/**
* Operating system for the running app-server target, for example
* `"macos"`, `"linux"`, or `"windows"`.
*/
platformOs: string, };

View file

@ -56,6 +56,12 @@ pub struct InitializeCapabilities {
#[serde(rename_all = "camelCase")]
pub struct InitializeResponse {
pub user_agent: String,
/// Platform family for the running app-server target, for example
/// `"unix"` or `"windows"`.
pub platform_family: String,
/// Operating system for the running app-server target, for example
/// `"macos"`, `"linux"`, or `"windows"`.
pub platform_os: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]

View file

@ -74,7 +74,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat
## Initialization
Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls on the same connection receive an `"Already initialized"` error.
Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services plus `platformFamily` and `platformOs` strings describing the app-server runtime target; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls on the same connection receive an `"Already initialized"` error.
`initialize.params.capabilities` also supports per-connection notification opt-out via `optOutNotificationMethods`, which is a list of exact method names to suppress for that connection. Matching is exact (no wildcards/prefixes). Unknown method names are accepted and ignored.

View file

@ -560,7 +560,11 @@ impl MessageProcessor {
}
let user_agent = get_codex_user_agent();
let response = InitializeResponse { user_agent };
let response = InitializeResponse {
user_agent,
platform_family: std::env::consts::FAMILY.to_string(),
platform_os: std::env::consts::OS.to_string(),
};
self.outgoing
.send_response(connection_request_id, response)
.await;

View file

@ -46,9 +46,15 @@ async fn initialize_uses_client_info_name_as_originator() -> Result<()> {
let JSONRPCMessage::Response(response) = message else {
anyhow::bail!("expected initialize response, got {message:?}");
};
let InitializeResponse { user_agent } = to_response::<InitializeResponse>(response)?;
let InitializeResponse {
user_agent,
platform_family,
platform_os,
} = to_response::<InitializeResponse>(response)?;
assert!(user_agent.starts_with("codex_vscode/"));
assert_eq!(platform_family, std::env::consts::FAMILY);
assert_eq!(platform_os, std::env::consts::OS);
Ok(())
}
@ -80,9 +86,15 @@ async fn initialize_respects_originator_override_env_var() -> Result<()> {
let JSONRPCMessage::Response(response) = message else {
anyhow::bail!("expected initialize response, got {message:?}");
};
let InitializeResponse { user_agent } = to_response::<InitializeResponse>(response)?;
let InitializeResponse {
user_agent,
platform_family,
platform_os,
} = to_response::<InitializeResponse>(response)?;
assert!(user_agent.starts_with("codex_originator_via_env_var/"));
assert_eq!(platform_family, std::env::consts::FAMILY);
assert_eq!(platform_os, std::env::consts::OS);
Ok(())
}

View file

@ -95,3 +95,5 @@ class ServerInfo(BaseModel):
class InitializeResponse(BaseModel):
serverInfo: ServerInfo | None = None
userAgent: str | None = None
platformFamily: str | None = None
platformOs: str | None = None