1+ import { Port } from "../pitcher-protocol/messages/port" ;
2+ import { Emitter , EmitterSubscription , Event } from "../utils/event" ;
3+ import { SandboxSession } from "../types" ;
4+ import { Disposable } from "../utils/disposable" ;
5+ import { Client , createClient , createConfig } from "../api-clients/pint/client" ;
6+ import {
7+ IAgentClient ,
8+ IAgentClientPorts ,
9+ IAgentClientShells ,
10+ IAgentClientState ,
11+ IAgentClientFS ,
12+ IAgentClientSetup ,
13+ IAgentClientTasks ,
14+ IAgentClientSystem ,
15+ PickRawFsResult ,
16+ } from "../agent-client-interface" ;
17+ import {
18+ listPorts ,
19+ PortInfo ,
20+ streamPortsList ,
21+ } from "../api-clients/pint" ;
22+
23+
24+
25+ function parseStreamEvent < T > ( evt : unknown ) : T {
26+ if ( typeof evt !== "string" ) {
27+ return evt as T ;
28+ }
29+
30+ const evtWithoutDataPrefix = evt . substring ( 5 ) ;
31+
32+ return JSON . parse ( evtWithoutDataPrefix ) ;
33+ }
34+
35+ class PintPortsClient implements IAgentClientPorts {
36+ private onPortsUpdatedEmitter = new EmitterSubscription < Port [ ] > ( ( fire ) => {
37+ const abortController = new AbortController ( ) ;
38+
39+ streamPortsList ( {
40+ signal : abortController . signal ,
41+ headers : {
42+ headers : { Accept : "text/event-stream" } ,
43+ } ,
44+ } ) . then ( async ( { stream } ) => {
45+ for await ( const evt of stream ) {
46+ const data = parseStreamEvent < PortInfo [ ] > ( evt ) ;
47+
48+ fire (
49+ data . map ( ( pintPort ) => ( {
50+ port : pintPort . port ,
51+ url : pintPort . address ,
52+ } ) )
53+ ) ;
54+ }
55+ } ) ;
56+
57+ return Disposable . create ( ( ) => {
58+ abortController . abort ( ) ;
59+ } ) ;
60+ } ) ;
61+ onPortsUpdated = this . onPortsUpdatedEmitter . event ;
62+
63+ constructor ( private apiClient : Client , private sandboxId : string ) { }
64+
65+ async getPorts ( ) : Promise < Port [ ] > {
66+ const ports = await listPorts ( {
67+ client : this . apiClient ,
68+ } ) ;
69+
70+ return (
71+ ports . data ?. ports . map ( ( port ) => ( {
72+ port : port . port ,
73+ url : `https://${ this . sandboxId } -${ port . port } .csb.app` ,
74+ } ) ) ?? [ ]
75+ ) ;
76+ }
77+ }
78+
79+
80+ export class PintClient implements IAgentClient {
81+ static async create ( session : SandboxSession ) {
82+ return new PintClient ( session ) ;
83+ }
84+
85+ readonly type = "pint" as const ;
86+
87+ // Since there is no websocket connection or internal hibernation, the state
88+ // will always be CONNECTED. No state change events will be triggered
89+ readonly state = "CONNECTED" ;
90+ private onStateChangeEmitter = new Emitter < IAgentClientState > ( ) ;
91+ onStateChange = this . onStateChangeEmitter . event ;
92+
93+ sandboxId : string ;
94+ workspacePath : string ;
95+ isUpToDate : boolean ;
96+
97+ ports : IAgentClientPorts ;
98+ shells : IAgentClientShells ;
99+ fs : IAgentClientFS ;
100+ setup : IAgentClientSetup ;
101+ tasks : IAgentClientTasks ;
102+ system : IAgentClientSystem ;
103+
104+ constructor ( session : SandboxSession ) {
105+ this . sandboxId = session . sandboxId ;
106+ this . workspacePath = session . workspacePath ;
107+ this . isUpToDate = true ;
108+
109+ const apiClient = createClient (
110+ createConfig ( {
111+ baseUrl : session . pitcherURL ,
112+ headers : {
113+ Authorization : `Bearer ${ session . pitcherToken } ` ,
114+ } ,
115+ } )
116+ ) ;
117+
118+ this . ports = new PintPortsClient ( apiClient , this . sandboxId ) ;
119+ this . shells = { } as IAgentClientShells ; // Not implemented for Pint
120+ this . fs = { } as IAgentClientFS ; // Not implemented for Pint
121+ this . tasks = { } as IAgentClientTasks ; // Not implemented for Pint
122+ this . setup = { } as IAgentClientSetup ; // Not implemented for Pint
123+ this . system = { } as IAgentClientSystem ; // Not implemented for Pint
124+ }
125+
126+ ping ( ) : void { }
127+ async reconnect ( ) : Promise < void > { }
128+ async disconnect ( ) : Promise < void > { }
129+ dispose ( ) : void { }
130+ }
0 commit comments