From 2575cf76a4c251fd7625d843a98f0ca8c17457d0 Mon Sep 17 00:00:00 2001 From: James Chu Date: Mon, 27 Apr 2026 18:24:38 -0400 Subject: [PATCH] Generalize product analytics event tracking --- .../analytics/src/analytics_client_tests.rs | 25 ++++--- codex-rs/analytics/src/events.rs | 26 +++---- codex-rs/analytics/src/reducer.rs | 16 +++-- .../schema/json/ClientRequest.json | 62 +++++++++++------ .../codex_app_server_protocol.schemas.json | 68 +++++++++++++------ .../codex_app_server_protocol.v2.schemas.json | 68 +++++++++++++------ .../v2/TrackProductAnalyticsEventParams.json | 57 ++++++++++++++++ ...> TrackProductAnalyticsEventResponse.json} | 2 +- .../json/v2/TrackUsageLimitBannerParams.json | 33 --------- .../schema/typescript/ClientRequest.ts | 4 +- ...nnerParams.ts => ProductAnalyticsEvent.ts} | 4 +- .../v2/TrackProductAnalyticsEventParams.ts | 6 ++ ... => TrackProductAnalyticsEventResponse.ts} | 2 +- ...rResponse.ts => UsageLimitBannerAction.ts} | 2 +- .../schema/typescript/v2/index.ts | 7 +- .../src/protocol/common.rs | 29 ++++---- .../app-server-protocol/src/protocol/v2.rs | 21 ++++-- codex-rs/app-server/README.md | 11 +-- .../app-server/src/codex_message_processor.rs | 6 +- codex-rs/app-server/src/message_processor.rs | 2 +- 20 files changed, 293 insertions(+), 158 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventParams.json rename codex-rs/app-server-protocol/schema/json/v2/{TrackUsageLimitBannerResponse.json => TrackProductAnalyticsEventResponse.json} (61%) delete mode 100644 codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerParams.json rename codex-rs/app-server-protocol/schema/typescript/v2/{TrackUsageLimitBannerParams.ts => ProductAnalyticsEvent.ts} (51%) create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventParams.ts rename codex-rs/app-server-protocol/schema/typescript/v2/{TrackUsageLimitBannerAction.ts => TrackProductAnalyticsEventResponse.ts} (68%) rename codex-rs/app-server-protocol/schema/typescript/v2/{TrackUsageLimitBannerResponse.ts => UsageLimitBannerAction.ts} (69%) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index ec1d7f4e28..6adae00b90 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -66,6 +66,7 @@ use codex_app_server_protocol::InitializeParams; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::NonSteerableTurnKind; use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile; +use codex_app_server_protocol::ProductAnalyticsEvent; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy; use codex_app_server_protocol::ServerNotification; @@ -74,8 +75,7 @@ use codex_app_server_protocol::Thread; use codex_app_server_protocol::ThreadResumeResponse; use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus; -use codex_app_server_protocol::TrackUsageLimitBannerAction; -use codex_app_server_protocol::TrackUsageLimitBannerParams; +use codex_app_server_protocol::TrackProductAnalyticsEventParams; use codex_app_server_protocol::Turn; use codex_app_server_protocol::TurnCompletedNotification; use codex_app_server_protocol::TurnError as AppServerTurnError; @@ -84,6 +84,7 @@ use codex_app_server_protocol::TurnStartedNotification; use codex_app_server_protocol::TurnStatus as AppServerTurnStatus; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::TurnSteerResponse; +use codex_app_server_protocol::UsageLimitBannerAction; use codex_app_server_protocol::UsageLimitBannerType; use codex_app_server_protocol::UserInput; use codex_login::default_client::DEFAULT_ORIGINATOR; @@ -2417,11 +2418,13 @@ async fn usage_limit_banner_shown_request_emits_credits_event() { AnalyticsFact::Request { connection_id: 7, request_id: RequestId::Integer(1), - request: Box::new(ClientRequest::TrackUsageLimitBanner { + request: Box::new(ClientRequest::TrackProductAnalyticsEvent { request_id: RequestId::Integer(1), - params: TrackUsageLimitBannerParams { - action: TrackUsageLimitBannerAction::Shown, - banner_type: UsageLimitBannerType::WorkspaceMemberCreditsDepleted, + params: TrackProductAnalyticsEventParams { + event: ProductAnalyticsEvent::UsageLimitBanner { + action: UsageLimitBannerAction::Shown, + banner_type: UsageLimitBannerType::WorkspaceMemberCreditsDepleted, + }, }, }), }, @@ -2453,11 +2456,13 @@ async fn usage_limit_banner_click_request_emits_usage_limit_event() { AnalyticsFact::Request { connection_id: 7, request_id: RequestId::Integer(1), - request: Box::new(ClientRequest::TrackUsageLimitBanner { + request: Box::new(ClientRequest::TrackProductAnalyticsEvent { request_id: RequestId::Integer(1), - params: TrackUsageLimitBannerParams { - action: TrackUsageLimitBannerAction::CtaClicked, - banner_type: UsageLimitBannerType::WorkspaceMemberUsageLimitReached, + params: TrackProductAnalyticsEventParams { + event: ProductAnalyticsEvent::UsageLimitBanner { + action: UsageLimitBannerAction::CtaClicked, + banner_type: UsageLimitBannerType::WorkspaceMemberUsageLimitReached, + }, }, }), }, diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 61b0f6d829..57dc1bf15b 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -20,8 +20,7 @@ use crate::facts::TurnSteerResult; use crate::facts::TurnSubmissionType; use crate::now_unix_seconds; use codex_app_server_protocol::CodexErrorInfo; -use codex_app_server_protocol::TrackUsageLimitBannerAction; -use codex_app_server_protocol::TrackUsageLimitBannerParams; +use codex_app_server_protocol::UsageLimitBannerAction; use codex_app_server_protocol::UsageLimitBannerType; use codex_login::default_client::originator; use codex_plugin::PluginTelemetryMetadata; @@ -591,35 +590,36 @@ pub(crate) fn plugin_state_event_type(state: PluginState) -> &'static str { } pub(crate) fn usage_limit_banner_event_request( - params: TrackUsageLimitBannerParams, + action: UsageLimitBannerAction, + banner_type: UsageLimitBannerType, ) -> UsageLimitBannerEventRequest { - let limit_reason = match params.banner_type { + let limit_reason = match banner_type { UsageLimitBannerType::WorkspaceMemberCreditsDepleted => "credits", UsageLimitBannerType::WorkspaceMemberUsageLimitReached => "usage_limit", }; - let banner_type = match params.banner_type { + let banner_type_param = match banner_type { UsageLimitBannerType::WorkspaceMemberCreditsDepleted => "workspace_member_credits_depleted", UsageLimitBannerType::WorkspaceMemberUsageLimitReached => { "workspace_member_usage_limit_reached" } }; - let cta_action = match params.banner_type { + let cta_action = match banner_type { UsageLimitBannerType::WorkspaceMemberCreditsDepleted => "notify_owner", UsageLimitBannerType::WorkspaceMemberUsageLimitReached => "request_increase", }; - let cta_action = match params.action { - TrackUsageLimitBannerAction::Shown => None, - TrackUsageLimitBannerAction::CtaClicked => Some(cta_action), + let cta_action = match action { + UsageLimitBannerAction::Shown => None, + UsageLimitBannerAction::CtaClicked => Some(cta_action), }; UsageLimitBannerEventRequest { - event_type: match params.action { - TrackUsageLimitBannerAction::Shown => "codex_usage_limit_banner_shown", - TrackUsageLimitBannerAction::CtaClicked => "codex_usage_limit_banner_cta_clicked", + event_type: match action { + UsageLimitBannerAction::Shown => "codex_usage_limit_banner_shown", + UsageLimitBannerAction::CtaClicked => "codex_usage_limit_banner_cta_clicked", }, event_params: UsageLimitBannerEventParams { platform: "codex_cli", - banner_type, + banner_type: banner_type_param, limit_reason, cta_action, }, diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index 4670ecb9fc..ad18845437 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -52,6 +52,7 @@ use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::ClientResponse; use codex_app_server_protocol::CodexErrorInfo; use codex_app_server_protocol::InitializeParams; +use codex_app_server_protocol::ProductAnalyticsEvent; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::TurnSteerResponse; @@ -333,10 +334,17 @@ impl AnalyticsReducer { }), ); } - ClientRequest::TrackUsageLimitBanner { params, .. } => { - out.push(TrackEventRequest::UsageLimitBanner( - usage_limit_banner_event_request(params), - )); + ClientRequest::TrackProductAnalyticsEvent { params, .. } => { + let event = match params.event { + ProductAnalyticsEvent::UsageLimitBanner { + action, + banner_type, + } => TrackEventRequest::UsageLimitBanner(usage_limit_banner_event_request( + action, + banner_type, + )), + }; + out.push(event); } _ => {} } diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 0ee16b662c..14aed4e57b 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2126,6 +2126,34 @@ ], "type": "object" }, + "ProductAnalyticsEvent": { + "oneOf": [ + { + "properties": { + "action": { + "$ref": "#/definitions/UsageLimitBannerAction" + }, + "bannerType": { + "$ref": "#/definitions/UsageLimitBannerType" + }, + "type": { + "enum": [ + "usageLimitBanner" + ], + "title": "UsageLimitBannerProductAnalyticsEventType", + "type": "string" + } + }, + "required": [ + "action", + "bannerType", + "type" + ], + "title": "UsageLimitBannerProductAnalyticsEvent", + "type": "object" + } + ] + }, "RealtimeOutputModality": { "enum": [ "text", @@ -4082,25 +4110,14 @@ ], "type": "object" }, - "TrackUsageLimitBannerAction": { - "enum": [ - "shown", - "cta_clicked" - ], - "type": "string" - }, - "TrackUsageLimitBannerParams": { + "TrackProductAnalyticsEventParams": { "properties": { - "action": { - "$ref": "#/definitions/TrackUsageLimitBannerAction" - }, - "bannerType": { - "$ref": "#/definitions/UsageLimitBannerType" + "event": { + "$ref": "#/definitions/ProductAnalyticsEvent" } }, "required": [ - "action", - "bannerType" + "event" ], "type": "object" }, @@ -4287,6 +4304,13 @@ ], "type": "object" }, + "UsageLimitBannerAction": { + "enum": [ + "shown", + "cta_clicked" + ], + "type": "string" + }, "UsageLimitBannerType": { "enum": [ "workspace_member_credits_depleted", @@ -5843,13 +5867,13 @@ }, "method": { "enum": [ - "account/usageLimitBanner/track" + "analytics/productEvent/track" ], - "title": "Account/usageLimitBanner/trackRequestMethod", + "title": "Analytics/productEvent/trackRequestMethod", "type": "string" }, "params": { - "$ref": "#/definitions/TrackUsageLimitBannerParams" + "$ref": "#/definitions/TrackProductAnalyticsEventParams" } }, "required": [ @@ -5857,7 +5881,7 @@ "method", "params" ], - "title": "Account/usageLimitBanner/trackRequest", + "title": "Analytics/productEvent/trackRequest", "type": "object" }, { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 650e6ab575..e639cc598a 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -1582,13 +1582,13 @@ }, "method": { "enum": [ - "account/usageLimitBanner/track" + "analytics/productEvent/track" ], - "title": "Account/usageLimitBanner/trackRequestMethod", + "title": "Analytics/productEvent/trackRequestMethod", "type": "string" }, "params": { - "$ref": "#/definitions/v2/TrackUsageLimitBannerParams" + "$ref": "#/definitions/v2/TrackProductAnalyticsEventParams" } }, "required": [ @@ -1596,7 +1596,7 @@ "method", "params" ], - "title": "Account/usageLimitBanner/trackRequest", + "title": "Analytics/productEvent/trackRequest", "type": "object" }, { @@ -12063,6 +12063,34 @@ ], "type": "object" }, + "ProductAnalyticsEvent": { + "oneOf": [ + { + "properties": { + "action": { + "$ref": "#/definitions/v2/UsageLimitBannerAction" + }, + "bannerType": { + "$ref": "#/definitions/v2/UsageLimitBannerType" + }, + "type": { + "enum": [ + "usageLimitBanner" + ], + "title": "UsageLimitBannerProductAnalyticsEventType", + "type": "string" + } + }, + "required": [ + "action", + "bannerType", + "type" + ], + "title": "UsageLimitBannerProductAnalyticsEvent", + "type": "object" + } + ] + }, "ProfileV2": { "additionalProperties": true, "properties": { @@ -16917,33 +16945,22 @@ }, "type": "object" }, - "TrackUsageLimitBannerAction": { - "enum": [ - "shown", - "cta_clicked" - ], - "type": "string" - }, - "TrackUsageLimitBannerParams": { + "TrackProductAnalyticsEventParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { - "action": { - "$ref": "#/definitions/v2/TrackUsageLimitBannerAction" - }, - "bannerType": { - "$ref": "#/definitions/v2/UsageLimitBannerType" + "event": { + "$ref": "#/definitions/v2/ProductAnalyticsEvent" } }, "required": [ - "action", - "bannerType" + "event" ], - "title": "TrackUsageLimitBannerParams", + "title": "TrackProductAnalyticsEventParams", "type": "object" }, - "TrackUsageLimitBannerResponse": { + "TrackProductAnalyticsEventResponse": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TrackUsageLimitBannerResponse", + "title": "TrackProductAnalyticsEventResponse", "type": "object" }, "Turn": { @@ -17370,6 +17387,13 @@ "title": "TurnSteerResponse", "type": "object" }, + "UsageLimitBannerAction": { + "enum": [ + "shown", + "cta_clicked" + ], + "type": "string" + }, "UsageLimitBannerType": { "enum": [ "workspace_member_credits_depleted", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index c55362a25e..5723e402cb 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -2288,13 +2288,13 @@ }, "method": { "enum": [ - "account/usageLimitBanner/track" + "analytics/productEvent/track" ], - "title": "Account/usageLimitBanner/trackRequestMethod", + "title": "Analytics/productEvent/trackRequestMethod", "type": "string" }, "params": { - "$ref": "#/definitions/TrackUsageLimitBannerParams" + "$ref": "#/definitions/TrackProductAnalyticsEventParams" } }, "required": [ @@ -2302,7 +2302,7 @@ "method", "params" ], - "title": "Account/usageLimitBanner/trackRequest", + "title": "Analytics/productEvent/trackRequest", "type": "object" }, { @@ -8737,6 +8737,34 @@ ], "type": "object" }, + "ProductAnalyticsEvent": { + "oneOf": [ + { + "properties": { + "action": { + "$ref": "#/definitions/UsageLimitBannerAction" + }, + "bannerType": { + "$ref": "#/definitions/UsageLimitBannerType" + }, + "type": { + "enum": [ + "usageLimitBanner" + ], + "title": "UsageLimitBannerProductAnalyticsEventType", + "type": "string" + } + }, + "required": [ + "action", + "bannerType", + "type" + ], + "title": "UsageLimitBannerProductAnalyticsEvent", + "type": "object" + } + ] + }, "ProfileV2": { "additionalProperties": true, "properties": { @@ -14803,33 +14831,22 @@ }, "type": "object" }, - "TrackUsageLimitBannerAction": { - "enum": [ - "shown", - "cta_clicked" - ], - "type": "string" - }, - "TrackUsageLimitBannerParams": { + "TrackProductAnalyticsEventParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { - "action": { - "$ref": "#/definitions/TrackUsageLimitBannerAction" - }, - "bannerType": { - "$ref": "#/definitions/UsageLimitBannerType" + "event": { + "$ref": "#/definitions/ProductAnalyticsEvent" } }, "required": [ - "action", - "bannerType" + "event" ], - "title": "TrackUsageLimitBannerParams", + "title": "TrackProductAnalyticsEventParams", "type": "object" }, - "TrackUsageLimitBannerResponse": { + "TrackProductAnalyticsEventResponse": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TrackUsageLimitBannerResponse", + "title": "TrackProductAnalyticsEventResponse", "type": "object" }, "Turn": { @@ -15256,6 +15273,13 @@ "title": "TurnSteerResponse", "type": "object" }, + "UsageLimitBannerAction": { + "enum": [ + "shown", + "cta_clicked" + ], + "type": "string" + }, "UsageLimitBannerType": { "enum": [ "workspace_member_credits_depleted", diff --git a/codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventParams.json b/codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventParams.json new file mode 100644 index 0000000000..c00f7d94dd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventParams.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ProductAnalyticsEvent": { + "oneOf": [ + { + "properties": { + "action": { + "$ref": "#/definitions/UsageLimitBannerAction" + }, + "bannerType": { + "$ref": "#/definitions/UsageLimitBannerType" + }, + "type": { + "enum": [ + "usageLimitBanner" + ], + "title": "UsageLimitBannerProductAnalyticsEventType", + "type": "string" + } + }, + "required": [ + "action", + "bannerType", + "type" + ], + "title": "UsageLimitBannerProductAnalyticsEvent", + "type": "object" + } + ] + }, + "UsageLimitBannerAction": { + "enum": [ + "shown", + "cta_clicked" + ], + "type": "string" + }, + "UsageLimitBannerType": { + "enum": [ + "workspace_member_credits_depleted", + "workspace_member_usage_limit_reached" + ], + "type": "string" + } + }, + "properties": { + "event": { + "$ref": "#/definitions/ProductAnalyticsEvent" + } + }, + "required": [ + "event" + ], + "title": "TrackProductAnalyticsEventParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventResponse.json similarity index 61% rename from codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerResponse.json rename to codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventResponse.json index 296d6bb9a3..a83fde1e8c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventResponse.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TrackUsageLimitBannerResponse", + "title": "TrackProductAnalyticsEventResponse", "type": "object" } \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerParams.json b/codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerParams.json deleted file mode 100644 index 702b002192..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/TrackUsageLimitBannerParams.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "TrackUsageLimitBannerAction": { - "enum": [ - "shown", - "cta_clicked" - ], - "type": "string" - }, - "UsageLimitBannerType": { - "enum": [ - "workspace_member_credits_depleted", - "workspace_member_usage_limit_reached" - ], - "type": "string" - } - }, - "properties": { - "action": { - "$ref": "#/definitions/TrackUsageLimitBannerAction" - }, - "bannerType": { - "$ref": "#/definitions/UsageLimitBannerType" - } - }, - "required": [ - "action", - "bannerType" - ], - "title": "TrackUsageLimitBannerParams", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index dbffeea457..5d7b234f7e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -68,7 +68,7 @@ import type { ThreadStartParams } from "./v2/ThreadStartParams"; import type { ThreadTurnsListParams } from "./v2/ThreadTurnsListParams"; import type { ThreadUnarchiveParams } from "./v2/ThreadUnarchiveParams"; import type { ThreadUnsubscribeParams } from "./v2/ThreadUnsubscribeParams"; -import type { TrackUsageLimitBannerParams } from "./v2/TrackUsageLimitBannerParams"; +import type { TrackProductAnalyticsEventParams } from "./v2/TrackProductAnalyticsEventParams"; import type { TurnInterruptParams } from "./v2/TurnInterruptParams"; import type { TurnStartParams } from "./v2/TurnStartParams"; import type { TurnSteerParams } from "./v2/TurnSteerParams"; @@ -77,4 +77,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "account/usageLimitBanner/track", id: RequestId, params: TrackUsageLimitBannerParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "analytics/productEvent/track", id: RequestId, params: TrackProductAnalyticsEventParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ProductAnalyticsEvent.ts similarity index 51% rename from codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerParams.ts rename to codex-rs/app-server-protocol/schema/typescript/v2/ProductAnalyticsEvent.ts index 3dde025b2b..3f4a2db1cc 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ProductAnalyticsEvent.ts @@ -1,7 +1,7 @@ // 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 { TrackUsageLimitBannerAction } from "./TrackUsageLimitBannerAction"; +import type { UsageLimitBannerAction } from "./UsageLimitBannerAction"; import type { UsageLimitBannerType } from "./UsageLimitBannerType"; -export type TrackUsageLimitBannerParams = { action: TrackUsageLimitBannerAction, bannerType: UsageLimitBannerType, }; +export type ProductAnalyticsEvent = { "type": "usageLimitBanner", action: UsageLimitBannerAction, bannerType: UsageLimitBannerType, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventParams.ts new file mode 100644 index 0000000000..17a589ea2f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventParams.ts @@ -0,0 +1,6 @@ +// 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 { ProductAnalyticsEvent } from "./ProductAnalyticsEvent"; + +export type TrackProductAnalyticsEventParams = { event: ProductAnalyticsEvent, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerAction.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventResponse.ts similarity index 68% rename from codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerAction.ts rename to codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventResponse.ts index 860738600d..58e6bf827e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerAction.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventResponse.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type TrackUsageLimitBannerAction = "shown" | "cta_clicked"; +export type TrackProductAnalyticsEventResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerAction.ts similarity index 69% rename from codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerResponse.ts rename to codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerAction.ts index 18f11213b3..9a52371a2f 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TrackUsageLimitBannerResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerAction.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type TrackUsageLimitBannerResponse = Record; +export type UsageLimitBannerAction = "shown" | "cta_clicked"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index a667d35a32..48f156c221 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -271,6 +271,7 @@ export type { PluginSummary } from "./PluginSummary"; export type { PluginUninstallParams } from "./PluginUninstallParams"; export type { PluginUninstallResponse } from "./PluginUninstallResponse"; export type { PluginsMigration } from "./PluginsMigration"; +export type { ProductAnalyticsEvent } from "./ProductAnalyticsEvent"; export type { ProfileV2 } from "./ProfileV2"; export type { RateLimitReachedType } from "./RateLimitReachedType"; export type { RateLimitSnapshot } from "./RateLimitSnapshot"; @@ -386,9 +387,8 @@ export type { ToolRequestUserInputParams } from "./ToolRequestUserInputParams"; export type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestion"; export type { ToolRequestUserInputResponse } from "./ToolRequestUserInputResponse"; export type { ToolsV2 } from "./ToolsV2"; -export type { TrackUsageLimitBannerAction } from "./TrackUsageLimitBannerAction"; -export type { TrackUsageLimitBannerParams } from "./TrackUsageLimitBannerParams"; -export type { TrackUsageLimitBannerResponse } from "./TrackUsageLimitBannerResponse"; +export type { TrackProductAnalyticsEventParams } from "./TrackProductAnalyticsEventParams"; +export type { TrackProductAnalyticsEventResponse } from "./TrackProductAnalyticsEventResponse"; export type { Turn } from "./Turn"; export type { TurnCompletedNotification } from "./TurnCompletedNotification"; export type { TurnDiffUpdatedNotification } from "./TurnDiffUpdatedNotification"; @@ -405,6 +405,7 @@ export type { TurnStartedNotification } from "./TurnStartedNotification"; export type { TurnStatus } from "./TurnStatus"; export type { TurnSteerParams } from "./TurnSteerParams"; export type { TurnSteerResponse } from "./TurnSteerResponse"; +export type { UsageLimitBannerAction } from "./UsageLimitBannerAction"; export type { UsageLimitBannerType } from "./UsageLimitBannerType"; export type { UserInput } from "./UserInput"; export type { WarningNotification } from "./WarningNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 7a3720066a..c5e809edde 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -573,9 +573,9 @@ client_request_definitions! { response: v2::SendAddCreditsNudgeEmailResponse, }, - TrackUsageLimitBanner => "account/usageLimitBanner/track" { - params: v2::TrackUsageLimitBannerParams, - response: v2::TrackUsageLimitBannerResponse, + TrackProductAnalyticsEvent => "analytics/productEvent/track" { + params: v2::TrackProductAnalyticsEventParams, + response: v2::TrackProductAnalyticsEventResponse, }, FeedbackUpload => "feedback/upload" { @@ -1468,23 +1468,28 @@ mod tests { } #[test] - fn serialize_track_usage_limit_banner() -> Result<()> { - let request = ClientRequest::TrackUsageLimitBanner { + fn serialize_track_product_analytics_event() -> Result<()> { + let request = ClientRequest::TrackProductAnalyticsEvent { request_id: RequestId::Integer(2), - params: v2::TrackUsageLimitBannerParams { - action: v2::TrackUsageLimitBannerAction::CtaClicked, - banner_type: v2::UsageLimitBannerType::WorkspaceMemberUsageLimitReached, + params: v2::TrackProductAnalyticsEventParams { + event: v2::ProductAnalyticsEvent::UsageLimitBanner { + action: v2::UsageLimitBannerAction::CtaClicked, + banner_type: v2::UsageLimitBannerType::WorkspaceMemberUsageLimitReached, + }, }, }; assert_eq!(request.id(), &RequestId::Integer(2)); - assert_eq!(request.method(), "account/usageLimitBanner/track"); + assert_eq!(request.method(), "analytics/productEvent/track"); assert_eq!( json!({ - "method": "account/usageLimitBanner/track", + "method": "analytics/productEvent/track", "id": 2, "params": { - "action": "cta_clicked", - "bannerType": "workspace_member_usage_limit_reached", + "event": { + "type": "usageLimitBanner", + "action": "cta_clicked", + "bannerType": "workspace_member_usage_limit_reached", + }, }, }), serde_json::to_value(&request)?, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 10d03404ce..26ca792c4d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2253,15 +2253,26 @@ pub struct SendAddCreditsNudgeEmailParams { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct TrackUsageLimitBannerParams { - pub action: TrackUsageLimitBannerAction, - pub banner_type: UsageLimitBannerType, +pub struct TrackProductAnalyticsEventParams { + pub event: ProductAnalyticsEvent, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(export_to = "v2/", tag = "type", rename_all = "camelCase")] +pub enum ProductAnalyticsEvent { + UsageLimitBanner { + action: UsageLimitBannerAction, + #[serde(rename = "bannerType")] + #[ts(rename = "bannerType")] + banner_type: UsageLimitBannerType, + }, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/", rename_all = "snake_case")] -pub enum TrackUsageLimitBannerAction { +pub enum UsageLimitBannerAction { Shown, CtaClicked, } @@ -2292,7 +2303,7 @@ pub struct SendAddCreditsNudgeEmailResponse { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct TrackUsageLimitBannerResponse {} +pub struct TrackProductAnalyticsEventResponse {} #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index d2260ddb72..96323744da 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1559,7 +1559,6 @@ Codex supports these authentication modes. The current mode is surfaced in `acco - `account/rateLimits/read` — fetch ChatGPT rate limits; updates arrive via `account/rateLimits/updated` (notify). - `account/rateLimits/updated` (notify) — emitted whenever a user's ChatGPT rate limits change. - `account/sendAddCreditsNudgeEmail` — ask ChatGPT to email the workspace owner about depleted credits or a reached usage limit. -- `account/usageLimitBanner/track` — record that the CLI workspace-owner nudge prompt was shown or its CTA was clicked. - `mcpServer/oauthLogin/completed` (notify) — emitted after a `mcpServer/oauth/login` flow finishes for a server; payload includes `{ name, success, error? }`. - `mcpServer/startupStatus/updated` (notify) — emitted when a configured MCP server's startup status changes for a loaded thread; payload includes `{ name, status, error }` where `status` is `starting`, `ready`, `failed`, or `cancelled`. @@ -1672,14 +1671,18 @@ Field notes: Use `creditType: "credits"` when workspace credits are depleted, or `creditType: "usage_limit"` when the workspace usage limit has been reached. If the owner was already notified recently, the response status is `cooldown_active`. -### 9) Track a usage-limit banner +## Product analytics endpoints + +- `analytics/productEvent/track` — record a typed client-side product analytics event. + +### Track a product analytics event ```json -{ "method": "account/usageLimitBanner/track", "id": 9, "params": { "action": "shown", "bannerType": "workspace_member_usage_limit_reached" } } +{ "method": "analytics/productEvent/track", "id": 9, "params": { "event": { "type": "usageLimitBanner", "action": "shown", "bannerType": "workspace_member_usage_limit_reached" } } } { "id": 9, "result": {} } ``` -Use `action: "shown"` when the banner is displayed, and `action: "cta_clicked"` when the user confirms it. The current workspace-owner nudge variants are `workspace_member_credits_depleted` and `workspace_member_usage_limit_reached`. +The current `usageLimitBanner` product event records when the CLI workspace-owner nudge prompt is shown or its CTA is clicked. Use `action: "shown"` when the banner is displayed, and `action: "cta_clicked"` when the user confirms it. The current workspace-owner nudge variants are `workspace_member_credits_depleted` and `workspace_member_usage_limit_reached`. ## Experimental API Opt-in diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index b27c6b3cf9..de68257ae6 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -211,7 +211,7 @@ use codex_app_server_protocol::ThreadUnarchivedNotification; use codex_app_server_protocol::ThreadUnsubscribeParams; use codex_app_server_protocol::ThreadUnsubscribeResponse; use codex_app_server_protocol::ThreadUnsubscribeStatus; -use codex_app_server_protocol::TrackUsageLimitBannerResponse; +use codex_app_server_protocol::TrackProductAnalyticsEventResponse; use codex_app_server_protocol::Turn; use codex_app_server_protocol::TurnError; use codex_app_server_protocol::TurnInterruptParams; @@ -1275,14 +1275,14 @@ impl CodexMessageProcessor { self.send_add_credits_nudge_email(to_connection_request_id(request_id), params) .await; } - ClientRequest::TrackUsageLimitBanner { + ClientRequest::TrackProductAnalyticsEvent { request_id, params: _, } => { self.outgoing .send_response( to_connection_request_id(request_id), - TrackUsageLimitBannerResponse {}, + TrackProductAnalyticsEventResponse {}, ) .await; } diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index c6e4c67dae..4581f35cfd 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -716,7 +716,7 @@ impl MessageProcessor { let connection_id = connection_request_id.connection_id; if let ClientRequest::TurnStart { request_id, .. } | ClientRequest::TurnSteer { request_id, .. } - | ClientRequest::TrackUsageLimitBanner { request_id, .. } = &codex_request + | ClientRequest::TrackProductAnalyticsEvent { request_id, .. } = &codex_request { self.analytics_events_client.track_request( connection_id.0,