mirror of
https://github.com/openai/codex.git
synced 2026-05-14 00:02:33 +00:00
Compare commits
7 Commits
dh--app-se
...
jchu/codex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e95c582fa | ||
|
|
2575cf76a4 | ||
|
|
f26d65b9c5 | ||
|
|
6c70786d88 | ||
|
|
094106eda9 | ||
|
|
42fe140d1a | ||
|
|
4cb7129579 |
@@ -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,6 +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::TrackProductAnalyticsEventParams;
|
||||
use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnCompletedNotification;
|
||||
use codex_app_server_protocol::TurnError as AppServerTurnError;
|
||||
@@ -82,6 +84,8 @@ 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;
|
||||
use codex_login::default_client::originator;
|
||||
@@ -2404,6 +2408,83 @@ async fn turn_completed_without_started_notification_emits_null_started_at() {
|
||||
assert_eq!(payload["event_params"]["total_tokens"], json!(null));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_limit_banner_shown_request_emits_credits_event() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut out = Vec::new();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
request: Box::new(ClientRequest::TrackProductAnalyticsEvent {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: TrackProductAnalyticsEventParams {
|
||||
event: ProductAnalyticsEvent::UsageLimitBanner {
|
||||
action: UsageLimitBannerAction::Shown,
|
||||
banner_type: UsageLimitBannerType::WorkspaceMemberCreditsDepleted,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
&mut out,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&out[0]).expect("serialize usage banner event");
|
||||
assert_eq!(
|
||||
json!({
|
||||
"event_type": "codex_usage_limit_banner_shown",
|
||||
"event_params": {
|
||||
"platform": "codex_cli",
|
||||
"banner_type": "workspace_member_credits_depleted",
|
||||
"limit_reason": "credits",
|
||||
},
|
||||
}),
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_limit_banner_click_request_emits_usage_limit_event() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut out = Vec::new();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
request: Box::new(ClientRequest::TrackProductAnalyticsEvent {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: TrackProductAnalyticsEventParams {
|
||||
event: ProductAnalyticsEvent::UsageLimitBanner {
|
||||
action: UsageLimitBannerAction::CtaClicked,
|
||||
banner_type: UsageLimitBannerType::WorkspaceMemberUsageLimitReached,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
&mut out,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&out[0]).expect("serialize usage banner event");
|
||||
assert_eq!(
|
||||
json!({
|
||||
"event_type": "codex_usage_limit_banner_cta_clicked",
|
||||
"event_params": {
|
||||
"platform": "codex_cli",
|
||||
"banner_type": "workspace_member_usage_limit_reached",
|
||||
"limit_reason": "usage_limit",
|
||||
"cta_action": "request_increase",
|
||||
},
|
||||
}),
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
fn sample_plugin_metadata() -> PluginTelemetryMetadata {
|
||||
PluginTelemetryMetadata {
|
||||
plugin_id: PluginId::parse("sample@test").expect("valid plugin id"),
|
||||
|
||||
@@ -20,6 +20,8 @@ use crate::facts::TurnSteerResult;
|
||||
use crate::facts::TurnSubmissionType;
|
||||
use crate::now_unix_seconds;
|
||||
use codex_app_server_protocol::CodexErrorInfo;
|
||||
use codex_app_server_protocol::UsageLimitBannerAction;
|
||||
use codex_app_server_protocol::UsageLimitBannerType;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||
@@ -61,6 +63,7 @@ pub(crate) enum TrackEventRequest {
|
||||
Compaction(Box<CodexCompactionEventRequest>),
|
||||
TurnEvent(Box<CodexTurnEventRequest>),
|
||||
TurnSteer(CodexTurnSteerEventRequest),
|
||||
UsageLimitBanner(UsageLimitBannerEventRequest),
|
||||
PluginUsed(CodexPluginUsedEventRequest),
|
||||
PluginInstalled(CodexPluginEventRequest),
|
||||
PluginUninstalled(CodexPluginEventRequest),
|
||||
@@ -68,6 +71,21 @@ pub(crate) enum TrackEventRequest {
|
||||
PluginDisabled(CodexPluginEventRequest),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct UsageLimitBannerEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: UsageLimitBannerEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct UsageLimitBannerEventParams {
|
||||
pub(crate) platform: &'static str,
|
||||
pub(crate) banner_type: &'static str,
|
||||
pub(crate) limit_reason: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) cta_action: Option<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct SkillInvocationEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
@@ -571,6 +589,43 @@ pub(crate) fn plugin_state_event_type(state: PluginState) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn usage_limit_banner_event_request(
|
||||
action: UsageLimitBannerAction,
|
||||
banner_type: UsageLimitBannerType,
|
||||
) -> UsageLimitBannerEventRequest {
|
||||
let limit_reason = match banner_type {
|
||||
UsageLimitBannerType::WorkspaceMemberCreditsDepleted => "credits",
|
||||
UsageLimitBannerType::WorkspaceMemberUsageLimitReached => "usage_limit",
|
||||
};
|
||||
let banner_type_param = match banner_type {
|
||||
UsageLimitBannerType::WorkspaceMemberCreditsDepleted => "workspace_member_credits_depleted",
|
||||
UsageLimitBannerType::WorkspaceMemberUsageLimitReached => {
|
||||
"workspace_member_usage_limit_reached"
|
||||
}
|
||||
};
|
||||
let cta_action = match banner_type {
|
||||
UsageLimitBannerType::WorkspaceMemberCreditsDepleted => "notify_owner",
|
||||
UsageLimitBannerType::WorkspaceMemberUsageLimitReached => "request_increase",
|
||||
};
|
||||
let cta_action = match action {
|
||||
UsageLimitBannerAction::Shown => None,
|
||||
UsageLimitBannerAction::CtaClicked => Some(cta_action),
|
||||
};
|
||||
|
||||
UsageLimitBannerEventRequest {
|
||||
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_param,
|
||||
limit_reason,
|
||||
cta_action,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn codex_app_metadata(
|
||||
tracking: &TrackEventsContext,
|
||||
app: AppInvocation,
|
||||
|
||||
@@ -28,6 +28,7 @@ use crate::events::plugin_state_event_type;
|
||||
use crate::events::subagent_parent_thread_id;
|
||||
use crate::events::subagent_source_name;
|
||||
use crate::events::subagent_thread_started_event_request;
|
||||
use crate::events::usage_limit_banner_event_request;
|
||||
use crate::facts::AnalyticsFact;
|
||||
use crate::facts::AnalyticsJsonRpcError;
|
||||
use crate::facts::AppMentionedInput;
|
||||
@@ -51,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;
|
||||
@@ -176,7 +178,7 @@ impl AnalyticsReducer {
|
||||
request_id,
|
||||
request,
|
||||
} => {
|
||||
self.ingest_request(connection_id, request_id, *request);
|
||||
self.ingest_request(connection_id, request_id, *request, out);
|
||||
}
|
||||
AnalyticsFact::Response {
|
||||
connection_id,
|
||||
@@ -309,6 +311,7 @@ impl AnalyticsReducer {
|
||||
connection_id: u64,
|
||||
request_id: RequestId,
|
||||
request: ClientRequest,
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
match request {
|
||||
ClientRequest::TurnStart { params, .. } => {
|
||||
@@ -331,6 +334,18 @@ impl AnalyticsReducer {
|
||||
}),
|
||||
);
|
||||
}
|
||||
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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2111,6 +2111,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",
|
||||
@@ -4067,6 +4095,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TrackProductAnalyticsEventParams": {
|
||||
"properties": {
|
||||
"event": {
|
||||
"$ref": "#/definitions/ProductAnalyticsEvent"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"event"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
@@ -4250,6 +4289,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"UsageLimitBannerAction": {
|
||||
"enum": [
|
||||
"shown",
|
||||
"cta_clicked"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"UsageLimitBannerType": {
|
||||
"enum": [
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"UserInput": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -5792,6 +5845,30 @@
|
||||
"title": "Account/sendAddCreditsNudgeEmailRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"analytics/productEvent/track"
|
||||
],
|
||||
"title": "Analytics/productEvent/trackRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/TrackProductAnalyticsEventParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Analytics/productEvent/trackRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1575,6 +1575,30 @@
|
||||
"title": "Account/sendAddCreditsNudgeEmailRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"analytics/productEvent/track"
|
||||
],
|
||||
"title": "Analytics/productEvent/trackRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/TrackProductAnalyticsEventParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Analytics/productEvent/trackRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -12024,6 +12048,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": {
|
||||
@@ -16878,6 +16930,24 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"TrackProductAnalyticsEventParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"event": {
|
||||
"$ref": "#/definitions/v2/ProductAnalyticsEvent"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"event"
|
||||
],
|
||||
"title": "TrackProductAnalyticsEventParams",
|
||||
"type": "object"
|
||||
},
|
||||
"TrackProductAnalyticsEventResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "TrackProductAnalyticsEventResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"Turn": {
|
||||
"properties": {
|
||||
"completedAt": {
|
||||
@@ -17302,6 +17372,20 @@
|
||||
"title": "TurnSteerResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"UsageLimitBannerAction": {
|
||||
"enum": [
|
||||
"shown",
|
||||
"cta_clicked"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"UsageLimitBannerType": {
|
||||
"enum": [
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"UserInput": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -2281,6 +2281,30 @@
|
||||
"title": "Account/sendAddCreditsNudgeEmailRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"analytics/productEvent/track"
|
||||
],
|
||||
"title": "Analytics/productEvent/trackRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/TrackProductAnalyticsEventParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Analytics/productEvent/trackRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -8698,6 +8722,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": {
|
||||
@@ -14764,6 +14816,24 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"TrackProductAnalyticsEventParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"event": {
|
||||
"$ref": "#/definitions/ProductAnalyticsEvent"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"event"
|
||||
],
|
||||
"title": "TrackProductAnalyticsEventParams",
|
||||
"type": "object"
|
||||
},
|
||||
"TrackProductAnalyticsEventResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "TrackProductAnalyticsEventResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"Turn": {
|
||||
"properties": {
|
||||
"completedAt": {
|
||||
@@ -15188,6 +15258,20 @@
|
||||
"title": "TurnSteerResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"UsageLimitBannerAction": {
|
||||
"enum": [
|
||||
"shown",
|
||||
"cta_clicked"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"UsageLimitBannerType": {
|
||||
"enum": [
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"UserInput": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
57
codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventParams.json
generated
Normal file
57
codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventParams.json
generated
Normal file
@@ -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"
|
||||
}
|
||||
5
codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventResponse.json
generated
Normal file
5
codex-rs/app-server-protocol/schema/json/v2/TrackProductAnalyticsEventResponse.json
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "TrackProductAnalyticsEventResponse",
|
||||
"type": "object"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
7
codex-rs/app-server-protocol/schema/typescript/v2/ProductAnalyticsEvent.ts
generated
Normal file
7
codex-rs/app-server-protocol/schema/typescript/v2/ProductAnalyticsEvent.ts
generated
Normal file
@@ -0,0 +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 { UsageLimitBannerAction } from "./UsageLimitBannerAction";
|
||||
import type { UsageLimitBannerType } from "./UsageLimitBannerType";
|
||||
|
||||
export type ProductAnalyticsEvent = { "type": "usageLimitBanner", action: UsageLimitBannerAction, bannerType: UsageLimitBannerType, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventParams.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventParams.ts
generated
Normal file
@@ -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, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventResponse.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/TrackProductAnalyticsEventResponse.ts
generated
Normal file
@@ -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 TrackProductAnalyticsEventResponse = Record<string, never>;
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerAction.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerAction.ts
generated
Normal file
@@ -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 UsageLimitBannerAction = "shown" | "cta_clicked";
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerType.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/UsageLimitBannerType.ts
generated
Normal file
@@ -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 UsageLimitBannerType = "workspace_member_credits_depleted" | "workspace_member_usage_limit_reached";
|
||||
@@ -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,6 +387,8 @@ export type { ToolRequestUserInputParams } from "./ToolRequestUserInputParams";
|
||||
export type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestion";
|
||||
export type { ToolRequestUserInputResponse } from "./ToolRequestUserInputResponse";
|
||||
export type { ToolsV2 } from "./ToolsV2";
|
||||
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";
|
||||
@@ -402,6 +405,8 @@ 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";
|
||||
export type { WebSearchAction } from "./WebSearchAction";
|
||||
|
||||
@@ -573,6 +573,11 @@ client_request_definitions! {
|
||||
response: v2::SendAddCreditsNudgeEmailResponse,
|
||||
},
|
||||
|
||||
TrackProductAnalyticsEvent => "analytics/productEvent/track" {
|
||||
params: v2::TrackProductAnalyticsEventParams,
|
||||
response: v2::TrackProductAnalyticsEventResponse,
|
||||
},
|
||||
|
||||
FeedbackUpload => "feedback/upload" {
|
||||
params: v2::FeedbackUploadParams,
|
||||
response: v2::FeedbackUploadResponse,
|
||||
@@ -1462,6 +1467,36 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_track_product_analytics_event() -> Result<()> {
|
||||
let request = ClientRequest::TrackProductAnalyticsEvent {
|
||||
request_id: RequestId::Integer(2),
|
||||
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(), "analytics/productEvent/track");
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "analytics/productEvent/track",
|
||||
"id": 2,
|
||||
"params": {
|
||||
"event": {
|
||||
"type": "usageLimitBanner",
|
||||
"action": "cta_clicked",
|
||||
"bannerType": "workspace_member_usage_limit_reached",
|
||||
},
|
||||
},
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_client_response() -> Result<()> {
|
||||
let cwd = absolute_path("/tmp");
|
||||
|
||||
@@ -2248,6 +2248,41 @@ pub struct SendAddCreditsNudgeEmailParams {
|
||||
pub credit_type: AddCreditsNudgeCreditType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
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 UsageLimitBannerAction {
|
||||
Shown,
|
||||
CtaClicked,
|
||||
}
|
||||
|
||||
#[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 UsageLimitBannerType {
|
||||
WorkspaceMemberCreditsDepleted,
|
||||
WorkspaceMemberUsageLimitReached,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
@@ -2263,6 +2298,11 @@ pub struct SendAddCreditsNudgeEmailResponse {
|
||||
pub status: AddCreditsNudgeEmailStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TrackProductAnalyticsEventResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
|
||||
@@ -1671,6 +1671,19 @@ 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`.
|
||||
|
||||
## Product analytics endpoints
|
||||
|
||||
- `analytics/productEvent/track` — record a typed client-side product analytics event.
|
||||
|
||||
### Track a product analytics event
|
||||
|
||||
```json
|
||||
{ "method": "analytics/productEvent/track", "id": 9, "params": { "event": { "type": "usageLimitBanner", "action": "shown", "bannerType": "workspace_member_usage_limit_reached" } } }
|
||||
{ "id": 9, "result": {} }
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
Some app-server methods and fields are intentionally gated behind an experimental capability with no backwards-compatible guarantees. This lets clients choose between:
|
||||
|
||||
@@ -211,6 +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::TrackProductAnalyticsEventResponse;
|
||||
use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnError;
|
||||
use codex_app_server_protocol::TurnInterruptParams;
|
||||
@@ -1281,6 +1282,17 @@ impl CodexMessageProcessor {
|
||||
self.send_add_credits_nudge_email(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::TrackProductAnalyticsEvent {
|
||||
request_id,
|
||||
params: _,
|
||||
} => {
|
||||
self.outgoing
|
||||
.send_response(
|
||||
to_connection_request_id(request_id),
|
||||
TrackProductAnalyticsEventResponse {},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::FeedbackUpload { request_id, params } => {
|
||||
self.upload_feedback(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
|
||||
@@ -715,7 +715,8 @@ impl MessageProcessor {
|
||||
}
|
||||
let connection_id = connection_request_id.connection_id;
|
||||
if let ClientRequest::TurnStart { request_id, .. }
|
||||
| ClientRequest::TurnSteer { request_id, .. } = &codex_request
|
||||
| ClientRequest::TurnSteer { request_id, .. }
|
||||
| ClientRequest::TrackProductAnalyticsEvent { request_id, .. } = &codex_request
|
||||
{
|
||||
self.analytics_events_client.track_request(
|
||||
connection_id.0,
|
||||
|
||||
Reference in New Issue
Block a user