Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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 crates/goose-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ derive_utoipa!(Icon as IconSchema);
super::routes::config_management::remove_custom_provider,
super::routes::config_management::check_provider,
super::routes::config_management::set_config_provider,
super::routes::config_management::get_telemetry_status,
super::routes::config_management::set_telemetry_status,
super::routes::agent::start_agent,
super::routes::agent::resume_agent,
super::routes::agent::get_tools,
Expand Down Expand Up @@ -414,6 +416,8 @@ derive_utoipa!(Icon as IconSchema);
super::routes::config_management::UpdateCustomProviderRequest,
super::routes::config_management::CheckProviderRequest,
super::routes::config_management::SetProviderRequest,
super::routes::config_management::TelemetryStatusResponse,
super::routes::config_management::SetTelemetryRequest,
super::routes::action_required::ConfirmToolActionRequest,
super::routes::reply::ChatRequest,
super::routes::session::ImportSessionRequest,
Expand Down
49 changes: 49 additions & 0 deletions crates/goose-server/src/routes/config_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,51 @@ pub async fn check_provider(
Ok(())
}

#[derive(Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TelemetryStatusResponse {
pub enabled: bool,
}

#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SetTelemetryRequest {
pub enabled: bool,
}

#[utoipa::path(
get,
path = "/config/telemetry",
responses(
(status = 200, description = "Telemetry status retrieved successfully", body = TelemetryStatusResponse)
)
)]
pub async fn get_telemetry_status() -> Json<TelemetryStatusResponse> {
Json(TelemetryStatusResponse {
enabled: goose::posthog::is_telemetry_enabled(),
})
}

#[utoipa::path(
post,
path = "/config/telemetry",
request_body = SetTelemetryRequest,
responses(
(status = 200, description = "Telemetry preference updated successfully", body = TelemetryStatusResponse),
(status = 500, description = "Internal server error")
)
)]
pub async fn set_telemetry_status(
Json(request): Json<SetTelemetryRequest>,
) -> Result<Json<TelemetryStatusResponse>, StatusCode> {
goose::posthog::set_telemetry_enabled(request.enabled)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

Ok(Json(TelemetryStatusResponse {
enabled: goose::posthog::is_telemetry_enabled(),
}))
}

#[utoipa::path(
post,
path = "/config/set_provider",
Expand Down Expand Up @@ -884,6 +929,10 @@ pub fn routes(state: Arc<AppState>) -> Router {
.route("/config/custom-providers/{id}", get(get_custom_provider))
.route("/config/check_provider", post(check_provider))
.route("/config/set_provider", post(set_config_provider))
.route(
"/config/telemetry",
get(get_telemetry_status).post(set_telemetry_status),
)
.with_state(state)
}

Expand Down
31 changes: 29 additions & 2 deletions crates/goose/src/posthog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,42 @@ use std::sync::atomic::{AtomicBool, Ordering};

const POSTHOG_API_KEY: &str = "phc_RyX5CaY01VtZJCQyhSR5KFh6qimUy81YwxsEpotAftT";

static TELEMETRY_DISABLED: Lazy<AtomicBool> = Lazy::new(|| {
/// Config key for telemetry opt-out preference
pub const TELEMETRY_ENABLED_KEY: &str = "GOOSE_TELEMETRY_ENABLED";

static TELEMETRY_DISABLED_BY_ENV: Lazy<AtomicBool> = Lazy::new(|| {
std::env::var("GOOSE_TELEMETRY_OFF")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false)
.into()
});

/// Check if telemetry is enabled.
///
/// Returns false if:
/// - GOOSE_TELEMETRY_OFF environment variable is set to "1" or "true"
/// - GOOSE_TELEMETRY_ENABLED config value is set to false
///
/// Returns true otherwise (telemetry is opt-out, enabled by default)
pub fn is_telemetry_enabled() -> bool {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be honest, I don't think you need any of this. our config already reads environment variables and you can already read the config from the client. so no need for a special handler here or routes I would think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah thanks I didn't realize it did all that, will look into refactoring quickly 👍

// Environment variable takes precedence
if TELEMETRY_DISABLED_BY_ENV.load(Ordering::Relaxed) {
return false;
}

let config = Config::global();
config
.get_param::<bool>(TELEMETRY_ENABLED_KEY)
.unwrap_or(true)
}

pub fn set_telemetry_enabled(enabled: bool) -> Result<(), crate::config::ConfigError> {
let config = Config::global();
config.set_param(TELEMETRY_ENABLED_KEY, enabled)
}

pub fn emit_session_started() {
if TELEMETRY_DISABLED.load(Ordering::Relaxed) {
if !is_telemetry_enabled() {
return;
}

Expand Down
73 changes: 73 additions & 0 deletions ui/desktop/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,57 @@
}
}
},
"/config/telemetry": {
"get": {
"tags": [
"super::routes::config_management"
],
"operationId": "get_telemetry_status",
"responses": {
"200": {
"description": "Telemetry status retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TelemetryStatusResponse"
}
}
}
}
}
},
"post": {
"tags": [
"super::routes::config_management"
],
"operationId": "set_telemetry_status",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SetTelemetryRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Telemetry preference updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TelemetryStatusResponse"
}
}
}
},
"500": {
"description": "Internal server error"
}
}
}
},
"/config/upsert": {
"post": {
"tags": [
Expand Down Expand Up @@ -4977,6 +5028,17 @@
}
}
},
"SetTelemetryRequest": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "boolean"
}
}
},
"Settings": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -5149,6 +5211,17 @@
"inlineMessage"
]
},
"TelemetryStatusResponse": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "boolean"
}
}
},
"TextContent": {
"type": "object",
"required": [
Expand Down
2 changes: 2 additions & 0 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ErrorUI } from './components/ErrorBoundary';
import { ExtensionInstallModal } from './components/ExtensionInstallModal';
import { ToastContainer } from 'react-toastify';
import AnnouncementModal from './components/AnnouncementModal';
import TelemetryOptOutModal from './components/TelemetryOptOutModal';
import ProviderGuard from './components/ProviderGuard';
import { createSession } from './sessions';

Expand Down Expand Up @@ -702,6 +703,7 @@ export default function App() {
<AppInner />
</HashRouter>
<AnnouncementModal />
<TelemetryOptOutModal />
</ModelAndProviderProvider>
);
}
13 changes: 12 additions & 1 deletion ui/desktop/src/api/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetTelemetryStatusData, GetTelemetryStatusResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetTelemetryStatusData, SetTelemetryStatusErrors, SetTelemetryStatusResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';

export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/**
Expand Down Expand Up @@ -195,6 +195,17 @@ export const setConfigProvider = <ThrowOnError extends boolean = false>(options:

export const getSlashCommands = <ThrowOnError extends boolean = false>(options?: Options<GetSlashCommandsData, ThrowOnError>) => (options?.client ?? client).get<GetSlashCommandsResponses, unknown, ThrowOnError>({ url: '/config/slash_commands', ...options });

export const getTelemetryStatus = <ThrowOnError extends boolean = false>(options?: Options<GetTelemetryStatusData, ThrowOnError>) => (options?.client ?? client).get<GetTelemetryStatusResponses, unknown, ThrowOnError>({ url: '/config/telemetry', ...options });

export const setTelemetryStatus = <ThrowOnError extends boolean = false>(options: Options<SetTelemetryStatusData, ThrowOnError>) => (options.client ?? client).post<SetTelemetryStatusResponses, SetTelemetryStatusErrors, ThrowOnError>({
url: '/config/telemetry',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});

export const upsertConfig = <ThrowOnError extends boolean = false>(options: Options<UpsertConfigData, ThrowOnError>) => (options.client ?? client).post<UpsertConfigResponses, UpsertConfigErrors, ThrowOnError>({
url: '/config/upsert',
...options,
Expand Down
47 changes: 47 additions & 0 deletions ui/desktop/src/api/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,10 @@ export type SetSlashCommandRequest = {
slash_command?: string | null;
};

export type SetTelemetryRequest = {
enabled: boolean;
};

export type Settings = {
goose_model?: string | null;
goose_provider?: string | null;
Expand Down Expand Up @@ -865,6 +869,10 @@ export type SystemNotificationContent = {

export type SystemNotificationType = 'thinkingMessage' | 'inlineMessage';

export type TelemetryStatusResponse = {
enabled: boolean;
};

export type TextContent = {
_meta?: {
[key: string]: unknown;
Expand Down Expand Up @@ -1760,6 +1768,45 @@ export type GetSlashCommandsResponses = {

export type GetSlashCommandsResponse = GetSlashCommandsResponses[keyof GetSlashCommandsResponses];

export type GetTelemetryStatusData = {
body?: never;
path?: never;
query?: never;
url: '/config/telemetry';
};

export type GetTelemetryStatusResponses = {
/**
* Telemetry status retrieved successfully
*/
200: TelemetryStatusResponse;
};

export type GetTelemetryStatusResponse = GetTelemetryStatusResponses[keyof GetTelemetryStatusResponses];

export type SetTelemetryStatusData = {
body: SetTelemetryRequest;
path?: never;
query?: never;
url: '/config/telemetry';
};

export type SetTelemetryStatusErrors = {
/**
* Internal server error
*/
500: unknown;
};

export type SetTelemetryStatusResponses = {
/**
* Telemetry preference updated successfully
*/
200: TelemetryStatusResponse;
};

export type SetTelemetryStatusResponse = SetTelemetryStatusResponses[keyof SetTelemetryStatusResponses];

export type UpsertConfigData = {
body: UpsertConfigQuery;
path?: never;
Expand Down
4 changes: 4 additions & 0 deletions ui/desktop/src/components/ProviderGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { OllamaSetup } from './OllamaSetup';
import ApiKeyTester from './ApiKeyTester';
import { SwitchModelModal } from './settings/models/subcomponents/SwitchModelModal';
import { createNavigationHandler } from '../utils/navigationUtils';
import TelemetrySettings from './settings/app/TelemetrySettings';

import { Goose, OpenRouter, Tetrate } from './icons';

Expand Down Expand Up @@ -306,6 +307,9 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
Go to Provider Settings →
</button>
</div>
<div className="mt-6">
<TelemetrySettings isWelcome />
</div>
</div>
</div>
</div>
Expand Down
Loading