@wenathlan/devthink 2.0.13 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/accounts.ts +2087 -0
  3. package/agent.ts +2331 -0
  4. package/antigravity.ts +2139 -0
  5. package/apifreeze.ts +4146 -0
  6. package/auth.ts +642 -0
  7. package/background.ts +16837 -0
  8. package/bridge.ts +2080 -0
  9. package/build.ts +54 -0
  10. package/bun.ts +26 -0
  11. package/capture.ts +1614 -0
  12. package/checksecrets.ts +72 -0
  13. package/cjs.ts +9 -0
  14. package/cli.ts +3500 -0
  15. package/commands.ts +1101 -0
  16. package/companion.ts +450 -0
  17. package/compatibility.ts +37 -0
  18. package/config.ts +3137 -0
  19. package/constants.ts +943 -0
  20. package/core.ts +1473 -0
  21. package/crossbrowser.ts +1359 -0
  22. package/dashdone.ts +605 -0
  23. package/data.ts +943 -0
  24. package/database.ts +133 -0
  25. package/debug.ts +850 -0
  26. package/deno.ts +27 -0
  27. package/devthink.ts +707 -0
  28. package/dist/accounts.d.ts +409 -0
  29. package/dist/agent.d.ts +837 -0
  30. package/dist/antigravity.d.ts +380 -0
  31. package/dist/apifreeze.d.ts +113 -0
  32. package/dist/auth.d.ts +224 -0
  33. package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
  34. package/dist/capture.d.ts +595 -0
  35. package/dist/cjs.d.ts +6 -0
  36. package/{cli.js → dist/cli.js} +2 -2
  37. package/dist/commands.d.ts +310 -0
  38. package/dist/compatibility.d.ts +17 -0
  39. package/dist/config.d.ts +1853 -0
  40. package/dist/constants.d.ts +637 -0
  41. package/dist/core.d.ts +361 -0
  42. package/dist/data.d.ts +330 -0
  43. package/dist/database.d.ts +24 -0
  44. package/dist/debug.d.ts +270 -0
  45. package/dist/engine.d.ts +74 -0
  46. package/dist/environments.d.ts +244 -0
  47. package/dist/evidence.d.ts +277 -0
  48. package/dist/export.d.ts +311 -0
  49. package/dist/fingerprint.d.ts +594 -0
  50. package/dist/flow.d.ts +300 -0
  51. package/dist/gates.d.ts +103 -0
  52. package/dist/identity.d.ts +51 -0
  53. package/dist/ids.d.ts +7 -0
  54. package/dist/llm.d.ts +652 -0
  55. package/dist/models.d.ts +1807 -0
  56. package/dist/net.d.ts +325 -0
  57. package/dist/neutral.d.ts +41 -0
  58. package/dist/oauth.d.ts +703 -0
  59. package/dist/page.d.ts +1166 -0
  60. package/dist/perf.d.ts +481 -0
  61. package/dist/plan.d.ts +134 -0
  62. package/dist/project.d.ts +395 -0
  63. package/dist/providers.d.ts +37 -0
  64. package/dist/quota.d.ts +453 -0
  65. package/dist/recovery.d.ts +621 -0
  66. package/dist/request.d.ts +1492 -0
  67. package/dist/run.d.ts +779 -0
  68. package/dist/runtime.d.ts +212 -0
  69. package/dist/search.d.ts +375 -0
  70. package/dist/security.d.ts +534 -0
  71. package/dist/serve.d.ts +136 -0
  72. package/dist/session.d.ts +296 -0
  73. package/dist/storage.d.ts +53 -0
  74. package/dist/streaming.d.ts +1119 -0
  75. package/dist/swarm.d.ts +328 -0
  76. package/dist/tools.d.ts +170 -0
  77. package/dist/types.d.ts +8357 -0
  78. package/dist/utils.d.ts +82 -0
  79. package/dist/version.d.ts +7 -0
  80. package/dist/versionregistry.d.ts +406 -0
  81. package/dist/views.d.ts +579 -0
  82. package/dist/workbenchsession.d.ts +69 -0
  83. package/dist/workflow.d.ts +649 -0
  84. package/docs/readme.md +92 -0
  85. package/docs/schemas/antigravity.json +192 -0
  86. package/engine.ts +1524 -0
  87. package/environments.ts +1005 -0
  88. package/evidence.ts +643 -0
  89. package/export.ts +889 -0
  90. package/fingerprint.ts +1458 -0
  91. package/flow.ts +1190 -0
  92. package/gates.ts +307 -0
  93. package/gateway.ts +34 -0
  94. package/hardening.ts +191 -0
  95. package/headless.ts +369 -0
  96. package/http.ts +2110 -0
  97. package/identity.ts +246 -0
  98. package/ids.ts +26 -0
  99. package/index.ts +369 -0
  100. package/llm.ts +2682 -0
  101. package/mcp.ts +1835 -0
  102. package/{memory.js → memory.ts} +3834 -2310
  103. package/models.ts +3805 -0
  104. package/modes.ts +43 -0
  105. package/net.ts +1214 -0
  106. package/neutral.ts +362 -0
  107. package/node.ts +26 -0
  108. package/oauth.ts +2216 -0
  109. package/offscreen.ts +170 -0
  110. package/pack.ts +956 -0
  111. package/package.json +175 -82
  112. package/page.ts +7824 -0
  113. package/pagebridge.ts +1116 -0
  114. package/perf.ts +957 -0
  115. package/plan.ts +672 -0
  116. package/plugin.ts +76 -0
  117. package/{policy.js → policy.ts} +5073 -4679
  118. package/prisma.config.ts +27 -0
  119. package/progress.ts +1308 -0
  120. package/project.ts +1291 -0
  121. package/protocol.ts +4187 -0
  122. package/providers.ts +246 -0
  123. package/quota.ts +1887 -0
  124. package/recovery.ts +2433 -0
  125. package/request.ts +4482 -0
  126. package/run.ts +2135 -0
  127. package/runtime.ts +526 -0
  128. package/search.ts +1760 -0
  129. package/security.ts +1673 -0
  130. package/serve.ts +419 -0
  131. package/server.ts +5538 -0
  132. package/session.ts +1162 -0
  133. package/storage.ts +182 -0
  134. package/streaming.ts +4101 -0
  135. package/swarm.ts +884 -0
  136. package/sync.ts +56 -0
  137. package/tests/agentcert.test.ts +128 -0
  138. package/tests/agentfleet.test.ts +214 -0
  139. package/tests/agentreview.test.ts +241 -0
  140. package/tests/agentstream.test.ts +335 -0
  141. package/tests/agentwork.test.ts +356 -0
  142. package/tests/apifreeze.test.ts +290 -0
  143. package/tests/apimap.test.ts +112 -0
  144. package/tests/approvalgate.test.ts +175 -0
  145. package/tests/artifactmanifest.test.ts +204 -0
  146. package/tests/attentionfeed.test.ts +163 -0
  147. package/tests/backgroundruns.test.ts +100 -0
  148. package/tests/batchscheduling.test.ts +131 -0
  149. package/tests/blackboard.test.ts +238 -0
  150. package/tests/bridge.test.ts +1088 -0
  151. package/tests/browsercoverage.test.ts +134 -0
  152. package/tests/browserpolyfills.test.ts +124 -0
  153. package/tests/build.mjs +1260 -0
  154. package/tests/clientauth.test.ts +223 -0
  155. package/tests/clitools.test.ts +578 -0
  156. package/tests/confirmgates.test.ts +190 -0
  157. package/tests/containerpack.test.ts +143 -0
  158. package/tests/controlflow.test.ts +2120 -0
  159. package/tests/coordination.test.ts +345 -0
  160. package/tests/copyscreen.test.ts +139 -0
  161. package/tests/core.test.ts +417 -0
  162. package/tests/costcert.test.ts +95 -0
  163. package/tests/crossbrowser.test.ts +115 -0
  164. package/tests/cspaudit.test.ts +79 -0
  165. package/tests/dashdone.test.ts +615 -0
  166. package/tests/datagrid.test.ts +137 -0
  167. package/tests/doccheck.test.ts +107 -0
  168. package/tests/dombatch.test.ts +109 -0
  169. package/tests/emulation.test.ts +282 -0
  170. package/tests/environments.test.ts +259 -0
  171. package/tests/evidenceviews.test.ts +88 -0
  172. package/tests/exporttools.test.ts +185 -0
  173. package/tests/firefoxprep.test.ts +134 -0
  174. package/tests/flowlibrary.test.ts +242 -0
  175. package/tests/flowrun.test.ts +303 -0
  176. package/tests/forensics.test.ts +439 -0
  177. package/tests/gateway.test.ts +939 -0
  178. package/tests/headlesslib.test.ts +171 -0
  179. package/tests/httpstream.test.ts +277 -0
  180. package/tests/immutablelog.test.ts +157 -0
  181. package/tests/inboundguard.test.ts +177 -0
  182. package/tests/lazyload.test.ts +72 -0
  183. package/tests/librarymodes.test.ts +623 -0
  184. package/tests/llm.test.ts +696 -0
  185. package/tests/manifest.test.ts +226 -0
  186. package/tests/maskinputs.test.ts +150 -0
  187. package/tests/matrixverify.test.ts +111 -0
  188. package/tests/mavenpack.test.ts +75 -0
  189. package/tests/mcpserver.test.ts +704 -0
  190. package/tests/memory.test.ts +4155 -0
  191. package/tests/memorycare.test.ts +230 -0
  192. package/tests/migrateplan.test.ts +349 -0
  193. package/tests/minimization.test.ts +414 -0
  194. package/tests/modelroute.test.ts +112 -0
  195. package/tests/multiagent.test.ts +440 -0
  196. package/tests/nativebridge.test.ts +807 -0
  197. package/tests/navigation.test.ts +284 -0
  198. package/tests/nugetpack.test.ts +83 -0
  199. package/tests/orchestration.test.ts +449 -0
  200. package/tests/originpolicy.test.ts +405 -0
  201. package/tests/outputcompare.test.ts +97 -0
  202. package/tests/package.test.ts +91 -0
  203. package/tests/pagebridge.test.ts +5909 -0
  204. package/tests/pentest.test.ts +75 -0
  205. package/tests/perfrecords.test.ts +257 -0
  206. package/tests/permdiff.test.ts +85 -0
  207. package/tests/phishguard.test.ts +128 -0
  208. package/tests/pickerviews.test.ts +155 -0
  209. package/tests/pipelines.test.ts +381 -0
  210. package/tests/planlint.test.ts +240 -0
  211. package/tests/planreview.test.ts +383 -0
  212. package/tests/platformtargets.test.ts +56 -0
  213. package/tests/policy.test.ts +8284 -0
  214. package/tests/portability.test.ts +96 -0
  215. package/tests/progress.test.ts +1036 -0
  216. package/tests/promptlibrary.test.ts +116 -0
  217. package/tests/protocol.test.ts +4793 -0
  218. package/tests/publishingpipeline.test.ts +314 -0
  219. package/tests/quickactions.test.ts +164 -0
  220. package/tests/readiness.test.ts +78 -0
  221. package/tests/recipes.test.ts +163 -0
  222. package/tests/redactshots.test.ts +216 -0
  223. package/tests/registernvidiakeys.ts +51 -0
  224. package/tests/relayserve.test.ts +304 -0
  225. package/tests/releasenotes.test.ts +276 -0
  226. package/tests/resilience.test.ts +467 -0
  227. package/tests/resourceaware.test.ts +187 -0
  228. package/tests/runbudget.test.ts +125 -0
  229. package/tests/runhistory.test.ts +299 -0
  230. package/tests/runreplay.test.ts +113 -0
  231. package/tests/runresume.test.ts +184 -0
  232. package/tests/runstate.test.ts +288 -0
  233. package/tests/runtimeadapters.test.ts +251 -0
  234. package/tests/safariskeleton.test.ts +115 -0
  235. package/tests/sandboxframe.test.ts +196 -0
  236. package/tests/sbom.test.ts +105 -0
  237. package/tests/secretvault.test.ts +188 -0
  238. package/tests/selcache.test.ts +111 -0
  239. package/tests/sessioninterface.test.ts +791 -0
  240. package/tests/sessions.test.ts +591 -0
  241. package/tests/siteprefs.test.ts +228 -0
  242. package/tests/snapshotdelta.test.ts +165 -0
  243. package/tests/statusviews.test.ts +237 -0
  244. package/tests/stepmeter.test.ts +200 -0
  245. package/tests/streamparse.test.ts +163 -0
  246. package/tests/stylepruning.test.ts +90 -0
  247. package/tests/surfaces.test.ts +282 -0
  248. package/tests/sweep.test.ts +78 -0
  249. package/tests/syncbridge.test.ts +164 -0
  250. package/tests/taskqueue.test.ts +206 -0
  251. package/tests/toolcalls.test.ts +336 -0
  252. package/tests/toolcatalog.test.ts +201 -0
  253. package/tests/torture-agent.test.ts +706 -0
  254. package/tests/torture-auth.test.ts +461 -0
  255. package/tests/torture-commands.test.ts +895 -0
  256. package/tests/torture-data.test.ts +923 -0
  257. package/tests/torture-environments.test.ts +285 -0
  258. package/tests/torture-export.test.ts +263 -0
  259. package/tests/torture-http.test.ts +1379 -0
  260. package/tests/torture-llm.test.ts +808 -0
  261. package/tests/torture-memory.test.ts +1084 -0
  262. package/tests/torture-plan.test.ts +829 -0
  263. package/tests/torture-run.test.ts +626 -0
  264. package/tests/torture-session.test.ts +928 -0
  265. package/tests/torture-swarm.test.ts +1245 -0
  266. package/tests/torture-tools.test.ts +964 -0
  267. package/tests/torture-workflow.test.ts +2354 -0
  268. package/tests/tourviews.test.ts +52 -0
  269. package/tests/transparency.test.ts +147 -0
  270. package/tests/trigger.test.ts +941 -0
  271. package/tests/v1sunset.test.ts +428 -0
  272. package/tests/virtlist.test.ts +94 -0
  273. package/tests/vision.test.ts +540 -0
  274. package/tests/vsixpack.test.ts +145 -0
  275. package/tests/webapi.test.ts +523 -0
  276. package/tests/workflow.test.ts +1785 -0
  277. package/tests/workfloweditor.test.ts +759 -0
  278. package/tests/xpipack.test.ts +128 -0
  279. package/tools.ts +734 -0
  280. package/types.ts +9908 -0
  281. package/umd.ts +9 -0
  282. package/utils.ts +249 -0
  283. package/version.ts +8 -0
  284. package/versionregistry.ts +1310 -0
  285. package/views.ts +2153 -0
  286. package/vitest.config.ts +12 -0
  287. package/web/App.tsx +78 -0
  288. package/web/README.md +47 -0
  289. package/web/capacitor.config.ts +46 -0
  290. package/web/const.ts +19 -0
  291. package/web/control.shell.tsx +66 -0
  292. package/web/dashboardpage.ts +554 -0
  293. package/web/db.ts +493 -0
  294. package/web/design.html +541 -0
  295. package/web/gatewayview/Gateway.tsx +345 -0
  296. package/web/gatewayview/config.ts +1042 -0
  297. package/web/gatewayview/definition.ts +174 -0
  298. package/web/icons.ts +15 -0
  299. package/{manifest.json → web/manifest.json} +1 -1
  300. package/web/optionspage.ts +691 -0
  301. package/web/popup.ts +1651 -0
  302. package/web/schema.prisma +176 -0
  303. package/web/sidepanel.ts +7941 -0
  304. package/web/sitemanifest.json +13 -0
  305. package/web/terminal-ui.tsx +622 -0
  306. package/web/transparencypage.ts +541 -0
  307. package/web/vite.config.ts +39 -0
  308. package/web/workspace.ts +23 -0
  309. package/workbenchmemory.ts +104 -0
  310. package/workbenchsession.ts +304 -0
  311. package/workflow.ts +4495 -0
  312. package/bridge.d.ts.map +0 -1
  313. package/bridge.js +0 -1877
  314. package/bun.d.ts.map +0 -1
  315. package/bun.js +0 -52329
  316. package/caps/background.json +0 -712
  317. package/caps/cli.json +0 -360
  318. package/caps/library.json +0 -3611
  319. package/caps/mcp.json +0 -126
  320. package/caps/pagebridge.json +0 -377
  321. package/caps/popup.json +0 -51
  322. package/caps/sidepanel.json +0 -522
  323. package/checksums.txt +0 -142
  324. package/cli.d.ts.map +0 -1
  325. package/companion.d.ts.map +0 -1
  326. package/companion.js +0 -389
  327. package/crossbrowser.d.ts.map +0 -1
  328. package/crossbrowser.js +0 -1191
  329. package/dashdone.d.ts.map +0 -1
  330. package/dashdone.js +0 -368
  331. package/deno.d.ts.map +0 -1
  332. package/deno.js +0 -52325
  333. package/devthink.umd.js +0 -52346
  334. package/fixtures/bun.ts +0 -7
  335. package/fixtures/cjs.js +0 -6
  336. package/fixtures/deno.ts +0 -6
  337. package/fixtures/example-org-pagestate.json +0 -22
  338. package/fixtures/importers/automa-workflow.json +0 -18
  339. package/fixtures/importers/selenium-side.json +0 -20
  340. package/fixtures/importers/tabular-plan.csv +0 -7
  341. package/fixtures/importers/uivision-macro.json +0 -10
  342. package/fixtures/importers/v1-plan.json +0 -13
  343. package/fixtures/mcp-client.mjs +0 -14
  344. package/fixtures/pages/checkout.html +0 -192
  345. package/fixtures/pages/dashboard.html +0 -110
  346. package/fixtures/pages/feed.html +0 -113
  347. package/fixtures/pages/product-grid.html +0 -150
  348. package/fixtures/plans/form-inventory-plan.json +0 -11
  349. package/fixtures/plans/release-notes-plan.json +0 -11
  350. package/fixtures/recipes/agentscompete.json +0 -11
  351. package/fixtures/recipes/agentsforms.json +0 -11
  352. package/fixtures/recipes/agentsmonitor.json +0 -11
  353. package/fixtures/recipes/agentsreview.json +0 -29
  354. package/fixtures/recipes/agentswarm.json +0 -11
  355. package/fixtures/recipes/fillcalendar.json +0 -12
  356. package/fixtures/recipes/fillcheckout.json +0 -13
  357. package/fixtures/recipes/filldependentdropdowns.json +0 -12
  358. package/fixtures/recipes/fillfromcsv.json +0 -13
  359. package/fixtures/recipes/fillloginform.json +0 -12
  360. package/fixtures/recipes/fillmultistepwizard.json +0 -13
  361. package/fixtures/recipes/fillregistration.json +0 -12
  362. package/fixtures/recipes/fillsearchfilters.json +0 -12
  363. package/fixtures/recipes/filltypeahead.json +0 -11
  364. package/fixtures/recipes/monitoralerts.json +0 -12
  365. package/fixtures/recipes/monitoravailability.json +0 -11
  366. package/fixtures/recipes/monitornetworkquiet.json +0 -12
  367. package/fixtures/recipes/monitorpagechanges.json +0 -11
  368. package/fixtures/recipes/monitorpricechange.json +0 -11
  369. package/fixtures/recipes/monitorschedule.json +0 -12
  370. package/fixtures/recipes/scrapedynamicprices.json +0 -13
  371. package/fixtures/recipes/scrapeimagegallery.json +0 -12
  372. package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
  373. package/fixtures/recipes/scrapenestedlists.json +0 -12
  374. package/fixtures/recipes/scrapeproductgrid.json +0 -12
  375. package/fixtures/recipes/scrapereviews.json +0 -12
  376. package/fixtures/recipes/scrapesearchresults.json +0 -12
  377. package/fixtures/recipes/scrapetablepagination.json +0 -12
  378. package/fixtures/recipes/testaccessibility.json +0 -34
  379. package/fixtures/recipes/testconsoleerrors.json +0 -12
  380. package/fixtures/recipes/testdeeplinks.json +0 -12
  381. package/fixtures/recipes/testformvalidation.json +0 -13
  382. package/fixtures/recipes/testlayoutshift.json +0 -12
  383. package/fixtures/recipes/testlinkcheck.json +0 -12
  384. package/fixtures/recipes/testloadtiming.json +0 -12
  385. package/fixtures/recipes/testvisualdiff.json +0 -12
  386. package/fixtures/workflows/changelog-digest-workflow.json +0 -13
  387. package/fixtures/workflows/form-survey-workflow.json +0 -13
  388. package/gallery.json +0 -706
  389. package/hardening.d.ts.map +0 -1
  390. package/hardening.js +0 -188
  391. package/headless.d.ts.map +0 -1
  392. package/headless.js +0 -11522
  393. package/http.d.ts.map +0 -1
  394. package/http.js +0 -2165
  395. package/index.cjs +0 -54682
  396. package/index.d.ts.map +0 -1
  397. package/index.js +0 -52310
  398. package/index.neutral.js +0 -46343
  399. package/mcp.d.ts.map +0 -1
  400. package/mcp.js +0 -20515
  401. package/memory.d.ts.map +0 -1
  402. package/nativehost.template.json +0 -10
  403. package/node.cjs +0 -54686
  404. package/node.d.ts.map +0 -1
  405. package/pack.d.ts.map +0 -1
  406. package/pack.js +0 -805
  407. package/policy.d.ts.map +0 -1
  408. package/progress.d.ts.map +0 -1
  409. package/progress.js +0 -645
  410. package/protocol.d.ts.map +0 -1
  411. package/protocol.js +0 -8334
  412. package/schemas/audit.schema.json +0 -226
  413. package/schemas/capability.schema.json +0 -113
  414. package/schemas/envelope.schema.json +0 -162
  415. package/schemas/memory.schema.json +0 -51
  416. package/schemas/observation.schema.json +0 -113
  417. package/schemas/plan.schema.json +0 -208
  418. package/schemas/progress.schema.json +0 -126
  419. package/schemas/proposal.schema.json +0 -68
  420. package/schemas/session.schema.json +0 -82
  421. package/schemas/tool.schema.json +0 -94
  422. package/server.d.ts.map +0 -1
  423. package/server.js +0 -28877
  424. package/umd-example.html +0 -18
  425. package/umd.d.ts.map +0 -1
  426. /package/{bun.d.ts → dist/bun.d.ts} +0 -0
  427. /package/{cli.d.ts → dist/cli.d.ts} +0 -0
  428. /package/{companion.d.ts → dist/companion.d.ts} +0 -0
  429. /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
  430. /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
  431. /package/{deno.d.ts → dist/deno.d.ts} +0 -0
  432. /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
  433. /package/{headless.d.ts → dist/headless.d.ts} +0 -0
  434. /package/{http.d.ts → dist/http.d.ts} +0 -0
  435. /package/{index.d.ts → dist/index.d.ts} +0 -0
  436. /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
  437. /package/{memory.d.ts → dist/memory.d.ts} +0 -0
  438. /package/{node.d.ts → dist/node.d.ts} +0 -0
  439. /package/{pack.d.ts → dist/pack.d.ts} +0 -0
  440. /package/{policy.d.ts → dist/policy.d.ts} +0 -0
  441. /package/{progress.d.ts → dist/progress.d.ts} +0 -0
  442. /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
  443. /package/{server.d.ts → dist/server.d.ts} +0 -0
  444. /package/{umd.d.ts → dist/umd.d.ts} +0 -0
package/project.ts ADDED
@@ -0,0 +1,1291 @@
1
+ /**
2
+ * @fileoverview project.ts - Cloud Code Assist Project ID discovery (merged v2.1.0 + v1 uniques)
3
+ * @module auth/project
4
+ * @description
5
+ * Merged module. Merge base is project(2).ts (v2.1.0, "bypass total" style Gemini CLI).
6
+ * The v1 lineage (project.ts / project(1).ts) contributed constants and
7
+ * utility ports; its parallel V1-suffixed flow was dead and is gone (see the
8
+ * v2.1.16 note below).
9
+ *
10
+ * v2.1.0 base (kept canonical):
11
+ * - loadCodeAssist(opts) / onboardUser(opts) / fetchAvailableModels(opts) / resolveProjectId(opts)
12
+ * - IdeType ("ANTIGRAVITY" / "IDE_UNSPECIFIED"), defaultMetadata(ideType?)
13
+ * - MODELS_2026_BASE / MODELS_2026 / MODELS_2026_ANTIGRAVITY / MODELS_2026_ALL split
14
+ * - buildDefaultUserAgent(version?), buildAntagravityHeaders, ProjectDiscoveryError
15
+ * - SHA-256 token-hash cache (getCachedProjectId/setCachedProjectId/getCacheStats)
16
+ *
17
+ * Ported v1 uniques (still live):
18
+ * - OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET / GEMINI_CLI_USER_AGENT / GOOGLE_API_NODE_VERSION
19
+ * - X_GOOG_API_CLIENT / CLIENT_METADATA_STRING / CLIENT_METADATA
20
+ * - ENDPOINT_BASES (incl. staging) / EndpointName / EndpointDef / CODE_ASSIST_PATHS
21
+ * - Account / FetchModelsResponse / ResolvedProject types
22
+ * - isLikelyExpiredToken(), getPlatformInfo()
23
+ *
24
+ * v2.1.16 dead-code cleanup: the dead v1 legacy flow (loadCodeAssistV1,
25
+ * onboardUserV1, fetchAvailableModelsV1, resolveProjectIdV1, the legacy
26
+ * projectCache cluster and buildGeminiHeaders) had zero consumers and was
27
+ * deleted; the v2 flow above is the single project-discovery surface.
28
+ *
29
+ * Architecture: library-first, root-first, direct cloudcode-pa (no localhost proxy),
30
+ * only node:* builtins + global fetch (Node >= 18). Timeouts 10s per call,
31
+ * retry on 403/404/408/429/5xx. In-memory caches keyed by truncated token hashes.
32
+ *
33
+ * v2.1.15 Phase A: this module is THE single owner of project discovery.
34
+ * The project-resolution block that used to live in models.ts
35
+ * (ProjectDiscoveryError 4-arg form, resolveProjectIdAsGeminiCliBypass +
36
+ * its option/result types, fetchAvailableModelsDirect) was absorbed here;
37
+ * every model-identification family it consumed is imported from models.ts.
38
+ * Canonical-value decisions for the shared near-copy symbols:
39
+ * - ProjectDiscoveryError: merged 4-arg constructor — the optional fourth
40
+ * argument overrides the status-derived default, so both lineages keep
41
+ * their exact historical retryable semantics.
42
+ * - loadCodeAssist / onboardUser / resolveProjectId / caches: the project.ts
43
+ * v2.1.0-lineage implementations stay canonical (most complete supersets —
44
+ * ideType plumbing with the ANTIGRAVITY→GEMINI retry, tier-id guard, LRO
45
+ * pollOperation, richer extractors); the former models.ts variants (fixed
46
+ * IDE_UNSPECIFIED metadata + jitter, no retry) had zero external consumers
47
+ * and were not carried — documented in the Phase A worklog.
48
+ * - resolveProjectIdAsGeminiCliBypass / fetchAvailableModelsDirect: absorbed
49
+ * from the models.ts block, rewired onto the canonical local primitives
50
+ * (positional-arg headers/fetch helpers) with their flows unchanged.
51
+ *
52
+ * @version 2.1.2
53
+ * @license MIT
54
+ */
55
+
56
+ import * as crypto from "node:crypto";
57
+ import * as os from "node:os";
58
+
59
+ // Deduplicated against the frozen owners: symbols whose local definitions were
60
+ // value-identical to constants.ts / models.ts are imported here and re-exported
61
+ // below, so each has exactly one definition (library dedupe task 2-d1).
62
+ import {
63
+ stripTrailingSlashes,
64
+ ANTIGRAVITY_VERSION_FALLBACK,
65
+ ANTIGRAVITY_USER_AGENT_FALLBACK,
66
+ CLIENT_METADATA,
67
+ CLIENT_METADATA_STRING,
68
+ CLOUDCODE_BASE_URL,
69
+ CLOUDCODE_DAILY_BASE,
70
+ CODE_ASSIST_PATH_MAP,
71
+ FALLBACK_PROJECT_ID,
72
+ FETCH_TIMEOUT_MS,
73
+ GEMINI_CLI_OAUTH_CLIENT_ID as OAUTH_CLIENT_ID,
74
+ GEMINI_CLI_OAUTH_CLIENT_SECRET as OAUTH_CLIENT_SECRET,
75
+ GEMINI_CLI_USER_AGENT_MINIMAL as GEMINI_CLI_USER_AGENT,
76
+ } from "./constants.js";
77
+ // v2.1.15 Phase B: generic utilities (fnv hash, fetch timeout wrapper, retry
78
+ // predicate) come from core.js; identity masquerade (gl-node api-client
79
+ // snapshot, antigravity header builder) comes from fingerprint.js.
80
+ import { fetchWithTimeout, fnv1a32, isRetryable } from "./core.js";
81
+ import { buildAntagravityHeaders, X_GOOG_API_CLIENT_GEMINI_CLI as X_GOOG_API_CLIENT } from "./fingerprint.js";
82
+ import type { AntigravityHeadersOptions } from "./fingerprint.js";
83
+ // v2.1.15 Phase A: project.ts is THE single owner of project discovery. All
84
+ // model-identification families it consumes (endpoint routing orders + the
85
+ // flat catalog lists) are imported from models.ts (layer 1, legal direction);
86
+ // the project-discovery block that used to live in models.ts was absorbed
87
+ // here (see the v2.1.15 Phase A notes in the file header below).
88
+ import {
89
+ CODE_ASSIST_ENDPOINTS,
90
+ ENDPOINT_ORDER,
91
+ ENDPOINT_ORDER_ANTIGRAVITY,
92
+ ENDPOINT_ORDER_GEMINI_CLI,
93
+ MODELS_2026_ALL,
94
+ MODELS_2026_ANTIGRAVITY,
95
+ MODELS_2026_BASE,
96
+ MODELS_2026_LEGACY,
97
+ } from "./models.js";
98
+ import type { Model2026 } from "./models.js";
99
+ import type { Account } from "./accounts.js";
100
+ export type { Account };
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // 01. CORE CONSTANTS (v2.1.0 base) — identical values owned by the frozen
104
+ // constants.ts / models.ts modules; re-exported for surface compatibility
105
+ // ---------------------------------------------------------------------------
106
+
107
+ export {
108
+ ENDPOINT_ORDER,
109
+ FALLBACK_PROJECT_ID,
110
+ ANTIGRAVITY_VERSION_FALLBACK,
111
+ ANTIGRAVITY_USER_AGENT_FALLBACK,
112
+ CODE_ASSIST_ENDPOINTS,
113
+ FETCH_TIMEOUT_MS,
114
+ };
115
+
116
+ /** Builds the dynamic Antigravity User-Agent: antigravity/{ver} {os}/{arch}. */
117
+ function buildDefaultUserAgent(version?: string): string {
118
+ try {
119
+ const ver = version && /^\d+\.\d+\.\d+/.test(version) ? version : ANTIGRAVITY_VERSION_FALLBACK;
120
+ const plat = os.platform();
121
+ const cpu = os.arch();
122
+ const mappedOs = plat === "darwin" ? "darwin" : plat === "win32" ? "win32" : "linux";
123
+ const mappedArch = cpu === "arm64" ? "arm64" : "x64";
124
+ return `antigravity/${ver} ${mappedOs}/${mappedArch}`;
125
+ } catch {
126
+ return ANTIGRAVITY_USER_AGENT_FALLBACK;
127
+ }
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // 02. BYPASS IDENTIFICATION CONSTANTS (ported from v1 lineage)
132
+ // ---------------------------------------------------------------------------
133
+
134
+ // v2.1.15 Phase C: OAUTH_CLIENT_ID (gemini-cli pair) moved to constants.js
135
+ // as GEMINI_CLI_OAUTH_CLIENT_ID — imported from the owner.
136
+
137
+ // local variant: diverges from constants (static "GeminiCLI/0.57.0" vs the dynamic GEMINI_CLI_USER_AGENT_FALLBACK "gemini-cli/0.57.0 os/arch"); canonical static source for accounts.ts
138
+
139
+ export const GOOGLE_API_NODE_VERSION = "22.19.0" as const;
140
+
141
+ // local variant: diverges from constants (static "gl-node/22.19.0" vs the dynamic "antigravity/<ver> gl-node/<node> gax/..."); canonical static source for accounts.ts/quota.ts
142
+
143
+ // local variant: diverges from constants (structured object vs the dynamic buildClientMetadata() string)
144
+
145
+ // v2.1.16 dead-code cleanup: REQUEST_TIMEOUT_MS (10s, used only by the deleted
146
+ // v1 legacy flow below) was deleted — FETCH_TIMEOUT_MS from constants.js is
147
+ // the live timeout of the v2 flow.
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // 03. LEGACY ENDPOINT TABLE (ported from v1 lineage, includes staging host)
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * v1 ordered endpoint chain. PROD/DAILY derive from the constants.js owner
155
+ * values; SANDBOX maps to the staging host and PREPROD is an extra hardening
156
+ * entry observed in telemetry — both genuinely distinct values with no
157
+ * constants.js equivalent, kept local (v2.1.16 dedup note).
158
+ */
159
+ export const ENDPOINT_BASES = [
160
+ {
161
+ name: "PROD" as const,
162
+ base: CLOUDCODE_BASE_URL,
163
+ },
164
+ {
165
+ name: "DAILY" as const,
166
+ base: CLOUDCODE_DAILY_BASE,
167
+ },
168
+ {
169
+ name: "SANDBOX" as const,
170
+ base: "https://staging-cloudcode-pa.googleapis.com",
171
+ },
172
+ // Extra hardening - preprod observed in telemetry
173
+ {
174
+ name: "PREPROD" as const,
175
+ base: "https://preprod-cloudcode-pa.googleapis.com",
176
+ },
177
+ ] as const;
178
+
179
+ export type EndpointName = (typeof ENDPOINT_BASES)[number]["name"];
180
+
181
+ export interface EndpointDef {
182
+ name: EndpointName;
183
+ base: string;
184
+ }
185
+
186
+ /**
187
+ * v1 method-path table. v2.1.16 dedup: every path derives from the
188
+ * constants.js CODE_ASSIST_PATH_MAP owner (identical values).
189
+ */
190
+ export const CODE_ASSIST_PATHS = {
191
+ LOAD: CODE_ASSIST_PATH_MAP.LOAD_CODE_ASSIST,
192
+ ONBOARD: CODE_ASSIST_PATH_MAP.ONBOARD_USER,
193
+ FETCH_MODELS: CODE_ASSIST_PATH_MAP.FETCH_AVAILABLE_MODELS,
194
+ } as const;
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // 04. MODELS 2026 - BASE / ANTIGRAVITY / ALL split (v2.1.0) + LEGACY list (v1)
198
+ // v2.1.15 Phase A: every list below is now imported from models.ts (THE owner
199
+ // of model identification) and re-exported under this module's historical
200
+ // names; the values are byte-identical to the former local copies.
201
+ // ---------------------------------------------------------------------------
202
+
203
+ /**
204
+ * Canonical model list up to 2026-08-25. Best-effort validated via
205
+ * fetchAvailableModels without failing when rollout has not reached
206
+ * DAILY/SANDBOX yet.
207
+ */
208
+ export { MODELS_2026_BASE, MODELS_2026_ANTIGRAVITY, MODELS_2026_ALL, MODELS_2026_LEGACY };
209
+
210
+ /**
211
+ * Historical project-module alias of {@link MODELS_2026_BASE} (the former
212
+ * local `MODELS_2026 = MODELS_2026_BASE` binding).
213
+ */
214
+ export { MODELS_2026_BASE as MODELS_2026 };
215
+
216
+ /** Union type of the v1 static fallback list ids. */
217
+ export type { Model2026 };
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // 05. TYPES - CLOUD CODE CONTRACTS
221
+ // ---------------------------------------------------------------------------
222
+
223
+ export type IdeType = "ANTIGRAVITY" | "IDE_UNSPECIFIED";
224
+
225
+ export interface CodeAssistMetadata {
226
+ ideType: IdeType;
227
+ platform: "PLATFORM_UNSPECIFIED";
228
+ pluginType: "GEMINI";
229
+ }
230
+
231
+ function defaultMetadata(ideType: IdeType = "ANTIGRAVITY"): CodeAssistMetadata {
232
+ return {
233
+ ideType,
234
+ platform: "PLATFORM_UNSPECIFIED",
235
+ pluginType: "GEMINI",
236
+ };
237
+ }
238
+
239
+ export interface LoadCodeAssistResponse {
240
+ cloudaicompanionProject?: unknown;
241
+ cloudaicompanionProjectId?: unknown;
242
+ projectId?: unknown;
243
+ currentTier?: unknown;
244
+ [k: string]: unknown;
245
+ }
246
+
247
+ export interface OnboardUserResponse {
248
+ done?: boolean;
249
+ name?: string;
250
+ response?: { cloudaicompanionProject?: unknown; projectId?: unknown };
251
+ cloudaicompanionProject?: unknown;
252
+ [k: string]: unknown;
253
+ }
254
+
255
+ export interface AvailableModel {
256
+ name: string;
257
+ displayName?: string;
258
+ supportedMethods?: string[];
259
+ }
260
+
261
+ export interface FetchAvailableModelsResponse {
262
+ models?: AvailableModel[];
263
+ availableModels?: AvailableModel[];
264
+ [k: string]: unknown;
265
+ }
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // 06. LEGACY TYPES (ported from v1 lineage)
269
+ // ---------------------------------------------------------------------------
270
+
271
+ /** v1 Account shape with mutable metadata persisted by callers. */
272
+ // v2.1.15 Phase C: the Account interface moved to accounts.js (account
273
+ // store owner) — imported type-only (erased at runtime).
274
+
275
+ // v2.1.16 dead-code cleanup: LoadCodeAssistResponseV1, OnboardUserResponseV1
276
+ // and LegacyProjectCacheEntry (used only by the deleted v1 legacy flow below)
277
+ // were deleted; the response types of the live v2 flow are above.
278
+
279
+ export interface FetchModelsResponse {
280
+ models?: Array<string | { name: string; displayName?: string; id?: string }>;
281
+ availableModels?: Array<string | { name: string }>;
282
+ // Fallbacks
283
+ modelList?: string[];
284
+ _raw?: unknown;
285
+ }
286
+
287
+ export interface ResolvedProject {
288
+ projectId: string;
289
+ source: EndpointName | "cache" | "metadata" | "fallback";
290
+ fromCache: boolean;
291
+ endpointBase?: string;
292
+ models?: string[];
293
+ }
294
+
295
+ // ---------------------------------------------------------------------------
296
+ // 07. IN-MEMORY CACHES
297
+ // ---------------------------------------------------------------------------
298
+
299
+ interface CacheEntry {
300
+ projectId: string;
301
+ timestamp: number;
302
+ email?: string | undefined;
303
+ }
304
+
305
+ const CACHE_TTL_MS = 60 * 60 * 1000; // 1h
306
+ const projectIdCache = new Map<string, CacheEntry>();
307
+
308
+ // local variant: diverges from core (the float multiply step rounds above 2^51 while core uses Math.imul, producing different hash values for the same input)
309
+
310
+ function hashToken(token: string): string {
311
+ try {
312
+ return crypto.createHash("sha256").update(token).digest("hex").slice(0, 32);
313
+ } catch {
314
+ return fnv1a32(token).toString(16);
315
+ }
316
+ }
317
+
318
+ // local variant: diverges from models (operates on the module-private cache exposed via projectIdCacheForTesting; unpadded fnv fallback key)
319
+ export function getCachedProjectId(accessToken: string): string | null {
320
+ const key = hashToken(accessToken);
321
+ const entry = projectIdCache.get(key);
322
+ if (!entry) return null;
323
+ if (Date.now() - entry.timestamp > CACHE_TTL_MS) {
324
+ projectIdCache.delete(key);
325
+ return null;
326
+ }
327
+ return entry.projectId;
328
+ }
329
+
330
+ // local variant: diverges from models (writes the module-private cache exposed via projectIdCacheForTesting)
331
+ export function setCachedProjectId(accessToken: string, projectId: string, email?: string): void {
332
+ const key = hashToken(accessToken);
333
+ projectIdCache.set(key, { projectId, timestamp: Date.now(), email });
334
+ }
335
+
336
+ export function clearProjectIdCache(): void {
337
+ projectIdCache.clear();
338
+ }
339
+
340
+ export function getCacheStats(): { size: number; entries: Array<{ email?: string | undefined; ageMs: number }> } {
341
+ const now = Date.now();
342
+ return {
343
+ size: projectIdCache.size,
344
+ entries: Array.from(projectIdCache.values()).map((e) => ({
345
+ email: e.email,
346
+ ageMs: now - e.timestamp,
347
+ })),
348
+ };
349
+ }
350
+
351
+ // v2.1.16 dead-code cleanup: the legacy v1 projectCache cluster —
352
+ // projectCache, getCacheKey, clearProjectCache, getProjectCacheSnapshot,
353
+ // pruneExpiredCache, isCacheValid and PROJECT_CACHE_TTL_MS — was used only by
354
+ // the deleted v1 legacy flow (resolveProjectIdV1) and had zero external
355
+ // consumers; it was deleted. The live v2 cache above (projectIdCache +
356
+ // getCachedProjectId/setCachedProjectId/clearProjectIdCache/getCacheStats)
357
+ // is untouched.
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // 08. HELPERS - TIMEOUT, RETRY, HEADERS
361
+ // ---------------------------------------------------------------------------
362
+
363
+ // v2.1.15 Phase B: AntigravityHeadersOptions moved to fingerprint.js (identity owner).
364
+
365
+ // local variant: diverges from models (opts-object signature; no Accept header; no forbidden-header stripping)
366
+
367
+ /**
368
+ * Error thrown by every project-discovery call. v2.1.15 Phase A merge of the
369
+ * project.ts lineage (3-arg constructor, retryable derived from the status
370
+ * via isRetryable) and the models.ts lineage (4-arg constructor, explicit
371
+ * retryable): the optional fourth argument overrides the status-derived
372
+ * default, so both historical call styles keep their exact semantics.
373
+ */
374
+ export class ProjectDiscoveryError extends Error {
375
+ public status?: number | undefined;
376
+ public endpoint?: string | undefined;
377
+ public retryable: boolean;
378
+ constructor(message: string, status?: number, endpoint?: string, retryable?: boolean) {
379
+ super(message);
380
+ this.name = "ProjectDiscoveryError";
381
+ this.status = status;
382
+ this.endpoint = endpoint;
383
+ this.retryable = retryable ?? (status !== undefined ? isRetryable(status) : true);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Fetch with 10s timeout through AbortController. Merges an external signal
389
+ * so caller aborts propagate. Throws ProjectDiscoveryError (status 408) on
390
+ * timeout so the cascade can retry.
391
+ */
392
+ // local variant: diverges from models (3-arg signature with 10s default; wraps AbortError into ProjectDiscoveryError 408)
393
+
394
+ // v2.1.16 dead-code cleanup: buildGeminiHeaders (v1 masquerade-header
395
+ // builder whose only callers were the deleted v1 legacy flow; zero external
396
+ // consumers) was deleted — the live v2 flow builds headers via
397
+ // fingerprint.js buildAntagravityHeaders.
398
+
399
+ /**
400
+ * Local heuristic only: reports whether a token looks expired
401
+ * (does not replace real validation via tokeninfo).
402
+ */
403
+ export function isLikelyExpiredToken(token: string): boolean {
404
+ if (!token) return true;
405
+ return token.length < 20;
406
+ }
407
+
408
+ /** Platform tuple helper for logging/debug - uses only node:os. */
409
+ export function getPlatformInfo(): { platform: string; arch: string; release: string; hostname: string } {
410
+ return {
411
+ platform: os.platform(),
412
+ arch: os.arch(),
413
+ release: os.release(),
414
+ hostname: os.hostname(),
415
+ };
416
+ }
417
+
418
+ // ---------------------------------------------------------------------------
419
+ // 09. PAYLOAD EXTRACTION
420
+ // ---------------------------------------------------------------------------
421
+
422
+ /** v2 extractor: normalizes strings, resource names and nested objects. */
423
+ function extractProjectId(raw: unknown): string | null {
424
+ if (!raw) return null;
425
+ if (typeof raw === "string") {
426
+ const s = raw.trim();
427
+ if (!s) return null;
428
+ if (s.includes("projects/")) {
429
+ const parts = s.split("/");
430
+ const idx = parts.indexOf("projects");
431
+ const id = parts[idx + 1];
432
+ return id || s.split("/").pop() || s;
433
+ }
434
+ return s;
435
+ }
436
+ if (typeof raw === "object") {
437
+ const obj = raw as any;
438
+ if (typeof obj.id === "string" && obj.id) return extractProjectId(obj.id);
439
+ if (typeof obj.name === "string" && obj.name) {
440
+ const n = obj.name as string;
441
+ return n.includes("/") ? (n.split("/").pop() ?? n) : n;
442
+ }
443
+ if (typeof obj.projectId === "string") return extractProjectId(obj.projectId);
444
+ if (typeof obj.project_id === "string") return extractProjectId(obj.project_id);
445
+ }
446
+ return null;
447
+ }
448
+
449
+ // v2.1.16 dead-code cleanup: extractProjectIdV1 (the v1 extractor whose only
450
+ // callers were the deleted v1 legacy flow) was deleted; the v2 extractor
451
+ // above is the canonical payload normalizer.
452
+
453
+ // ---------------------------------------------------------------------------
454
+ // 10. loadCodeAssist / onboardUser / pollOperation (v2 canonical)
455
+ // ---------------------------------------------------------------------------
456
+
457
+ // local variant: diverges from models (extra ideType option)
458
+ export interface LoadCodeAssistOptions {
459
+ accessToken: string;
460
+ endpoint: string;
461
+ projectId?: string | undefined;
462
+ userAgent?: string | undefined;
463
+ ideType?: IdeType;
464
+ }
465
+
466
+ /**
467
+ * loadCodeAssist implemented exactly like Gemini CLI:
468
+ * - POST {endpoint}/v1internal:loadCodeAssist
469
+ * - Header: Authorization Bearer + User-Agent antigravity/{ver} {os}/{arch}
470
+ * - Body: {metadata {ideType ANTIGRAVITY|GEMINI, platform PLATFORM_UNSPECIFIED, pluginType GEMINI}}
471
+ * - No project ID required: when projectId is absent only metadata is sent (bypass)
472
+ * - When provided and valid, sends cloudaicompanionProject for validation (cache compat)
473
+ * - On 403/404 with ideType ANTIGRAVITY, retries automatically as GEMINI
474
+ * (bypass observed in pi-antigravity-auth)
475
+ * - Returns the projectId string or null indicating onboarding is required
476
+ */
477
+ // local variant: diverges from models (ANTIGRAVITY->GEMINI retry bypass, extractProjectId normalization, tier-id guard, no jitter)
478
+ export async function loadCodeAssist(opts: LoadCodeAssistOptions): Promise<string | null> {
479
+ const { accessToken, endpoint, projectId, userAgent, ideType = "ANTIGRAVITY" } = opts;
480
+ const base = stripTrailingSlashes(endpoint);
481
+ const url = `${base}${CODE_ASSIST_PATHS.LOAD}`;
482
+ const headers = buildAntagravityHeaders(accessToken, { userAgent });
483
+
484
+ const attemptLoad = async (currentIde: IdeType): Promise<string | null> => {
485
+ const body: Record<string, unknown> = {
486
+ metadata: defaultMetadata(currentIde),
487
+ };
488
+ if (projectId && projectId !== FALLBACK_PROJECT_ID) {
489
+ body.cloudaicompanionProject = projectId;
490
+ }
491
+
492
+ let res: Response;
493
+ try {
494
+ res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, FETCH_TIMEOUT_MS);
495
+ } catch (e: any) {
496
+ throw new ProjectDiscoveryError(
497
+ `loadCodeAssist network failed at ${endpoint} ide=${currentIde}: ${e.message}`,
498
+ e.status ?? 0,
499
+ endpoint,
500
+ );
501
+ }
502
+
503
+ if (!res.ok) {
504
+ const text = await res.text().catch(() => "");
505
+ throw new ProjectDiscoveryError(
506
+ `loadCodeAssist failed ${res.status} at ${endpoint} ide=${currentIde}: ${text.slice(0, 600)}`,
507
+ res.status,
508
+ endpoint,
509
+ );
510
+ }
511
+
512
+ let data: LoadCodeAssistResponse;
513
+ try {
514
+ data = (await res.json()) as LoadCodeAssistResponse;
515
+ } catch {
516
+ throw new ProjectDiscoveryError(`loadCodeAssist invalid JSON at ${endpoint}`, 500, endpoint);
517
+ }
518
+
519
+ const raw =
520
+ (data as any).cloudaicompanionProject ??
521
+ (data as any).cloudaicompanionProjectId ??
522
+ (data as any).projectId ??
523
+ (data as any).currentTier?.id;
524
+
525
+ if (!raw) return null;
526
+
527
+ // Guard: currentTier.id values ("free-tier"/"standard-tier") are TIER ids,
528
+ // not project ids — a body without cloudaicompanionProject means the
529
+ // account still needs onboarding (null = onboarding required).
530
+ if (typeof raw === "string" && raw.endsWith("-tier")) return null;
531
+
532
+ return extractProjectId(raw);
533
+ };
534
+
535
+ try {
536
+ return await attemptLoad(ideType);
537
+ } catch (err: any) {
538
+ if (err instanceof ProjectDiscoveryError && isRetryable(err.status ?? 0) && ideType === "ANTIGRAVITY") {
539
+ // GEMINI retry bypass - what Gemini CLI does when Antigravity is blocked
540
+ try {
541
+ return await attemptLoad("IDE_UNSPECIFIED");
542
+ } catch (retryErr: any) {
543
+ if (retryErr instanceof ProjectDiscoveryError) throw retryErr;
544
+ throw err;
545
+ }
546
+ }
547
+ throw err;
548
+ }
549
+ }
550
+
551
+ // local variant: diverges from models (tierId union + ideType option)
552
+ export interface OnboardUserOptions {
553
+ accessToken: string;
554
+ endpoint: string;
555
+ tierId?: "free-tier" | "standard-tier" | "FREE" | "TRIAL" | "STANDARD";
556
+ userAgent?: string | undefined;
557
+ ideType?: IdeType;
558
+ }
559
+
560
+ /** onboardUser - FREE tier with LRO pollOperation backoff 500ms-8s, 5 attempts. */
561
+ // local variant: diverges from models (free-tier default, LRO pollOperation backoff, 409 handling, ideType option)
562
+ export async function onboardUser(opts: OnboardUserOptions): Promise<string | null> {
563
+ const { accessToken, endpoint, tierId = "free-tier", userAgent, ideType = "ANTIGRAVITY" } = opts;
564
+ const base = stripTrailingSlashes(endpoint);
565
+ const url = `${base}${CODE_ASSIST_PATHS.ONBOARD}`;
566
+ const headers = buildAntagravityHeaders(accessToken, { userAgent });
567
+
568
+ const body = {
569
+ tierId,
570
+ metadata: defaultMetadata(ideType),
571
+ };
572
+
573
+ let res: Response;
574
+ try {
575
+ res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, FETCH_TIMEOUT_MS);
576
+ } catch (e: any) {
577
+ throw new ProjectDiscoveryError(`onboardUser network failed at ${endpoint}: ${e.message}`, e.status ?? 0, endpoint);
578
+ }
579
+
580
+ if (!res.ok) {
581
+ const text = await res.text().catch(() => "");
582
+ // Already-onboarded accounts may return 409 - treat as "run load again"
583
+ if (res.status === 409) return null;
584
+ throw new ProjectDiscoveryError(
585
+ `onboardUser failed ${res.status} at ${endpoint}: ${text.slice(0, 600)}`,
586
+ res.status,
587
+ endpoint,
588
+ );
589
+ }
590
+
591
+ let data: OnboardUserResponse;
592
+ try {
593
+ data = (await res.json()) as OnboardUserResponse;
594
+ } catch {
595
+ throw new ProjectDiscoveryError(`onboardUser invalid JSON at ${endpoint}`, 500, endpoint);
596
+ }
597
+
598
+ const direct =
599
+ (data as any).cloudaicompanionProject ??
600
+ (data as any).response?.cloudaicompanionProject ??
601
+ (data as any).response?.projectId;
602
+
603
+ if (direct) {
604
+ const extracted = extractProjectId(direct);
605
+ if (extracted) return extracted;
606
+ }
607
+
608
+ // LRO: a fresh onboard returns {name} without done — poll until the
609
+ // operation completes (verified live: first call enqueues the operation,
610
+ // immediate re-calls return done:true with cloudaicompanionProject).
611
+ if (data.name && data.done !== true) {
612
+ const polled = await pollOperation({
613
+ accessToken,
614
+ endpoint,
615
+ operationName: data.name,
616
+ userAgent,
617
+ maxAttempts: 5,
618
+ });
619
+ if (polled) return polled;
620
+ }
621
+
622
+ if (data.done && data.response) {
623
+ const proj = (data.response as any).cloudaicompanionProject ?? (data.response as any).projectId;
624
+ const extracted = extractProjectId(proj);
625
+ if (extracted) return extracted;
626
+ }
627
+
628
+ // Pattern observed in gemini-cli: empty onboard reply, next load succeeds
629
+ return null;
630
+ }
631
+
632
+ interface PollOperationOptions {
633
+ accessToken: string;
634
+ endpoint: string;
635
+ operationName: string;
636
+ userAgent?: string | undefined;
637
+ maxAttempts?: number;
638
+ }
639
+
640
+ /**
641
+ * Replicates Gemini CLI LRO polling with 500ms-8s backoff, 5 attempts,
642
+ * tolerant to several operationName formats.
643
+ */
644
+ async function pollOperation(opts: PollOperationOptions): Promise<string | null> {
645
+ const { accessToken, endpoint, operationName, userAgent, maxAttempts = 5 } = opts;
646
+ const base = stripTrailingSlashes(endpoint);
647
+ const headers = buildAntagravityHeaders(accessToken, { userAgent });
648
+
649
+ // Normalize operationName shapes seen in the wild:
650
+ // - "v1internal/operations/xxx"
651
+ // - "operations/xxx"
652
+ // - bare "xxx"
653
+ const buildOpUrl = (name: string): string => {
654
+ if (name.includes("/operations/")) {
655
+ if (name.startsWith("v1internal/")) return `${base}/${name}`;
656
+ return `${base}/v1internal/${name}`;
657
+ }
658
+ if (name.startsWith("operations/")) return `${base}/v1internal/${name}`;
659
+ return `${base}/v1internal/operations/${name}`;
660
+ };
661
+
662
+ const initialUrl = buildOpUrl(operationName);
663
+
664
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
665
+ if (attempt > 0) {
666
+ const delayMs = Math.min(500 * Math.pow(2, attempt - 1), 8000);
667
+ await new Promise((r) => setTimeout(r, delayMs));
668
+ }
669
+ try {
670
+ const res = await fetchWithTimeout(
671
+ attempt === 0 ? initialUrl : buildOpUrl(operationName),
672
+ { method: "GET", headers },
673
+ FETCH_TIMEOUT_MS,
674
+ );
675
+ if (!res.ok) continue;
676
+ const data = (await res.json()) as OnboardUserResponse;
677
+ if (data.done) {
678
+ const proj =
679
+ (data as any).response?.cloudaicompanionProject ??
680
+ (data as any).response?.projectId ??
681
+ (data as any).cloudaicompanionProject;
682
+ const extracted = extractProjectId(proj);
683
+ if (extracted) return extracted;
684
+ return null;
685
+ }
686
+ } catch {
687
+ continue;
688
+ }
689
+ }
690
+ return null;
691
+ }
692
+
693
+ // ---------------------------------------------------------------------------
694
+ // 11. fetchAvailableModels (v2 canonical)
695
+ // ---------------------------------------------------------------------------
696
+
697
+ export interface FetchModelsOptions {
698
+ accessToken: string;
699
+ endpoint: string;
700
+ projectId: string;
701
+ userAgent?: string;
702
+ }
703
+
704
+ export async function fetchAvailableModels(opts: FetchModelsOptions): Promise<AvailableModel[]> {
705
+ const { accessToken, endpoint, projectId, userAgent } = opts;
706
+ if (!projectId) throw new ProjectDiscoveryError("fetchAvailableModels requires projectId", 400, endpoint);
707
+
708
+ const url = `${stripTrailingSlashes(endpoint)}${CODE_ASSIST_PATHS.FETCH_MODELS}`;
709
+ const headers = buildAntagravityHeaders(accessToken, { userAgent });
710
+ // 2026-08 schema: the endpoint rejects the legacy {cloudaicompanionProject,
711
+ // metadata} body with 400 "Unknown name ... Cannot find field" — the models
712
+ // list is now resolved server-side from the bearer token. Send an empty body.
713
+ const body: Record<string, unknown> = {};
714
+
715
+ let res: Response;
716
+ try {
717
+ res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, FETCH_TIMEOUT_MS);
718
+ } catch (e: any) {
719
+ throw new ProjectDiscoveryError(
720
+ `fetchAvailableModels network failed at ${endpoint}: ${e.message}`,
721
+ e.status ?? 0,
722
+ endpoint,
723
+ );
724
+ }
725
+
726
+ if (!res.ok) {
727
+ const text = await res.text().catch(() => "");
728
+ throw new ProjectDiscoveryError(
729
+ `fetchAvailableModels failed ${res.status} at ${endpoint}: ${text.slice(0, 500)}`,
730
+ res.status,
731
+ endpoint,
732
+ );
733
+ }
734
+
735
+ let data: FetchAvailableModelsResponse;
736
+ try {
737
+ data = (await res.json()) as FetchAvailableModelsResponse;
738
+ } catch {
739
+ throw new ProjectDiscoveryError(`fetchAvailableModels invalid JSON at ${endpoint}`, 500, endpoint);
740
+ }
741
+
742
+ const rawModels = (data as any).models ?? (data as any).availableModels ?? [];
743
+ const models: AvailableModel[] = Array.isArray(rawModels) ? rawModels : [];
744
+ return models;
745
+ }
746
+
747
+ // ---------------------------------------------------------------------------
748
+ // 12. LEGACY v1 FLOW — DELETED (v2.1.16 dead-code cleanup)
749
+ // ---------------------------------------------------------------------------
750
+ // v2.1.16 dead-code cleanup: the dead v1 legacy flow (loadCodeAssistV1,
751
+ // onboardUserV1, fetchAvailableModelsV1, resolveProjectIdV1 and the private
752
+ // buildUrl helper) had zero external consumers and zero internal callers
753
+ // outside itself and was deleted (~455 lines). The live v2 flow above
754
+ // (loadCodeAssist / onboardUser / fetchAvailableModels / resolveProjectId +
755
+ // projectIdCache) is untouched.
756
+
757
+ // ---------------------------------------------------------------------------
758
+ // 13. resolveProjectId ORCHESTRATOR (v2 canonical)
759
+ // ---------------------------------------------------------------------------
760
+
761
+ export interface AccountMetadataSaver {
762
+ (projectId: string): Promise<void> | void;
763
+ }
764
+
765
+ export interface ResolveProjectIdOptions {
766
+ accessToken: string;
767
+ cachedProjectId?: string | undefined;
768
+ email?: string | undefined;
769
+ metadataSaver?: AccountMetadataSaver | undefined;
770
+ userAgent?: string | undefined;
771
+ endpoints?: readonly string[];
772
+ includeAutopush?: boolean;
773
+ /** When true (default), tries the discovered project before the blinded fallback */
774
+ bypass?: boolean;
775
+ /** Preferred ideType - ANTIGRAVITY first with automatic GEMINI fallback */
776
+ ideType?: IdeType;
777
+ }
778
+
779
+ export interface ResolveResult {
780
+ projectId: string;
781
+ fromCache: boolean;
782
+ endpointUsed?: string | undefined;
783
+ isFallback: boolean;
784
+ modelsChecked?: number | undefined;
785
+ ideTypeUsed?: IdeType;
786
+ }
787
+
788
+ /**
789
+ * Resolves the project id with full Gemini CLI-style bypass.
790
+ * Observer flow:
791
+ * 1. In-memory Map cache (token hash) - avoids multi-account round-trips
792
+ * 2. When cachedProjectId is persisted on disk, validates it via loadCodeAssist
793
+ * on each endpoint (compat with AccountManager V3 ~/.config/opencode/
794
+ * antigravity-accounts.json chmod 600)
795
+ * 3. Pure bypass: loadCodeAssist WITHOUT projectId -> {metadata {ANTIGRAVITY|GEMINI,
796
+ * PLATFORM_UNSPECIFIED, GEMINI}} -> returns discovered cloudaicompanionProject
797
+ * without requiring a user project
798
+ * 4. When load returns null or 403/404 -> onboardUser {tierId FREE, metadata} ->
799
+ * LRO pollOperation backoff 500ms-8s 5 attempts -> load again
800
+ * 5. Best-effort validation via fetchAvailableModels (does not fail on 403)
801
+ * 6. Saves cache + metadataSaver for persistence
802
+ * 7. If every endpoint fails with retryable errors, falls back to
803
+ * rising-fact-p41fc with a warning; bypass prefers the discovered project.
804
+ */
805
+ export async function resolveProjectId(opts: ResolveProjectIdOptions): Promise<ResolveResult> {
806
+ const {
807
+ accessToken,
808
+ cachedProjectId,
809
+ email,
810
+ metadataSaver,
811
+ userAgent = buildDefaultUserAgent(),
812
+ endpoints = ENDPOINT_ORDER,
813
+ includeAutopush = false,
814
+ bypass = true,
815
+ ideType = "ANTIGRAVITY",
816
+ } = opts;
817
+
818
+ if (!accessToken) throw new ProjectDiscoveryError("resolveProjectId requires accessToken");
819
+
820
+ // 1. Fast Map cache
821
+ const memCached = getCachedProjectId(accessToken);
822
+ if (memCached) {
823
+ return {
824
+ projectId: memCached,
825
+ fromCache: true,
826
+ isFallback: memCached === FALLBACK_PROJECT_ID,
827
+ ideTypeUsed: ideType,
828
+ };
829
+ }
830
+
831
+ const allEndpoints = includeAutopush ? [...endpoints, CODE_ASSIST_ENDPOINTS.AUTOPUSH] : endpoints;
832
+
833
+ let lastError: ProjectDiscoveryError | null = null;
834
+ let attemptedEndpoints = 0;
835
+
836
+ for (const endpoint of allEndpoints) {
837
+ attemptedEndpoints++;
838
+ try {
839
+ let discoveredProjectId: string | null = null;
840
+
841
+ // 2a. With a disk-cached projectId, validate it first (compat)
842
+ if (cachedProjectId && cachedProjectId !== FALLBACK_PROJECT_ID) {
843
+ try {
844
+ const validated = await loadCodeAssist({
845
+ accessToken,
846
+ endpoint,
847
+ projectId: cachedProjectId,
848
+ userAgent,
849
+ ideType,
850
+ });
851
+ if (validated) discoveredProjectId = validated;
852
+ } catch (e: any) {
853
+ if (e instanceof ProjectDiscoveryError && !e.retryable) throw e;
854
+ // Retryable - fall through to pure bypass on the same endpoint
855
+ lastError = e instanceof ProjectDiscoveryError ? e : null;
856
+ }
857
+ }
858
+
859
+ // 2b. Pure bypass - load without projectId, exactly like Gemini CLI
860
+ if (!discoveredProjectId && bypass) {
861
+ try {
862
+ discoveredProjectId = await loadCodeAssist({ accessToken, endpoint, userAgent, ideType });
863
+ } catch (e: any) {
864
+ if (e instanceof ProjectDiscoveryError) {
865
+ lastError = e;
866
+ if (e.status === 403 || e.status === 404) {
867
+ // 403/404 means no provisioned project yet -> onboarding required;
868
+ // do not skip the endpoint yet, try onboard + load
869
+ } else if (!isRetryable(e.status ?? 0)) {
870
+ throw e;
871
+ } else {
872
+ // Retryable 5xx/408/429 -> next endpoint after onboard attempt
873
+ }
874
+ } else {
875
+ lastError = new ProjectDiscoveryError(
876
+ `unexpected load error at ${endpoint}: ${e?.message ?? String(e)}`,
877
+ 500,
878
+ endpoint,
879
+ );
880
+ }
881
+ }
882
+ }
883
+
884
+ // 2c. When load returned null or failed with 403/404: onboardUser + load again
885
+ if (!discoveredProjectId) {
886
+ try {
887
+ const onboarded = await onboardUser({ accessToken, endpoint, tierId: "free-tier", userAgent, ideType });
888
+ if (onboarded) {
889
+ discoveredProjectId = onboarded;
890
+ } else {
891
+ // gemini-cli pattern: onboard without immediate project, next load works
892
+ try {
893
+ discoveredProjectId = await loadCodeAssist({ accessToken, endpoint, userAgent, ideType });
894
+ } catch (loadAfterOnboardErr: any) {
895
+ if (loadAfterOnboardErr instanceof ProjectDiscoveryError) {
896
+ lastError = loadAfterOnboardErr;
897
+ if (!isRetryable(loadAfterOnboardErr.status ?? 0)) throw loadAfterOnboardErr;
898
+ continue;
899
+ }
900
+ lastError = loadAfterOnboardErr;
901
+ continue;
902
+ }
903
+ }
904
+ } catch (onboardErr: any) {
905
+ if (onboardErr instanceof ProjectDiscoveryError) {
906
+ lastError = onboardErr;
907
+ if (!isRetryable(onboardErr.status ?? 0)) throw onboardErr;
908
+ continue;
909
+ }
910
+ lastError = onboardErr;
911
+ continue;
912
+ }
913
+ }
914
+
915
+ if (!discoveredProjectId) {
916
+ lastError = new ProjectDiscoveryError(`no project id from ${endpoint}`, 404, endpoint);
917
+ continue;
918
+ }
919
+
920
+ // 2d. Best-effort validation via fetchAvailableModels - NEVER fatal:
921
+ // a 403 (region/entitlement) or any schema drift in this auxiliary call
922
+ // must not discard a successfully discovered projectId.
923
+ let modelsCount: number | undefined;
924
+ try {
925
+ const models = await fetchAvailableModels({
926
+ accessToken,
927
+ endpoint,
928
+ projectId: discoveredProjectId,
929
+ userAgent,
930
+ });
931
+ modelsCount = models.length;
932
+ } catch {
933
+ // swallow — the projectId is already validated by loadCodeAssist
934
+ }
935
+
936
+ // 2e. Success - save cache and metadata
937
+ setCachedProjectId(accessToken, discoveredProjectId, email);
938
+
939
+ if (metadataSaver) {
940
+ try {
941
+ await metadataSaver(discoveredProjectId);
942
+ } catch (saveErr) {
943
+ console.warn(
944
+ `[m[devthink] failed to save projectId metadata for ${email ?? "unknown"}: ${(saveErr as Error).message}`,
945
+ );
946
+ }
947
+ }
948
+
949
+ return {
950
+ projectId: discoveredProjectId,
951
+ fromCache: false,
952
+ endpointUsed: endpoint,
953
+ isFallback: false,
954
+ modelsChecked: modelsCount,
955
+ ideTypeUsed: ideType,
956
+ };
957
+ } catch (err: any) {
958
+ if (err instanceof ProjectDiscoveryError) {
959
+ lastError = err;
960
+ if (!err.retryable) throw err;
961
+ continue;
962
+ }
963
+ lastError = new ProjectDiscoveryError(
964
+ `unexpected error at ${endpoint}: ${err?.message ?? String(err)}`,
965
+ 500,
966
+ endpoint,
967
+ );
968
+ continue;
969
+ }
970
+ }
971
+
972
+ // 3. Every endpoint failed - blinded fallback with warning.
973
+ console.warn(
974
+ `[m[devthink] WARNING: all ${attemptedEndpoints} endpoints failed` +
975
+ `${lastError ? ` (last: ${lastError.status} ${lastError.message.slice(0, 250)})` : ""}.` +
976
+ ` Falling back to blinded project ${FALLBACK_PROJECT_ID}.` +
977
+ ` This works for Antigravity but will 403 for Gemini CLI models.` +
978
+ ` Bypass attempted to discover project via loadCodeAssist/onboardUser like Gemini CLI does.` +
979
+ ` Check SECURITY.md of the merged repository for the 403 permission guidance`,
980
+ );
981
+
982
+ setCachedProjectId(accessToken, FALLBACK_PROJECT_ID, email);
983
+
984
+ if (metadataSaver) {
985
+ try {
986
+ await metadataSaver(FALLBACK_PROJECT_ID);
987
+ } catch {
988
+ /* the guarded best-effort operation falls through: the outer flow owns the failure */
989
+ }
990
+ }
991
+
992
+ return {
993
+ projectId: FALLBACK_PROJECT_ID,
994
+ fromCache: false,
995
+ endpointUsed: undefined,
996
+ isFallback: true,
997
+ ideTypeUsed: ideType,
998
+ };
999
+ }
1000
+
1001
+ /** Convenience wrapper resolving and saving in one shot. */
1002
+ export async function resolveAndSaveProjectId(
1003
+ accessToken: string,
1004
+ account: { email?: string; projectId?: string; save?: AccountMetadataSaver },
1005
+ opts: Omit<ResolveProjectIdOptions, "accessToken" | "cachedProjectId" | "email" | "metadataSaver"> = {},
1006
+ ): Promise<ResolveResult> {
1007
+ return resolveProjectId({
1008
+ accessToken,
1009
+ cachedProjectId: account.projectId,
1010
+ email: account.email,
1011
+ metadataSaver: account.save,
1012
+ ...opts,
1013
+ });
1014
+ }
1015
+
1016
+ // ---------------------------------------------------------------------------
1017
+ // 13b. ABSORBED FROM models.ts (v2.1.15 Phase A) — the Gemini CLI bypass
1018
+ // resolver and the direct fetchAvailableModels call that used to live in
1019
+ // the models.ts project block. Flows kept verbatim; the primitives they
1020
+ // call are this module's canonical implementations above.
1021
+ // ---------------------------------------------------------------------------
1022
+
1023
+ export type ResolveProjectIdBypassOptions = {
1024
+ accessToken: string;
1025
+ cachedProjectId?: string;
1026
+ email?: string;
1027
+ userAgent?: string;
1028
+ endpointOverrides?: string[];
1029
+ isGeminiCliModel?: boolean; // when true, skip SANDBOX
1030
+ metadataSaver?: (projectId: string) => Promise<void> | void;
1031
+ };
1032
+
1033
+ export type ResolveProjectIdBypassResult = {
1034
+ projectId: string;
1035
+ fromCache: boolean;
1036
+ endpointUsed?: string | undefined;
1037
+ isFallback: boolean;
1038
+ attempts: string[];
1039
+ };
1040
+
1041
+ /**
1042
+ * Resolves the project id with the pure Gemini CLI bypass flow (the former
1043
+ * models.ts orchestrator): cache validation first, then a cachedProjectId
1044
+ * direct-load sweep, then the discovery cascade (load without project ->
1045
+ * onboard -> load again) over the pool-appropriate endpoint order, with the
1046
+ * rising-fact-p41fc blinded fallback last.
1047
+ */
1048
+ export async function resolveProjectIdAsGeminiCliBypass(
1049
+ opts: ResolveProjectIdBypassOptions,
1050
+ ): Promise<ResolveProjectIdBypassResult> {
1051
+ const { accessToken, cachedProjectId, email, userAgent, endpointOverrides, isGeminiCliModel, metadataSaver } = opts;
1052
+ if (!accessToken?.trim()) throw new ProjectDiscoveryError("accessToken required", 400, undefined, false);
1053
+
1054
+ // 1. Cache hit
1055
+ const cached = getCachedProjectId(accessToken);
1056
+ if (cached) {
1057
+ // best-effort validate with loadCodeAssist using cached id
1058
+ try {
1059
+ const ep = endpointOverrides?.[0] ?? CODE_ASSIST_ENDPOINTS.PROD;
1060
+ const validated = await loadCodeAssist({ accessToken, endpoint: ep, projectId: cached, userAgent });
1061
+ if (validated) {
1062
+ setCachedProjectId(accessToken, validated, email);
1063
+ return { projectId: validated, fromCache: true, endpointUsed: ep, isFallback: false, attempts: [ep] };
1064
+ }
1065
+ } catch {
1066
+ // ignore, proceed to discovery
1067
+ }
1068
+ // even if validation skipped, return cached if explicit override present
1069
+ if (cachedProjectId && cachedProjectId === cached) {
1070
+ return { projectId: cached, fromCache: true, isFallback: false, attempts: [] };
1071
+ }
1072
+ }
1073
+
1074
+ if (cachedProjectId?.trim()) {
1075
+ // Caller provided cached value (from accounts.json chmod 600) - try direct load with it
1076
+ const epList =
1077
+ endpointOverrides ?? (isGeminiCliModel ? [...ENDPOINT_ORDER_GEMINI_CLI] : [...ENDPOINT_ORDER_ANTIGRAVITY]);
1078
+ for (const ep of epList) {
1079
+ try {
1080
+ const pid = await loadCodeAssist({ accessToken, endpoint: ep, projectId: cachedProjectId.trim(), userAgent });
1081
+ if (pid) {
1082
+ setCachedProjectId(accessToken, pid, email);
1083
+ if (metadataSaver) await Promise.resolve(metadataSaver(pid)).catch(() => {});
1084
+ return {
1085
+ projectId: pid,
1086
+ fromCache: false,
1087
+ endpointUsed: ep,
1088
+ isFallback: false,
1089
+ attempts: epList as string[],
1090
+ };
1091
+ }
1092
+ } catch (e: any) {
1093
+ if (e instanceof ProjectDiscoveryError && !isRetryable(e.status ?? 0) && e.status !== 0) throw e;
1094
+ continue;
1095
+ }
1096
+ }
1097
+ }
1098
+
1099
+ // 2. Discovery cascade - PROD first, DAILY second, SANDBOX only if antigravity group
1100
+ const endpoints = endpointOverrides
1101
+ ? [...endpointOverrides]
1102
+ : isGeminiCliModel
1103
+ ? [...ENDPOINT_ORDER_GEMINI_CLI]
1104
+ : [...ENDPOINT_ORDER_ANTIGRAVITY];
1105
+
1106
+ let lastError: ProjectDiscoveryError | null = null;
1107
+ const attempts: string[] = [];
1108
+
1109
+ for (const ep of endpoints) {
1110
+ attempts.push(ep);
1111
+ try {
1112
+ // a) load without project id - Gemini CLI behavior
1113
+ let pid: string | null = null;
1114
+ try {
1115
+ pid = await loadCodeAssist({ accessToken, endpoint: ep, userAgent });
1116
+ } catch (e: any) {
1117
+ if (e instanceof ProjectDiscoveryError) {
1118
+ lastError = e;
1119
+ if (!e.retryable) throw e;
1120
+ continue;
1121
+ }
1122
+ lastError = new ProjectDiscoveryError(String(e?.message ?? e), 500, ep, true);
1123
+ continue;
1124
+ }
1125
+
1126
+ // b) if null, onboard then load again
1127
+ if (!pid) {
1128
+ try {
1129
+ const onboarded = await onboardUser({ accessToken, endpoint: ep, userAgent });
1130
+ if (onboarded) pid = onboarded;
1131
+ else pid = await loadCodeAssist({ accessToken, endpoint: ep, userAgent });
1132
+ } catch (e: any) {
1133
+ if (e instanceof ProjectDiscoveryError) {
1134
+ lastError = e;
1135
+ if (!isRetryable(e.status ?? 0) && e.status !== 0) throw e;
1136
+ continue;
1137
+ }
1138
+ lastError = new ProjectDiscoveryError(String(e?.message ?? e), 500, ep, true);
1139
+ continue;
1140
+ }
1141
+ }
1142
+
1143
+ if (!pid) {
1144
+ lastError = new ProjectDiscoveryError(`no project id from ${ep}`, 404, ep, true);
1145
+ continue;
1146
+ }
1147
+
1148
+ // success
1149
+ setCachedProjectId(accessToken, pid, email);
1150
+ if (metadataSaver) {
1151
+ try {
1152
+ await metadataSaver(pid);
1153
+ } catch {
1154
+ /* the guarded best-effort operation falls through: the outer flow owns the failure */
1155
+ }
1156
+ }
1157
+ return { projectId: pid, fromCache: false, endpointUsed: ep, isFallback: false, attempts };
1158
+ } catch (e: any) {
1159
+ if (e instanceof ProjectDiscoveryError) {
1160
+ lastError = e;
1161
+ if (!e.retryable) throw e;
1162
+ continue;
1163
+ }
1164
+ lastError = new ProjectDiscoveryError(`unexpected at ${ep}: ${String(e?.message ?? e)}`, 500, ep, true);
1165
+ continue;
1166
+ }
1167
+ }
1168
+
1169
+ // 3. All endpoints failed - hardened fallback (works for Antigravity, 403 for Gemini CLI models per README)
1170
+ // Observer notes: pi-antigravity-auth warns that fallback rising-fact-p41fc fails for gemini-cli group with 403
1171
+ // We return fallback only if caller allows antigravity group; for gemini-cli we still return fallback with isFallback true for caller to decide
1172
+ setCachedProjectId(accessToken, FALLBACK_PROJECT_ID, email);
1173
+ if (metadataSaver) {
1174
+ try {
1175
+ await metadataSaver(FALLBACK_PROJECT_ID);
1176
+ } catch {
1177
+ /* the guarded best-effort operation falls through: the outer flow owns the failure */
1178
+ }
1179
+ }
1180
+ return {
1181
+ projectId: FALLBACK_PROJECT_ID,
1182
+ fromCache: false,
1183
+ endpointUsed: undefined,
1184
+ isFallback: true,
1185
+ attempts,
1186
+ };
1187
+ }
1188
+
1189
+ /**
1190
+ * fetchAvailableModels direct on cloudcode-pa without localhost v1 proxy
1191
+ * (the former models.ts implementation): POSTs
1192
+ * /v1internal:fetchAvailableModels with the resolved project and normalizes
1193
+ * the varied response shapes into a plain id/name string list.
1194
+ */
1195
+ export async function fetchAvailableModelsDirect(opts: {
1196
+ accessToken: string;
1197
+ endpoint: string;
1198
+ projectId: string;
1199
+ userAgent?: string;
1200
+ }): Promise<string[]> {
1201
+ const { accessToken, endpoint, projectId, userAgent } = opts;
1202
+ const base = stripTrailingSlashes(endpoint);
1203
+ const url = `${base}${CODE_ASSIST_PATHS.FETCH_MODELS}`;
1204
+ const headers = buildAntagravityHeaders(accessToken, { userAgent });
1205
+ const body = JSON.stringify({ cloudaicompanionProject: projectId });
1206
+
1207
+ const res = await fetchWithTimeout(url, { method: "POST", headers, body }, FETCH_TIMEOUT_MS).catch((e: any) => {
1208
+ throw new ProjectDiscoveryError(
1209
+ `fetchAvailableModels network error ${endpoint}: ${e?.message ?? String(e)}`,
1210
+ 0,
1211
+ endpoint,
1212
+ true,
1213
+ );
1214
+ });
1215
+
1216
+ if (!res.ok) {
1217
+ const txt = await res.text().catch(() => "");
1218
+ throw new ProjectDiscoveryError(
1219
+ `fetchAvailableModels failed ${res.status} at ${endpoint}: ${txt.slice(0, 500)}`,
1220
+ res.status,
1221
+ endpoint,
1222
+ isRetryable(res.status),
1223
+ );
1224
+ }
1225
+
1226
+ try {
1227
+ const json = (await res.json()) as any;
1228
+ // Shapes: { models: [{ name, ... }] } or { availableModels: [...] }
1229
+ const arr = json?.models ?? json?.availableModels ?? json?.modelsList ?? [];
1230
+ if (Array.isArray(arr)) {
1231
+ return arr.map((m: any) => (typeof m === "string" ? m : (m?.name ?? m?.id ?? ""))).filter(Boolean);
1232
+ }
1233
+ if (Array.isArray(json)) return json.map((m: any) => (typeof m === "string" ? m : (m?.name ?? ""))).filter(Boolean);
1234
+ return [];
1235
+ } catch {
1236
+ return [];
1237
+ }
1238
+ }
1239
+
1240
+ // ---------------------------------------------------------------------------
1241
+ // 14. COMPAT EXPORTS + DEFAULT BUNDLE
1242
+ // ---------------------------------------------------------------------------
1243
+
1244
+ export const projectIdCacheForTesting = projectIdCache;
1245
+
1246
+ const projectModule = {
1247
+ // v2 constants
1248
+ CODE_ASSIST_ENDPOINTS,
1249
+ ENDPOINT_ORDER,
1250
+ FETCH_TIMEOUT_MS,
1251
+ FALLBACK_PROJECT_ID,
1252
+ MODELS_2026: MODELS_2026_BASE,
1253
+ MODELS_2026_BASE,
1254
+ MODELS_2026_ANTIGRAVITY,
1255
+ MODELS_2026_ALL,
1256
+ MODELS_2026_LEGACY,
1257
+ isRetryable,
1258
+ buildAntagravityHeaders,
1259
+ fetchWithTimeout,
1260
+ loadCodeAssist,
1261
+ onboardUser,
1262
+ fetchAvailableModels,
1263
+ resolveProjectId,
1264
+ resolveAndSaveProjectId,
1265
+ resolveProjectIdAsGeminiCliBypass,
1266
+ fetchAvailableModelsDirect,
1267
+ getCachedProjectId,
1268
+ setCachedProjectId,
1269
+ clearProjectIdCache,
1270
+ getCacheStats,
1271
+ fnv1a32,
1272
+ ProjectDiscoveryError,
1273
+ buildDefaultUserAgent,
1274
+ // v1 ported constants (v2.1.16: REQUEST_TIMEOUT_MS / PROJECT_CACHE_TTL_MS /
1275
+ // the legacy projectCache cluster / buildGeminiHeaders and the V1-suffixed
1276
+ // flow entries were deleted with the dead v1 flow)
1277
+ OAUTH_CLIENT_ID,
1278
+ OAUTH_CLIENT_SECRET,
1279
+ GEMINI_CLI_USER_AGENT,
1280
+ GOOGLE_API_NODE_VERSION,
1281
+ X_GOOG_API_CLIENT,
1282
+ CLIENT_METADATA,
1283
+ CLIENT_METADATA_STRING,
1284
+ ENDPOINT_BASES,
1285
+ CODE_ASSIST_PATHS,
1286
+ // v1 utils
1287
+ isLikelyExpiredToken,
1288
+ getPlatformInfo,
1289
+ };
1290
+
1291
+ export default projectModule;