Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ConvosMetrics/Sources/ConvosMetrics/CoreActions.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
public protocol CoreActions: AnyObject, Sendable {
func startedConversation() async
func joinedConversation(verificationDuration: Float, memberCount: Int, hasAssistant: Bool, source: ConversationSource) async
func conversationJoinTimedOut(waitDuration: Float, source: ConversationSource) async
func invitedToConversation(memberCount: Int, hasAssistant: Bool) async
func addedAssistant(memberCount: Int) async
func assistantJoined(waitDuration: Float, surface: AssistantJoinSurface, memberCount: Int) async
func assistantJoinTimedOut(waitDuration: Float, surface: AssistantJoinSurface) async
func assistantJoinRescuedByPolling(streamAgeSecs: Float, pollTick: Int) async
func sentMessage(sendingTime: Float, memberCount: Int, attachmentTypes: [String], hasText: Bool, hasAssistant: Bool, isSuccess: Bool) async
func sharedConversation(memberCount: Int, hasAssistant: Bool, shareTarget: ShareTarget, hasExpiration: Bool, expiresAfterUse: Bool, isSuccess: Bool) async
func builtAgent(buildDuration: Float, instructionCharCount: Int, instructionWordCount: Int, attachmentTypes: [String], hasVoiceMemo: Bool, voiceMemoDuration: Float, connectionTypes: [String], entryMode: AgentBuilderEntryMode, isSuccess: Bool) async
Expand Down
16 changes: 16 additions & 0 deletions ConvosMetrics/Sources/ConvosMetrics/CoreEnums.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ extension ConversationSource {
}
}

public enum AssistantJoinSurface {
case statusMessage
case contactCard
case builderPlaceholder
}

extension AssistantJoinSurface {
public var metricsString: String {
switch self {
case .statusMessage: return "status_message"
case .contactCard: return "contact_card"
case .builderPlaceholder: return "builder_placeholder"
}
}
}

public enum ShareTarget {
case messages
case mail
Expand Down
37 changes: 37 additions & 0 deletions ConvosMetrics/Sources/ConvosMetrics/MetricsCoreActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ public final class MetricsCoreActions: CoreActions, @unchecked Sendable {
])
}

public func conversationJoinTimedOut(waitDuration: Float, source: ConversationSource) async {
delegate?.sendEvent(name: Self.eventConversationJoinTimedOut, properties: [
Self.paramWaitDuration: waitDuration,
Self.paramSource: source.metricsString,
])
}

public func invitedToConversation(memberCount: Int, hasAssistant: Bool) async {
delegate?.sendEvent(name: Self.eventInvitedToConversation, properties: [
Self.paramMemberCount: memberCount,
Expand All @@ -31,6 +38,28 @@ public final class MetricsCoreActions: CoreActions, @unchecked Sendable {
])
}

public func assistantJoined(waitDuration: Float, surface: AssistantJoinSurface, memberCount: Int) async {
delegate?.sendEvent(name: Self.eventAssistantJoined, properties: [
Self.paramWaitDuration: waitDuration,
Self.paramSurface: surface.metricsString,
Self.paramMemberCount: memberCount,
])
}

public func assistantJoinTimedOut(waitDuration: Float, surface: AssistantJoinSurface) async {
delegate?.sendEvent(name: Self.eventAssistantJoinTimedOut, properties: [
Self.paramWaitDuration: waitDuration,
Self.paramSurface: surface.metricsString,
])
}

public func assistantJoinRescuedByPolling(streamAgeSecs: Float, pollTick: Int) async {
delegate?.sendEvent(name: Self.eventAssistantJoinRescuedByPolling, properties: [
Self.paramStreamAgeSecs: streamAgeSecs,
Self.paramPollTick: pollTick,
])
}

public func sentMessage(sendingTime: Float, memberCount: Int, attachmentTypes: [String], hasText: Bool, hasAssistant: Bool, isSuccess: Bool) async {
delegate?.sendEvent(name: Self.eventSentMessage, properties: [
Self.paramSendingTime: sendingTime,
Expand Down Expand Up @@ -109,8 +138,12 @@ public final class MetricsCoreActions: CoreActions, @unchecked Sendable {

public static let eventStartedConversation: String = "started_conversation"
public static let eventJoinedConversation: String = "joined_conversation"
public static let eventConversationJoinTimedOut: String = "conversation_join_timed_out"
public static let eventInvitedToConversation: String = "invited_to_conversation"
public static let eventAddedAssistant: String = "added_assistant"
public static let eventAssistantJoined: String = "assistant_joined"
public static let eventAssistantJoinTimedOut: String = "assistant_join_timed_out"
public static let eventAssistantJoinRescuedByPolling: String = "assistant_join_rescued_by_polling"
public static let eventSentMessage: String = "sent_message"
public static let eventSharedConversation: String = "shared_conversation"
public static let eventBuiltAgent: String = "built_agent"
Expand All @@ -123,6 +156,10 @@ public final class MetricsCoreActions: CoreActions, @unchecked Sendable {
public static let paramMemberCount: String = "member_count"
public static let paramHasAssistant: String = "has_assistant"
public static let paramSource: String = "source"
public static let paramWaitDuration: String = "wait_duration"
public static let paramSurface: String = "surface"
public static let paramStreamAgeSecs: String = "stream_age_secs"
public static let paramPollTick: String = "poll_tick"
public static let paramSendingTime: String = "sending_time"
public static let paramAttachmentTypes: String = "attachment_types"
public static let paramHasText: String = "has_text"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ The section between the AUTOGEN markers below is regenerated by the build from t
|-------|----------|------------|
| `started_conversation` | `startedConversation` | _none_ |
| `joined_conversation` | `joinedConversation` | `verification_duration`: Float<br>`member_count`: Int<br>`has_assistant`: Boolean<br>`source`: ConversationSource { URL, Scan, Paste, Message } |
| `conversation_join_timed_out` | `conversationJoinTimedOut` | `wait_duration`: Float<br>`source`: ConversationSource { URL, Scan, Paste, Message } |
| `invited_to_conversation` | `invitedToConversation` | `member_count`: Int<br>`has_assistant`: Boolean |
| `added_assistant` | `addedAssistant` | `member_count`: Int |
| `assistant_joined` | `assistantJoined` | `wait_duration`: Float<br>`surface`: AssistantJoinSurface { STATUS_MESSAGE, CONTACT_CARD, BUILDER_PLACEHOLDER }<br>`member_count`: Int |
| `assistant_join_timed_out` | `assistantJoinTimedOut` | `wait_duration`: Float<br>`surface`: AssistantJoinSurface { STATUS_MESSAGE, CONTACT_CARD, BUILDER_PLACEHOLDER } |
| `assistant_join_rescued_by_polling` | `assistantJoinRescuedByPolling` | `stream_age_secs`: Float<br>`poll_tick`: Int |
| `sent_message` | `sentMessage` | `sending_time`: Float<br>`member_count`: Int<br>`attachment_types`: List<br>`has_text`: Boolean<br>`has_assistant`: Boolean<br>`is_success`: Boolean |
| `shared_conversation` | `sharedConversation` | `member_count`: Int<br>`has_assistant`: Boolean<br>`share_target`: ShareTarget { MESSAGES, MAIL, COPY, QR_CODE, AIRDROP, OTHER, CANCELLED }<br>`has_expiration`: Boolean<br>`expires_after_use`: Boolean<br>`is_success`: Boolean |
| `built_agent` | `builtAgent` | `build_duration`: Float<br>`instruction_char_count`: Int<br>`instruction_word_count`: Int<br>`attachment_types`: List<br>`has_voice_memo`: Boolean<br>`voice_memo_duration`: Float<br>`connection_types`: List<br>`entry_mode`: AgentBuilderEntryMode { COMPOSER, VOICE_MEMO }<br>`is_success`: Boolean |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ enum class PurchaseFailureReason {
UNKNOWN,
}

// The UI surface where the user watches an assistant join after requesting
// one: the in-chat pending status bubble (bare add), the agent contact card
// (template add / deep link), or the agent-builder placeholder (post-Make).
enum class AssistantJoinSurface {
STATUS_MESSAGE,
CONTACT_CARD,
BUILDER_PLACEHOLDER,
}

@CoreActionsTarget
interface CoreActions {
suspend fun startedConversation()
Expand All @@ -55,6 +64,16 @@ interface CoreActions {
source: ConversationSource
)

// Fired when an invite join is still unverified after the client wait
// window - the joiner watched the "Verifying" state without the
// conversation creator's device approving the join request (counterpart
// to joinedConversation's verificationDuration, which only samples
// successes).
suspend fun conversationJoinTimedOut(
waitDuration: Float,
source: ConversationSource
)

suspend fun invitedToConversation(
memberCount: Int,
hasAssistant: Boolean
Expand All @@ -64,6 +83,31 @@ interface CoreActions {
memberCount: Int
)

// Fired when a verified assistant actually appears in the conversation's
// member list after a join was requested. `waitDuration` is the seconds
// the user spent watching the joining/verifying state.
suspend fun assistantJoined(
waitDuration: Float,
surface: AssistantJoinSurface,
memberCount: Int
)

// Fired when no verified assistant appeared within the join wait window
// (the assistant backend gives up after about two minutes).
suspend fun assistantJoinTimedOut(
waitDuration: Float,
surface: AssistantJoinSurface
)

// Fired when the client's join-request polling fallback processed an
// assistant join request that the realtime message stream had missed -
// direct evidence of a silently dead stream. `streamAgeSecs` is the time
// since the stream last delivered any message (-1 when it never did).
suspend fun assistantJoinRescuedByPolling(
streamAgeSecs: Float,
pollTick: Int
)

suspend fun sentMessage(
sendingTime: Float,
memberCount: Int,
Expand Down