11import { logger } from "@renderer/lib/logger" ;
22import type {
33 RepoAutonomyStatus ,
4+ SignalReportArtefact ,
45 SignalReportArtefactsResponse ,
56 SignalReportsResponse ,
67 Task ,
@@ -12,6 +13,87 @@ import { createApiClient, type Schemas } from "./generated";
1213
1314const log = logger . scope ( "posthog-client" ) ;
1415
16+ function isObjectRecord ( value : unknown ) : value is Record < string , unknown > {
17+ return typeof value === "object" && value !== null ;
18+ }
19+
20+ function optionalString ( value : unknown ) : string | null {
21+ return typeof value === "string" ? value : null ;
22+ }
23+
24+ function normalizeSignalReportArtefact (
25+ value : unknown ,
26+ ) : SignalReportArtefact | null {
27+ if ( ! isObjectRecord ( value ) ) {
28+ return null ;
29+ }
30+
31+ const id = optionalString ( value . id ) ;
32+ if ( ! id ) {
33+ return null ;
34+ }
35+
36+ const contentValue = isObjectRecord ( value . content ) ? value . content : null ;
37+ if ( ! contentValue ) {
38+ return null ;
39+ }
40+
41+ const content = optionalString ( contentValue . content ) ;
42+ const sessionId = optionalString ( contentValue . session_id ) ;
43+
44+ // The backend may return empty content objects when binary decode fails.
45+ if ( ! content && ! sessionId ) {
46+ return null ;
47+ }
48+
49+ return {
50+ id,
51+ type : optionalString ( value . type ) ?? "unknown" ,
52+ created_at : optionalString ( value . created_at ) ?? new Date ( 0 ) . toISOString ( ) ,
53+ content : {
54+ session_id : sessionId ?? "" ,
55+ start_time : optionalString ( contentValue . start_time ) ?? "" ,
56+ end_time : optionalString ( contentValue . end_time ) ?? "" ,
57+ distinct_id : optionalString ( contentValue . distinct_id ) ?? "" ,
58+ content : content ?? "" ,
59+ distance_to_centroid :
60+ typeof contentValue . distance_to_centroid === "number"
61+ ? contentValue . distance_to_centroid
62+ : null ,
63+ } ,
64+ } ;
65+ }
66+
67+ function parseSignalReportArtefactsPayload (
68+ value : unknown ,
69+ ) : SignalReportArtefactsResponse {
70+ const payload = isObjectRecord ( value ) ? value : null ;
71+ const rawResults = Array . isArray ( payload ?. results )
72+ ? payload . results
73+ : Array . isArray ( value )
74+ ? value
75+ : [ ] ;
76+
77+ const results = rawResults
78+ . map ( normalizeSignalReportArtefact )
79+ . filter ( ( artefact ) : artefact is SignalReportArtefact => artefact !== null ) ;
80+ const count =
81+ typeof payload ?. count === "number" ? payload . count : results . length ;
82+
83+ if ( rawResults . length > 0 && results . length === 0 ) {
84+ return {
85+ results : [ ] ,
86+ count : 0 ,
87+ unavailableReason : "invalid_payload" ,
88+ } ;
89+ }
90+
91+ return {
92+ results,
93+ count,
94+ } ;
95+ }
96+
1597export class PostHogAPIClient {
1698 private api : ReturnType < typeof createApiClient > ;
1799 private _teamId : number | null = null ;
@@ -551,23 +633,58 @@ export class PostHogAPIClient {
551633 const url = new URL (
552634 `${ this . api . baseUrl } /api/projects/${ teamId } /signal_reports/${ reportId } /artefacts/` ,
553635 ) ;
554- const response = await this . api . fetcher . fetch ( {
555- method : "get" ,
556- url,
557- path : `/api/projects/${ teamId } /signal_reports/${ reportId } /artefacts/` ,
558- } ) ;
636+ const path = `/api/projects/${ teamId } /signal_reports/${ reportId } /artefacts/` ;
559637
560- if ( ! response . ok ) {
561- throw new Error (
562- `Failed to fetch signal report artefacts: ${ response . statusText } ` ,
563- ) ;
564- }
638+ try {
639+ const response = await this . api . fetcher . fetch ( {
640+ method : "get" ,
641+ url,
642+ path,
643+ } ) ;
565644
566- const data = await response . json ( ) ;
567- return {
568- results : data . results ?? data ?? [ ] ,
569- count : data . count ?? data . results ?. length ?? data ?. length ?? 0 ,
570- } ;
645+ if ( ! response . ok ) {
646+ const responseText = await response . text ( ) ;
647+ const unavailableReason =
648+ response . status === 403
649+ ? "forbidden"
650+ : response . status === 404
651+ ? "not_found"
652+ : "request_failed" ;
653+
654+ log . warn ( "Signal report artefacts unavailable" , {
655+ teamId,
656+ reportId,
657+ status : response . status ,
658+ statusText : response . statusText ,
659+ body : responseText || undefined ,
660+ } ) ;
661+
662+ return { results : [ ] , count : 0 , unavailableReason } ;
663+ }
664+
665+ const data = ( await response . json ( ) ) as unknown ;
666+ const parsed = parseSignalReportArtefactsPayload ( data ) ;
667+
668+ if ( parsed . unavailableReason ) {
669+ log . warn ( "Signal report artefacts payload did not match schema" , {
670+ teamId,
671+ reportId,
672+ } ) ;
673+ }
674+
675+ return parsed ;
676+ } catch ( error ) {
677+ log . warn ( "Failed to fetch signal report artefacts" , {
678+ teamId,
679+ reportId,
680+ error,
681+ } ) ;
682+ return {
683+ results : [ ] ,
684+ count : 0 ,
685+ unavailableReason : "request_failed" ,
686+ } ;
687+ }
571688 }
572689
573690 async getRepositoryReadiness (
0 commit comments