1+ import { readFile } from "fs/promises" ;
12import { createRequire } from "node:module" ;
3+ import { dirname , join } from "node:path" ;
24import { pathToFileURL } from "node:url" ;
35
6+ import { getOpencodeRuntimeDirCandidates } from "./opencode-runtime-paths.js" ;
7+
48const require = createRequire ( import . meta. url ) ;
59
610const COMPANION_PACKAGE_NAME = "opencode-antigravity-auth" ;
7- const COMPANION_IMPORT_SPECIFIER = `${ COMPANION_PACKAGE_NAME } /dist/src/constants.js` ;
11+ const COMPANION_JS_IMPORT_SPECIFIERS = [
12+ `${ COMPANION_PACKAGE_NAME } /dist/src/constants.js` ,
13+ `${ COMPANION_PACKAGE_NAME } /src/constants.js` ,
14+ ] as const ;
15+ const COMPANION_SOURCE_IMPORT_SPECIFIER = `${ COMPANION_PACKAGE_NAME } /src/constants.ts` ;
16+ const COMPANION_PACKAGE_JSON_SPECIFIER = `${ COMPANION_PACKAGE_NAME } /package.json` ;
17+ const COMPANION_DIRECT_CANDIDATE_PATHS = [
18+ [ "dist" , "src" , "constants.js" ] ,
19+ [ "src" , "constants.ts" ] ,
20+ [ "src" , "constants.js" ] ,
21+ [ "dist" , "index.js" ] ,
22+ ] as const ;
823const COMPANION_MISSING_ERROR = `Install ${ COMPANION_PACKAGE_NAME } separately to enable Google Antigravity quota` ;
924const COMPANION_INVALID_ERROR = `Installed ${ COMPANION_PACKAGE_NAME } package is incompatible` ;
1025
@@ -51,6 +66,10 @@ type CompanionModule = {
5166 ANTIGRAVITY_CLIENT_SECRET ?: unknown ;
5267} ;
5368
69+ type CompanionResolutionContext = {
70+ packageFound : boolean ;
71+ } ;
72+
5473let resolvedCompanionStatePromise : Promise < ResolvedCompanionState > | null = null ;
5574
5675function isModuleNotFoundError ( error : unknown ) : boolean {
@@ -67,94 +86,288 @@ function isModuleNotFoundError(error: unknown): boolean {
6786 return message . includes ( "Cannot find module" ) ;
6887}
6988
70- async function resolveCompanionState ( ) : Promise < ResolvedCompanionState > {
71- let resolvedPath : string ;
89+ function isPackagePathNotExportedError ( error : unknown ) : boolean {
90+ if ( ! error || typeof error !== "object" ) {
91+ return false ;
92+ }
93+
94+ return "code" in error && error . code === "ERR_PACKAGE_PATH_NOT_EXPORTED" ;
95+ }
96+
97+ function isFallthroughResolutionError ( error : unknown ) : boolean {
98+ return isModuleNotFoundError ( error ) || isPackagePathNotExportedError ( error ) ;
99+ }
100+
101+ function normalizeCredential ( value : unknown ) : string {
102+ return typeof value === "string" ? value . trim ( ) : "" ;
103+ }
104+
105+ function getCompanionResolvePaths ( ) : string [ ] {
106+ return getOpencodeRuntimeDirCandidates ( ) . cacheDirs ;
107+ }
72108
109+ function markPackageFoundForExportBlock (
110+ error : unknown ,
111+ context : CompanionResolutionContext ,
112+ ) : void {
113+ if ( isPackagePathNotExportedError ( error ) ) {
114+ context . packageFound = true ;
115+ }
116+ }
117+
118+ function resolveCompanionSpecifier (
119+ specifier : string ,
120+ context : CompanionResolutionContext ,
121+ ) : string {
73122 try {
74- resolvedPath = require . resolve ( COMPANION_IMPORT_SPECIFIER ) ;
123+ return require . resolve ( specifier ) ;
75124 } catch ( error ) {
76- if ( isModuleNotFoundError ( error ) ) {
77- return {
78- presence : {
79- state : "missing" ,
80- importSpecifier : COMPANION_IMPORT_SPECIFIER ,
81- error : COMPANION_MISSING_ERROR ,
82- } ,
83- credentials : {
84- state : "missing" ,
85- error : COMPANION_MISSING_ERROR ,
86- } ,
87- } ;
125+ markPackageFoundForExportBlock ( error , context ) ;
126+ if ( ! isFallthroughResolutionError ( error ) ) {
127+ throw error ;
88128 }
89129
90- return {
91- presence : {
92- state : "invalid" ,
93- importSpecifier : COMPANION_IMPORT_SPECIFIER ,
94- error : COMPANION_INVALID_ERROR ,
95- } ,
96- credentials : {
97- state : "invalid" ,
98- error : COMPANION_INVALID_ERROR ,
99- } ,
100- } ;
130+ try {
131+ return require . resolve ( specifier , { paths : getCompanionResolvePaths ( ) } ) ;
132+ } catch ( resolvePathsError ) {
133+ markPackageFoundForExportBlock ( resolvePathsError , context ) ;
134+ throw resolvePathsError ;
135+ }
136+ }
137+ }
138+
139+ function buildConfiguredState ( params : {
140+ importSpecifier : string ;
141+ resolvedPath : string ;
142+ clientId : string ;
143+ clientSecret : string ;
144+ } ) : ResolvedCompanionState {
145+ return {
146+ presence : {
147+ state : "present" ,
148+ importSpecifier : params . importSpecifier ,
149+ resolvedPath : params . resolvedPath ,
150+ } ,
151+ credentials : {
152+ state : "configured" ,
153+ clientId : params . clientId ,
154+ clientSecret : params . clientSecret ,
155+ resolvedPath : params . resolvedPath ,
156+ } ,
157+ } ;
158+ }
159+
160+ function buildInvalidState ( importSpecifier : string , resolvedPath ?: string ) : ResolvedCompanionState {
161+ return {
162+ presence : {
163+ state : "invalid" ,
164+ importSpecifier,
165+ ...( resolvedPath ? { resolvedPath } : { } ) ,
166+ error : COMPANION_INVALID_ERROR ,
167+ } ,
168+ credentials : {
169+ state : "invalid" ,
170+ ...( resolvedPath ? { resolvedPath } : { } ) ,
171+ error : COMPANION_INVALID_ERROR ,
172+ } ,
173+ } ;
174+ }
175+
176+ function parseSourceCredentials ( content : string ) : { clientId : string ; clientSecret : string } | null {
177+ const clientId =
178+ content . match ( / (?: e x p o r t \s + c o n s t | c o n s t | v a r ) \s + A N T I G R A V I T Y _ C L I E N T _ I D \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / ) ?. [ 1 ] ?. trim ( ) ?? "" ;
179+ const clientSecret =
180+ content . match ( / (?: e x p o r t \s + c o n s t | c o n s t | v a r ) \s + A N T I G R A V I T Y _ C L I E N T _ S E C R E T \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / ) ?. [ 1 ] ?. trim ( ) ?? "" ;
181+
182+ return clientId && clientSecret ? { clientId, clientSecret } : null ;
183+ }
184+
185+ function getPackageCredentialPaths ( packageRoot : string ) : string [ ] {
186+ return COMPANION_DIRECT_CANDIDATE_PATHS . map ( ( parts ) => join ( packageRoot , ...parts ) ) ;
187+ }
188+
189+ function getRuntimeCredentialPaths ( ) : string [ ] {
190+ return getCompanionResolvePaths ( ) . flatMap ( ( cacheDir ) =>
191+ getPackageCredentialPaths ( join ( cacheDir , "node_modules" , COMPANION_PACKAGE_NAME ) ) ,
192+ ) ;
193+ }
194+
195+ function isMissingFileError ( error : unknown ) : boolean {
196+ if ( ! error || typeof error !== "object" ) {
197+ return false ;
198+ }
199+
200+ const code = "code" in error ? error . code : undefined ;
201+ return code === "ENOENT" || code === "ENOTDIR" ;
202+ }
203+
204+ async function tryReadCredentialsPath (
205+ importSpecifier : string ,
206+ resolvedPath : string ,
207+ ) : Promise < ResolvedCompanionState | null > {
208+ let content : string ;
209+ try {
210+ content = await readFile ( resolvedPath , "utf8" ) ;
211+ } catch ( error ) {
212+ return isMissingFileError ( error ) ? null : buildInvalidState ( importSpecifier , resolvedPath ) ;
213+ }
214+
215+ const credentials = parseSourceCredentials ( content ) ;
216+ if ( ! credentials ) {
217+ return buildInvalidState ( importSpecifier , resolvedPath ) ;
218+ }
219+
220+ return buildConfiguredState ( {
221+ importSpecifier,
222+ resolvedPath,
223+ clientId : credentials . clientId ,
224+ clientSecret : credentials . clientSecret ,
225+ } ) ;
226+ }
227+
228+ async function tryResolveJsConstants (
229+ importSpecifier : string ,
230+ context : CompanionResolutionContext ,
231+ ) : Promise < ResolvedCompanionState | null > {
232+ let resolvedPath : string ;
233+ try {
234+ resolvedPath = resolveCompanionSpecifier ( importSpecifier , context ) ;
235+ } catch ( error ) {
236+ if ( isFallthroughResolutionError ( error ) ) {
237+ return null ;
238+ }
239+ return buildInvalidState ( importSpecifier ) ;
101240 }
102241
103242 let companionModule : CompanionModule ;
104243 try {
105244 companionModule = ( await import ( pathToFileURL ( resolvedPath ) . href ) ) as CompanionModule ;
106245 } catch {
107- return {
108- presence : {
109- state : "invalid" ,
110- importSpecifier : COMPANION_IMPORT_SPECIFIER ,
111- resolvedPath,
112- error : COMPANION_INVALID_ERROR ,
113- } ,
114- credentials : {
115- state : "invalid" ,
116- resolvedPath,
117- error : COMPANION_INVALID_ERROR ,
118- } ,
119- } ;
246+ return buildInvalidState ( importSpecifier , resolvedPath ) ;
120247 }
121248
122- const clientId =
123- typeof companionModule . ANTIGRAVITY_CLIENT_ID === "string"
124- ? companionModule . ANTIGRAVITY_CLIENT_ID . trim ( )
125- : "" ;
126- const clientSecret =
127- typeof companionModule . ANTIGRAVITY_CLIENT_SECRET === "string"
128- ? companionModule . ANTIGRAVITY_CLIENT_SECRET . trim ( )
129- : "" ;
130-
249+ const clientId = normalizeCredential ( companionModule . ANTIGRAVITY_CLIENT_ID ) ;
250+ const clientSecret = normalizeCredential ( companionModule . ANTIGRAVITY_CLIENT_SECRET ) ;
131251 if ( ! clientId || ! clientSecret ) {
132- return {
133- presence : {
134- state : "invalid" ,
135- importSpecifier : COMPANION_IMPORT_SPECIFIER ,
136- resolvedPath,
137- error : COMPANION_INVALID_ERROR ,
138- } ,
139- credentials : {
140- state : "invalid" ,
141- resolvedPath,
142- error : COMPANION_INVALID_ERROR ,
143- } ,
144- } ;
252+ return buildInvalidState ( importSpecifier , resolvedPath ) ;
253+ }
254+
255+ return buildConfiguredState ( { importSpecifier, resolvedPath, clientId, clientSecret } ) ;
256+ }
257+
258+ async function tryRuntimeCredentialPaths ( ) : Promise < ResolvedCompanionState | null > {
259+ for ( const candidatePath of getRuntimeCredentialPaths ( ) ) {
260+ const resolved = await tryReadCredentialsPath ( COMPANION_SOURCE_IMPORT_SPECIFIER , candidatePath ) ;
261+ if ( resolved ) {
262+ return resolved ;
263+ }
264+ }
265+
266+ return null ;
267+ }
268+
269+ async function tryResolveSourceConstants (
270+ context : CompanionResolutionContext ,
271+ ) : Promise < ResolvedCompanionState | null > {
272+ let resolvedPath : string ;
273+ try {
274+ resolvedPath = resolveCompanionSpecifier ( COMPANION_SOURCE_IMPORT_SPECIFIER , context ) ;
275+ } catch ( error ) {
276+ return isFallthroughResolutionError ( error )
277+ ? null
278+ : buildInvalidState ( COMPANION_SOURCE_IMPORT_SPECIFIER ) ;
279+ }
280+
281+ return (
282+ ( await tryReadCredentialsPath ( COMPANION_SOURCE_IMPORT_SPECIFIER , resolvedPath ) ) ??
283+ buildInvalidState ( COMPANION_SOURCE_IMPORT_SPECIFIER , resolvedPath )
284+ ) ;
285+ }
286+
287+ async function tryResolvePackageJson (
288+ context : CompanionResolutionContext ,
289+ ) : Promise < ResolvedCompanionState | null > {
290+ let packageJsonPath : string ;
291+ try {
292+ packageJsonPath = resolveCompanionSpecifier ( COMPANION_PACKAGE_JSON_SPECIFIER , context ) ;
293+ } catch ( error ) {
294+ return isFallthroughResolutionError ( error )
295+ ? null
296+ : buildInvalidState ( COMPANION_PACKAGE_JSON_SPECIFIER ) ;
297+ }
298+
299+ const packageRoot = dirname ( packageJsonPath ) ;
300+ for ( const candidatePath of getPackageCredentialPaths ( packageRoot ) ) {
301+ const resolved = await tryReadCredentialsPath ( COMPANION_PACKAGE_JSON_SPECIFIER , candidatePath ) ;
302+ if ( resolved ) {
303+ return resolved ;
304+ }
305+ }
306+
307+ return buildInvalidState ( COMPANION_PACKAGE_JSON_SPECIFIER , packageJsonPath ) ;
308+ }
309+
310+ async function tryResolvePackageEntry (
311+ context : CompanionResolutionContext ,
312+ ) : Promise < ResolvedCompanionState | null > {
313+ let packageEntryPath : string ;
314+ try {
315+ packageEntryPath = resolveCompanionSpecifier ( COMPANION_PACKAGE_NAME , context ) ;
316+ } catch ( error ) {
317+ return isFallthroughResolutionError ( error )
318+ ? null
319+ : buildInvalidState ( COMPANION_PACKAGE_NAME ) ;
320+ }
321+
322+ return (
323+ ( await tryReadCredentialsPath ( COMPANION_PACKAGE_NAME , packageEntryPath ) ) ??
324+ buildInvalidState ( COMPANION_PACKAGE_NAME , packageEntryPath )
325+ ) ;
326+ }
327+
328+ async function resolveCompanionState ( ) : Promise < ResolvedCompanionState > {
329+ const context : CompanionResolutionContext = { packageFound : false } ;
330+
331+ for ( const importSpecifier of COMPANION_JS_IMPORT_SPECIFIERS ) {
332+ const resolved = await tryResolveJsConstants ( importSpecifier , context ) ;
333+ if ( resolved ) {
334+ return resolved ;
335+ }
336+ }
337+
338+ const runtimeResolved = await tryRuntimeCredentialPaths ( ) ;
339+ if ( runtimeResolved ) {
340+ return runtimeResolved ;
341+ }
342+
343+ const sourceResolved = await tryResolveSourceConstants ( context ) ;
344+ if ( sourceResolved ) {
345+ return sourceResolved ;
346+ }
347+
348+ const packageJsonResolved = await tryResolvePackageJson ( context ) ;
349+ if ( packageJsonResolved ) {
350+ return packageJsonResolved ;
351+ }
352+
353+ const packageEntryResolved = await tryResolvePackageEntry ( context ) ;
354+ if ( packageEntryResolved ) {
355+ return packageEntryResolved ;
356+ }
357+
358+ if ( context . packageFound ) {
359+ return buildInvalidState ( COMPANION_PACKAGE_NAME ) ;
145360 }
146361
147362 return {
148363 presence : {
149- state : "present " ,
150- importSpecifier : COMPANION_IMPORT_SPECIFIER ,
151- resolvedPath ,
364+ state : "missing " ,
365+ importSpecifier : COMPANION_SOURCE_IMPORT_SPECIFIER ,
366+ error : COMPANION_MISSING_ERROR ,
152367 } ,
153368 credentials : {
154- state : "configured" ,
155- clientId,
156- clientSecret,
157- resolvedPath,
369+ state : "missing" ,
370+ error : COMPANION_MISSING_ERROR ,
158371 } ,
159372 } ;
160373}
0 commit comments