11import { isSupportedReasoningEffort } from "@posthog/agent/adapters/reasoning-effort" ;
2- import { type PermissionMode } from "@posthog/agent/execution-mode" ;
2+ import type { PermissionMode } from "@posthog/agent/execution-mode" ;
33import type {
44 ActionabilityJudgmentArtefact ,
55 AvailableSuggestedReviewer ,
@@ -24,11 +24,29 @@ import type {
2424 TaskRun ,
2525} from "@shared/types" ;
2626import type { CloudRunSource , PrAuthorshipMode } from "@shared/types/cloud" ;
27+ import type { SeatData } from "@shared/types/seat" ;
28+ import { SEAT_PRODUCT_KEY } from "@shared/types/seat" ;
2729import type { StoredLogEntry } from "@shared/types/session-events" ;
2830import { logger } from "@utils/logger" ;
2931import { buildApiFetcher } from "./fetcher" ;
3032import { createApiClient , type Schemas } from "./generated" ;
3133
34+ export class SeatSubscriptionRequiredError extends Error {
35+ redirectUrl : string ;
36+ constructor ( redirectUrl : string ) {
37+ super ( "Billing subscription required" ) ;
38+ this . name = "SeatSubscriptionRequiredError" ;
39+ this . redirectUrl = redirectUrl ;
40+ }
41+ }
42+
43+ export class SeatPaymentFailedError extends Error {
44+ constructor ( message ?: string ) {
45+ super ( message ?? "Payment failed" ) ;
46+ this . name = "SeatPaymentFailedError" ;
47+ }
48+ }
49+
3250const log = logger . scope ( "posthog-client" ) ;
3351
3452export type McpRecommendedServer = Schemas . RecommendedServer ;
@@ -1178,39 +1196,6 @@ export class PostHogAPIClient {
11781196 return await response . json ( ) ;
11791197 }
11801198
1181- /**
1182- * Get billing information for a specific organization.
1183- */
1184- async getOrgBilling ( orgId : string ) : Promise < {
1185- has_active_subscription : boolean ;
1186- customer_id : string | null ;
1187- } > {
1188- const url = new URL (
1189- `${ this . api . baseUrl } /api/organizations/${ orgId } /billing/` ,
1190- ) ;
1191- const response = await this . api . fetcher . fetch ( {
1192- method : "get" ,
1193- url,
1194- path : `/api/organizations/${ orgId } /billing/` ,
1195- } ) ;
1196-
1197- if ( ! response . ok ) {
1198- throw new Error (
1199- `Failed to fetch organization billing: ${ response . statusText } ` ,
1200- ) ;
1201- }
1202-
1203- const data = await response . json ( ) ;
1204- return {
1205- has_active_subscription :
1206- typeof data . has_active_subscription === "boolean"
1207- ? data . has_active_subscription
1208- : false ,
1209- customer_id :
1210- typeof data . customer_id === "string" ? data . customer_id : null ,
1211- } ;
1212- }
1213-
12141199 async getSignalReports (
12151200 params ?: SignalReportsQueryParams ,
12161201 ) : Promise < SignalReportsResponse > {
@@ -1741,6 +1726,145 @@ export class PostHogAPIClient {
17411726 }
17421727 }
17431728
1729+ async getMySeat ( ) : Promise < SeatData | null > {
1730+ try {
1731+ const url = new URL ( `${ this . api . baseUrl } /api/seats/me/` ) ;
1732+ url . searchParams . set ( "product_key" , SEAT_PRODUCT_KEY ) ;
1733+ const response = await this . api . fetcher . fetch ( {
1734+ method : "get" ,
1735+ url,
1736+ path : "/api/seats/me/" ,
1737+ } ) ;
1738+ return ( await response . json ( ) ) as SeatData ;
1739+ } catch ( error ) {
1740+ if ( this . isFetcherStatusError ( error , 404 ) ) {
1741+ return null ;
1742+ }
1743+ throw error ;
1744+ }
1745+ }
1746+
1747+ async createSeat ( planKey : string ) : Promise < SeatData > {
1748+ try {
1749+ const user = await this . getCurrentUser ( ) ;
1750+ const distinctId = user . distinct_id ;
1751+ if ( ! distinctId ) {
1752+ throw new Error ( "Cannot create seat: user has no distinct_id" ) ;
1753+ }
1754+ const url = new URL ( `${ this . api . baseUrl } /api/seats/` ) ;
1755+ const response = await this . api . fetcher . fetch ( {
1756+ method : "post" ,
1757+ url,
1758+ path : "/api/seats/" ,
1759+ overrides : {
1760+ body : JSON . stringify ( {
1761+ product_key : SEAT_PRODUCT_KEY ,
1762+ plan_key : planKey ,
1763+ user_distinct_id : distinctId ,
1764+ } ) ,
1765+ } ,
1766+ } ) ;
1767+ return ( await response . json ( ) ) as SeatData ;
1768+ } catch ( error ) {
1769+ this . throwSeatError ( error ) ;
1770+ }
1771+ }
1772+
1773+ async upgradeSeat ( planKey : string ) : Promise < SeatData > {
1774+ try {
1775+ const url = new URL ( `${ this . api . baseUrl } /api/seats/me/` ) ;
1776+ const response = await this . api . fetcher . fetch ( {
1777+ method : "patch" ,
1778+ url,
1779+ path : "/api/seats/me/" ,
1780+ overrides : {
1781+ body : JSON . stringify ( {
1782+ product_key : SEAT_PRODUCT_KEY ,
1783+ plan_key : planKey ,
1784+ } ) ,
1785+ } ,
1786+ } ) ;
1787+ return ( await response . json ( ) ) as SeatData ;
1788+ } catch ( error ) {
1789+ this . throwSeatError ( error ) ;
1790+ }
1791+ }
1792+
1793+ async cancelSeat ( ) : Promise < void > {
1794+ try {
1795+ const url = new URL ( `${ this . api . baseUrl } /api/seats/me/` ) ;
1796+ url . searchParams . set ( "product_key" , SEAT_PRODUCT_KEY ) ;
1797+ await this . api . fetcher . fetch ( {
1798+ method : "delete" ,
1799+ url,
1800+ path : "/api/seats/me/" ,
1801+ } ) ;
1802+ } catch ( error ) {
1803+ if ( this . isFetcherStatusError ( error , 204 ) ) {
1804+ return ;
1805+ }
1806+ this . throwSeatError ( error ) ;
1807+ }
1808+ }
1809+
1810+ async reactivateSeat ( ) : Promise < SeatData > {
1811+ try {
1812+ const url = new URL ( `${ this . api . baseUrl } /api/seats/me/reactivate/` ) ;
1813+ const response = await this . api . fetcher . fetch ( {
1814+ method : "post" ,
1815+ url,
1816+ path : "/api/seats/me/reactivate/" ,
1817+ overrides : {
1818+ body : JSON . stringify ( { product_key : SEAT_PRODUCT_KEY } ) ,
1819+ } ,
1820+ } ) ;
1821+ return ( await response . json ( ) ) as SeatData ;
1822+ } catch ( error ) {
1823+ this . throwSeatError ( error ) ;
1824+ }
1825+ }
1826+
1827+ private isFetcherStatusError ( error : unknown , status : number ) : boolean {
1828+ return error instanceof Error && error . message . includes ( `[${ status } ]` ) ;
1829+ }
1830+
1831+ private parseFetcherError ( error : unknown ) : {
1832+ status : number ;
1833+ body : Record < string , unknown > ;
1834+ } | null {
1835+ if ( ! ( error instanceof Error ) ) return null ;
1836+ const match = error . message . match ( / \[ ( \d + ) \] \s * ( .* ) / ) ;
1837+ if ( ! match ) return null ;
1838+ try {
1839+ return {
1840+ status : Number . parseInt ( match [ 1 ] , 10 ) ,
1841+ body : JSON . parse ( match [ 2 ] ) as Record < string , unknown > ,
1842+ } ;
1843+ } catch {
1844+ return { status : Number . parseInt ( match [ 1 ] , 10 ) , body : { } } ;
1845+ }
1846+ }
1847+
1848+ private throwSeatError ( error : unknown ) : never {
1849+ const parsed = this . parseFetcherError ( error ) ;
1850+
1851+ if ( parsed ) {
1852+ if (
1853+ parsed . status === 400 &&
1854+ typeof parsed . body . redirect_url === "string"
1855+ ) {
1856+ throw new SeatSubscriptionRequiredError ( parsed . body . redirect_url ) ;
1857+ }
1858+ if ( parsed . status === 402 ) {
1859+ const message =
1860+ typeof parsed . body . error === "string" ? parsed . body . error : undefined ;
1861+ throw new SeatPaymentFailedError ( message ) ;
1862+ }
1863+ }
1864+
1865+ throw error ;
1866+ }
1867+
17441868 /**
17451869 * Check if a feature flag is enabled for the current project.
17461870 * Returns true if the flag exists and is active, false otherwise.
0 commit comments