@wenathlan/devthink 2.0.12 → 2.0.14

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 +20 -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/fingerprint.ts ADDED
@@ -0,0 +1,1458 @@
1
+ /**
2
+ * @file fingerprint.ts
3
+ * @module provider/fingerprint
4
+ * @description
5
+ * Anti-detection fingerprint generator mimicking Gemini CLI / Antigravity
6
+ * traffic against cloudcode-pa.googleapis.com (merged superset of the two
7
+ * prior variants).
8
+ *
9
+ * v2.1.15 Phase B: this module is THE single owner of identity masquerade —
10
+ * it absorbed getDynamicUserAgent (+Async), fetchRemoteAntigravityVersion,
11
+ * normalizePlatform/normalizeArch, getXGoogApiClient + X_GOOG_API_CLIENT (+
12
+ * the accurately split X_GOOG_API_CLIENT_GEMINI_CLI snapshot),
13
+ * buildClientMetadata, buildGeminiUserAgent, ua/apiClient (auth lineage),
14
+ * getRandomPlatformString, getGeminiCLIHeaders(WithOptions),
15
+ * getAntigravityHeaders(WithOptions), buildAntagravityHeaders (canonical of
16
+ * the five plugin/project/quota/search/models variants) and the hex/limit
17
+ * utility re-exports from core.js. Raw values stay in constants.ts; generic
18
+ * utilities live in core.js.
19
+ *
20
+ * Merged responsibilities:
21
+ * - OAuth constants, endpoints, 2026 model list
22
+ * - Weighted coherent platform pools (win32/x64, linux/x64, darwin/arm64)
23
+ * plus a keyed PLATFORM_POOLS record with Go-style amd64 naming
24
+ * - Deterministic FNV-1a hashing for session_id / user_prompt_id
25
+ * - Jitter 0-80ms anti-rate-limit (sync value + async sleep + instance API)
26
+ * - Gemini CLI style User-Agent with google-api-nodejs-client pairing
27
+ * - Antigravity style User-Agent (`antigravity/{version} {os}/{arch}`)
28
+ * - X-Goog-Api-Client generation (simplified and full chains)
29
+ * - Client-Metadata (fixed IDE_UNSPECIFIED form and structured JSON form)
30
+ * - Unified FingerprintGenerator constructor accepting BOTH option shapes:
31
+ * new FingerprintGenerator({ directory, allowExtendedPlatforms, ... })
32
+ * new FingerprintGenerator("1.19.2" | "antigravity/1.19.2 darwin/arm64")
33
+ * - defaultFingerprintGenerator singleton + getFingerprintHeaders() wrapper
34
+ *
35
+ * Runtime constraint: only node:* builtins + global fetch/Headers.
36
+ *
37
+ * @license MIT
38
+ */
39
+
40
+ import * as os from "node:os";
41
+ import * as path from "node:path";
42
+ import * as crypto from "node:crypto";
43
+
44
+ // ============================================================================
45
+ // Section: Mandatory constants (OAuth bypass context) — identical values are
46
+ // imported from the frozen owner constants.ts; divergent local variants are
47
+ // kept below with an explicit marker
48
+ // ============================================================================
49
+
50
+ import {
51
+ ANTIGRAVITY_CLI_VERSION_POOL,
52
+ ANTIGRAVITY_CLIENT_METADATA_TEMPLATE,
53
+ ANTIGRAVITY_USER_AGENT_FALLBACK,
54
+ ANTIGRAVITY_VERSION_FALLBACK,
55
+ ANTIGRAVITY_VERSION_REMOTE_URLS,
56
+ ARCH_POOL,
57
+ CLOUDCODE_BASE_URL,
58
+ CURRENT_GAPI_CLIENT_VERSION,
59
+ CURRENT_GEMINI_CLI_VERSION,
60
+ CURRENT_GL_NODE_VERSION,
61
+ FINGERPRINT_IDE_TYPES,
62
+ FINGERPRINT_PLATFORMS,
63
+ FINGERPRINT_PLUGIN_TYPES,
64
+ FINGERPRINT_USER_AGENT_SOURCES,
65
+ GAPI_CLIENT_VERSION_POOL,
66
+ GEMINI_CLI_OAUTH_CLIENT_ID as OAUTH_CLIENT_ID,
67
+ GEMINI_CLI_OAUTH_CLIENT_SECRET as OAUTH_CLIENT_SECRET,
68
+ GEMINI_CLI_SCOPES,
69
+ GEMINI_CLI_VERSION_POOL,
70
+ GL_NODE_VERSION_POOL,
71
+ PLATFORM_POOL,
72
+ PROJECT_FALLBACK,
73
+ VERSION_FETCH_TIMEOUT_MS,
74
+ } from "./constants.js";
75
+ import type { AntigravityVersion, SupportedArch, SupportedPlatform } from "./constants.js";
76
+ export { ANTIGRAVITY_VERSION_FALLBACK, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, PROJECT_FALLBACK };
77
+
78
+ // local variant: diverges from constants (constants.GEMINI_CLI_SCOPES is the 9-scope antigravity set with openid/email/profile/cclog; this is the 3-scope Gemini CLI set — value equals constants.GEMINI_CLI_SCOPES)
79
+ /** Scopes required by the Gemini CLI auth flow. */
80
+ // v2.1.15 Phase C: GEMINI_CLI_SCOPES (3-scope Gemini CLI set) is constants.js
81
+ // GEMINI_CLI_SCOPES — imported from the owner.
82
+
83
+ /** Base URL for the Cloud Code API (imported from constants.js — canonical owner since v2.1.15 Phase B). */
84
+ export { CLOUDCODE_BASE_URL };
85
+ export type { AntigravityVersion, SupportedArch, SupportedPlatform };
86
+
87
+ // local variant: diverges from core (core.ENDPOINTS is the {PROD,DAILY,SANDBOX} base-URL map; this is the full v1internal URL map keyed by RPC name)
88
+ /**
89
+ * v1internal endpoints used by Gemini CLI.
90
+ * All POST, requiring OAuth2 auth plus fingerprint headers.
91
+ */
92
+ // v2.1.15 Phase D: the method-path map moved to request.ts as API_PATHS;
93
+ // this module derives its private copy from the constants.js parts (layer
94
+ // rules forbid importing request.js from here).
95
+ const FINGERPRINT_API_PATHS = {
96
+ loadCodeAssist: `${CLOUDCODE_BASE_URL}/v1internal:loadCodeAssist`,
97
+ } as const;
98
+
99
+ // v2.1.15 Phase A: MODELS_2026 (the 11-id legacy bypass list) and the
100
+ // ModelId2026 union moved to models.ts (MODELS_2026_LEGACY / ModelId2026) —
101
+ // imported under the historical name and re-exported so the public surface
102
+ // is unchanged.
103
+ import { MODELS_2026_LEGACY as MODELS_2026, UA_DEFAULT_MODEL } from "./models.js";
104
+ import type { ModelId2026 } from "./models.js";
105
+ export { MODELS_2026 };
106
+ export type { ModelId2026 };
107
+
108
+ // ============================================================================
109
+ // Section: Platform types - coherent anti-ban pools
110
+ // ============================================================================
111
+
112
+ export type PlatformOS = "win32" | "linux" | "darwin";
113
+ export type PlatformArch = "x64" | "arm64";
114
+ export type FingerprintType = "gemini-cli" | "antigravity";
115
+
116
+ /**
117
+ * Supported header generation styles (observer naming for FingerprintType).
118
+ * - antigravity: emulates Antigravity Manager content requests
119
+ * - gemini-cli: emulates Gemini CLI quota pool (includes X-Goog-Api-Client)
120
+ */
121
+ export type HeaderStyle = "antigravity" | "gemini-cli";
122
+
123
+ /** OS name normalized for keyed fingerprint pools. */
124
+ export type OsName = "windows" | "darwin" | "linux";
125
+
126
+ /** Architecture name normalized for keyed fingerprint pools. */
127
+ export type ArchName = "amd64" | "arm64" | "x64";
128
+
129
+ /**
130
+ * Coherent platform profile for the weighted UA pools.
131
+ * Guarantees win32 never pairs with arm64 and that darwin prefers arm64.
132
+ */
133
+ export interface PlatformProfile {
134
+ /** Node.js OS identifier, exact `process.platform` value */
135
+ os: PlatformOS;
136
+ /** Node.js architecture identifier */
137
+ arch: PlatformArch;
138
+ /** Formatted string inside the UA parenthesis: "win32; x64" */
139
+ uaSegment: string;
140
+ /** Distribution source - GitHub dominant for coherence */
141
+ source: string;
142
+ /** Randomization weight - darwin arm64 more common among developers */
143
+ weight: number;
144
+ }
145
+
146
+ /**
147
+ * Keyed coherent platform pool entry (Go-style amd64 naming on Windows,
148
+ * matching observed AM traffic).
149
+ */
150
+ export interface PlatformPool {
151
+ /** Logical key e.g. "windows/amd64" */
152
+ readonly key: string;
153
+ /** Normalized OS name used in User-Agent */
154
+ readonly os: OsName;
155
+ /** Normalized arch name used in User-Agent */
156
+ readonly arch: ArchName;
157
+ /** Node.js platform identifier */
158
+ readonly nodePlatform: NodeJS.Platform;
159
+ /** Node.js arch identifier */
160
+ readonly nodeArch: string;
161
+ /** Electron/Chrome OS display fragment */
162
+ readonly displayOs: string;
163
+ }
164
+
165
+ /** Map of all known keyed pools. */
166
+ export type PoolMap = Record<string, PlatformPool>;
167
+
168
+ /**
169
+ * Keyed pool record. Windows uses Go-convention amd64 naming to match AM
170
+ * traffic; darwin uses arm64 (Apple Silicon primary for 2026); linux x64.
171
+ */
172
+ // local variant: diverges from constants (constants.FINGERPRINT_POOLS is the flat cli/gapi/node/platform pool record; this is the keyed Go-style platform map)
173
+ export const PLATFORM_POOLS: PoolMap = {
174
+ "windows/amd64": {
175
+ key: "windows/amd64",
176
+ os: "windows",
177
+ arch: "amd64",
178
+ nodePlatform: "win32",
179
+ nodeArch: "x64",
180
+ displayOs: "Windows_NT",
181
+ },
182
+ "darwin/arm64": {
183
+ key: "darwin/arm64",
184
+ os: "darwin",
185
+ arch: "arm64",
186
+ nodePlatform: "darwin",
187
+ nodeArch: "arm64",
188
+ displayOs: "Darwin",
189
+ },
190
+ "linux/x64": {
191
+ key: "linux/x64",
192
+ os: "linux",
193
+ arch: "x64",
194
+ nodePlatform: "linux",
195
+ nodeArch: "x64",
196
+ displayOs: "Linux",
197
+ },
198
+ } as const;
199
+
200
+ // v2.1.15 Phase B: the weighted PlatformProfile pool renamed from the
201
+ // historical name PLATFORM_POOL (which now resolves to the constants.ts raw
202
+ // string list ["linux","darwin","win32"] re-exported below). The two
203
+ // same-named values had different shapes, so the accurate split keeps each
204
+ // semantic under an honest name.
205
+ /** Weighted pool of real-world valid combinations. */
206
+ export const PLATFORM_PROFILE_POOL: readonly PlatformProfile[] = [
207
+ { os: "linux", arch: "x64", uaSegment: "linux; x64", source: "GitHub", weight: 35 },
208
+ { os: "darwin", arch: "arm64", uaSegment: "darwin; arm64", source: "GitHub", weight: 40 },
209
+ { os: "win32", arch: "x64", uaSegment: "win32; x64", source: "GitHub", weight: 25 },
210
+ ] as const;
211
+
212
+ export { PLATFORM_POOL };
213
+
214
+ export const PLATFORM_POOL_EXTENDED: readonly PlatformProfile[] = [
215
+ { os: "linux", arch: "x64", uaSegment: "linux; x64", source: "GitHub", weight: 30 },
216
+ { os: "darwin", arch: "arm64", uaSegment: "darwin; arm64", source: "GitHub", weight: 35 },
217
+ { os: "darwin", arch: "x64", uaSegment: "darwin; x64", source: "Homebrew", weight: 10 },
218
+ { os: "win32", arch: "x64", uaSegment: "win32; x64", source: "GitHub", weight: 20 },
219
+ { os: "linux", arch: "arm64", uaSegment: "linux; arm64", source: "GitHub", weight: 5 },
220
+ ] as const;
221
+
222
+ // ============================================================================
223
+ // Section: Version governance fallbacks
224
+ // ============================================================================
225
+
226
+ // ANTIGRAVITY_VERSION_FALLBACK ('1.19.2') is imported from constants.js
227
+ // (identical value) and re-exported above.
228
+
229
+ /** Preferred plugin version for new installs / Client-Metadata payloads. */
230
+ export const PLUGIN_VERSION_FALLBACK = "2.1.16";
231
+
232
+ // ============================================================================
233
+ // Section: Version pools - anti-ban randomized OS/version pairs
234
+ // (v2.1.15 Phase B: GEMINI_CLI_VERSION_POOL, GL_NODE_VERSION_POOL and
235
+ // ANTIGRAVITY_CLI_VERSION_POOL are imported from constants.js — the raw-value
236
+ // owner; the former local telemetry copies merged there.)
237
+ // ============================================================================
238
+
239
+ export { ANTIGRAVITY_CLI_VERSION_POOL, GEMINI_CLI_VERSION_POOL, GL_NODE_VERSION_POOL };
240
+
241
+ export const ANTIGRAVITY_VERSION_POOL = ["1.12.8", "1.12.7", "1.12.5", "1.11.9", "1.11.4"] as const;
242
+
243
+ // local variant: diverges from constants (constants.GAPI_CLIENT_VERSION_POOL is [9.15.1,9.14.0,9.15.0,9.13.0]; this pool adds 9.14.1 and orders 9.15.0 before 9.14.0)
244
+ /** google-api-nodejs-client versions - lockstep with GeminiCLI releases. */
245
+ export const GOOGLE_API_CLIENT_VERSION_POOL = ["9.15.1", "9.15.0", "9.14.1", "9.14.0", "9.13.0"] as const;
246
+
247
+ /** Complementary gax version pool. */
248
+ export const GAX_VERSION_POOL = ["5.0.4", "5.0.3", "4.3.6"] as const;
249
+
250
+ // ============================================================================
251
+ // Section: Crypto utilities - FNV-1a + secure random
252
+ // (v2.1.15 Phase B: fnv1a32/fnv1a32Hex/fnv1a64/pickRandom/getJitterMs are
253
+ // owned by core.js — the former local hex-string fnv1a32 was exactly core's
254
+ // fnv1a32Hex under the legacy name, so this module now imports and
255
+ // re-exports the canonical bindings.)
256
+ // ============================================================================
257
+
258
+ import { fnv1a32, fnv1a32Hex, fnv1a64, getJitterMs, pickRandom } from "./core.js";
259
+
260
+ export { fnv1a32, fnv1a32Hex, fnv1a64, getJitterMs, pickRandom };
261
+
262
+ // v2.1.16: the private fnv1a32Num was deleted — its output was verified
263
+ // byte-identical to core.fnv1a32 over ascii/non-ascii/empty/long inputs (same
264
+ // UTF-16 code-unit FNV-1a); the instance helpers below now delegate to the
265
+ // core import directly.
266
+
267
+ /**
268
+ * Raw numeric 64-bit FNV-1a over UTF-16 code units.
269
+ * Divergence from core.fnv1a64 (kept deliberately): the core export computes
270
+ * the same digest but returns it as a 16-char zero-padded hex string, while
271
+ * this private returns the bigint form because the instance helpers format
272
+ * and slice it themselves.
273
+ */
274
+ function fnv1a64Big(input: string): bigint {
275
+ let hash = 14695981039346656037n;
276
+ const prime = 1099511628211n;
277
+ for (let i = 0; i < input.length; i++) {
278
+ hash ^= BigInt(input.charCodeAt(i));
279
+ hash = (hash * prime) & 0xffffffffffffffffn;
280
+ }
281
+ return hash;
282
+ }
283
+
284
+ /**
285
+ * Generates a deterministic session_id based on the current directory.
286
+ * Same folder => same session_id => avoids device-hopping flags.
287
+ *
288
+ * @param directory - base path (defaults to process.cwd())
289
+ */
290
+ export function generateSessionUuid(directory?: string): string {
291
+ const dir = directory ?? process.cwd() ?? os.homedir();
292
+ const normalized = path.resolve(dir).toLowerCase();
293
+ const shortHash = fnv1a32Hex(normalized);
294
+ const longHash = fnv1a64(normalized + "|antigravity");
295
+ // Pseudo-UUID deterministic format, not reversible.
296
+ return `${shortHash.slice(0, 8)}-${longHash.slice(0, 4)}-${longHash.slice(4, 8)}-${longHash.slice(8, 12)}-${longHash.slice(12, 16)}${shortHash.slice(0, 4)}`;
297
+ }
298
+
299
+ /**
300
+ * Secure random float in [0,1) using crypto.randomInt with Math.random fallback.
301
+ */
302
+ export function secureRandom(): number {
303
+ try {
304
+ return crypto.randomInt(0, 1_000_000) / 1_000_000;
305
+ } catch {
306
+ return Math.random();
307
+ }
308
+ }
309
+
310
+ /** Picks a random element honoring optional weights. */
311
+ export function pickRandomWeighted<T extends { weight: number }>(pool: readonly T[]): T {
312
+ const total = pool.reduce((s, p) => s + p.weight, 0);
313
+ let r = secureRandom() * total;
314
+ for (const item of pool) {
315
+ if (r < item.weight) return item;
316
+ r -= item.weight;
317
+ }
318
+ return pool[pool.length - 1] as T;
319
+ }
320
+
321
+ /**
322
+ * Async jitter SLEEP in [minMs, maxMs] for anti-rate-limit / anti-ban delays
323
+ * (renamed from `jitter` in v2.1.15 Phase B: core.js owns the sync `jitter`
324
+ * number draw, so this async sleep variant carries an accurate distinct name).
325
+ */
326
+ export async function jitterSleep(minMs = 0, maxMs = 80): Promise<void> {
327
+ const delta = maxMs - minMs;
328
+ const wait = minMs + Math.floor(secureRandom() * (delta + 1));
329
+ if (wait <= 0) return;
330
+ await new Promise<void>((res) => setTimeout(res, wait));
331
+ }
332
+
333
+ // ============================================================================
334
+ // Section: Identity masquerade families (v2.1.15 Phase B absorption)
335
+ // — getDynamicUserAgent (constants/models/plugin/quota), buildGeminiUserAgent
336
+ // (auth/config), buildClientMetadata (constants/quota/request/search),
337
+ // getXGoogApiClient + X_GOOG_API_CLIENT (constants), ua/apiClient (auth),
338
+ // getGeminiCLIHeaders/getAntigravityHeaders (constants/plugin),
339
+ // buildAntagravityHeaders (models/plugin/project/quota/search) and
340
+ // getRandomPlatformString (constants) all live HERE now — the single home of
341
+ // identity/UA/header masquerade logic. Raw values stay in constants.ts.
342
+ // ============================================================================
343
+
344
+ /**
345
+ * Normalizes a Node platform identifier for UA emission (identity-form
346
+ * canonical: darwin/win32/linux pass through; exotic values pass through
347
+ * unchanged as well — this is a stable lowercase identity map, not a filter).
348
+ */
349
+ export function normalizePlatform(p: string): string {
350
+ switch (p) {
351
+ case "darwin":
352
+ return "darwin";
353
+ case "win32":
354
+ return "win32";
355
+ case "linux":
356
+ return "linux";
357
+ default:
358
+ return p;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Normalizes a Node arch identifier for UA emission
364
+ * (arm64/x64/ia32 identity; exotic values pass through unchanged).
365
+ */
366
+ export function normalizeArch(a: string): string {
367
+ switch (a) {
368
+ case "arm64":
369
+ return "arm64";
370
+ case "x64":
371
+ return "x64";
372
+ case "ia32":
373
+ return "ia32";
374
+ default:
375
+ return a;
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Dynamic Antigravity User-Agent: `antigravity/{version} {os}/{arch}`.
381
+ * Invalid/absent versions fall back to ANTIGRAVITY_VERSION_FALLBACK.
382
+ * Canonical of the getDynamicUserAgent family (v2.1.15 Phase B: the
383
+ * constants.ts, models.ts, plugin.ts and quota.ts locals deleted; the
384
+ * async version.ts GeminiCLI-template variant stays in version.ts under its
385
+ * own accurate name because it is a semantically distinct async builder).
386
+ */
387
+ export function getDynamicUserAgent(version?: string, opts?: { os?: string; arch?: string }): string {
388
+ const ver = (version && /^\d+\.\d+\.\d+/.test(version) ? version : ANTIGRAVITY_VERSION_FALLBACK).trim();
389
+ const plat = normalizePlatform(opts?.os ?? os.platform());
390
+ const arc = normalizeArch(opts?.arch ?? os.arch());
391
+ return `antigravity/${ver} ${plat}/${arc}`;
392
+ }
393
+
394
+ /**
395
+ * Async dynamic Antigravity User-Agent — resolves the remote Antigravity
396
+ * version when no cached version qualifies, then builds the UA via
397
+ * {@link getDynamicUserAgent}. (Absorbed from constants.ts in v2.1.15
398
+ * Phase B; version.ts's own remote chain remains the version-detection
399
+ * owner and may dedupe with {@link fetchRemoteAntigravityVersion} later.)
400
+ */
401
+ export async function getDynamicUserAgentAsync(cachedVersion?: string): Promise<string> {
402
+ if (cachedVersion && /^\d+\.\d+\.\d+/.test(cachedVersion)) {
403
+ return getDynamicUserAgent(cachedVersion);
404
+ }
405
+ try {
406
+ const latest = await fetchRemoteAntigravityVersion();
407
+ if (latest) return getDynamicUserAgent(latest);
408
+ } catch {
409
+ /* the guarded best-effort operation falls through: the outer flow owns the failure */
410
+ }
411
+ return getDynamicUserAgent();
412
+ }
413
+
414
+ /**
415
+ * Fetches the latest Antigravity version across the known update sources
416
+ * (absorbed from constants.ts in v2.1.15 Phase B). Returns null when every
417
+ * source fails; never throws.
418
+ */
419
+ export async function fetchRemoteAntigravityVersion(): Promise<string | null> {
420
+ const controller = new AbortController();
421
+ const to = setTimeout(() => controller.abort(), VERSION_FETCH_TIMEOUT_MS);
422
+ try {
423
+ for (const url of ANTIGRAVITY_VERSION_REMOTE_URLS) {
424
+ try {
425
+ const res = await fetch(url, {
426
+ signal: controller.signal,
427
+ headers: { Accept: "application/json", "User-Agent": ANTIGRAVITY_USER_AGENT_FALLBACK },
428
+ });
429
+ if (!res.ok) continue;
430
+ const json = (await res.json()) as any;
431
+ if (json?.version && /^\d+\.\d+\.\d+/.test(json.version)) return json.version;
432
+ if (json?.tag_name) {
433
+ const m = String(json.tag_name).match(/(\d+\.\d+\.\d+)/);
434
+ if (m) return m[1] ?? null;
435
+ }
436
+ if (json?.latest && /^\d+\.\d+\.\d+/.test(json.latest)) return json.latest;
437
+ } catch {
438
+ continue;
439
+ }
440
+ }
441
+ return null;
442
+ } finally {
443
+ clearTimeout(to);
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Builds the antigravity-style X-Goog-Api-Client chain:
449
+ * `antigravity/{version} gl-node/{node} gax/4.9.0 grpc/1.14.0`.
450
+ * Canonical of the getXGoogApiClient family (constants.ts lineage — the same
451
+ * chain request.ts's buildApiClient and plugin.ts's buildCommonHeaders
452
+ * compose inline; the bare `gl-node/22.19.0` snapshot has its own accurate
453
+ * name below).
454
+ */
455
+ export function getXGoogApiClient(version?: string): string {
456
+ const ver = version ?? ANTIGRAVITY_VERSION_FALLBACK;
457
+ const nodeVer = process.versions.node;
458
+ return `antigravity/${ver} gl-node/${nodeVer} gax/4.9.0 grpc/1.14.0`;
459
+ }
460
+
461
+ /**
462
+ * Canonical X_GOOG_API_CLIENT computed value — the dynamic
463
+ * {@link getXGoogApiClient} result (v2.1.15 Phase B: the constants.ts
464
+ * computed export moved here; the static "gl-node/22.19.0" copies in
465
+ * config/project/recovery/request/streaming were NOT the same value and
466
+ * keep their exact wire behavior through the accurately named
467
+ * {@link X_GOOG_API_CLIENT_GEMINI_CLI}).
468
+ */
469
+ export const X_GOOG_API_CLIENT: string = getXGoogApiClient();
470
+
471
+ /**
472
+ * Bare gl-node X-Goog-Api-Client snapshot observed on real Gemini CLI
473
+ * traffic (`gl-node/22.19.0`). Distinct value from {@link X_GOOG_API_CLIENT}
474
+ * — the former static "stale" copies in project.ts, quota.ts, request.ts,
475
+ * streaming.ts, recovery.ts, accounts.ts and config.ts resolve HERE so every
476
+ * live chain keeps sending exactly the bytes it sent before the merge.
477
+ */
478
+ export const X_GOOG_API_CLIENT_GEMINI_CLI = `gl-node/${CURRENT_GL_NODE_VERSION ?? "22.19.0"}` as const;
479
+
480
+ /**
481
+ * Builds the Antigravity Client-Metadata header string — the quota.ts
482
+ * lineage (live-validated quota chain): uppercase platform/arch,
483
+ * pluginVersion taken from the constants template, osVersion before arch.
484
+ * Canonical of the buildClientMetadata family (v2.1.15 Phase B: the
485
+ * constants.ts, quota.ts, request.ts and search.ts locals deleted).
486
+ */
487
+ export function buildClientMetadata(version?: string): string {
488
+ const ver = version ?? ANTIGRAVITY_VERSION_FALLBACK;
489
+ const plat = normalizePlatform(os.platform()).toUpperCase();
490
+ const arch = normalizeArch(os.arch()).toUpperCase();
491
+ const parts = [
492
+ `ideType=${ANTIGRAVITY_CLIENT_METADATA_TEMPLATE.ideType}`,
493
+ `platform=${plat}`,
494
+ `ideVersion=${ver}`,
495
+ `pluginVersion=${ANTIGRAVITY_CLIENT_METADATA_TEMPLATE.pluginVersion}`,
496
+ `osVersion=${os.release()}`,
497
+ `arch=${arch}`,
498
+ ];
499
+ return parts.join(",");
500
+ }
501
+
502
+ /**
503
+ * Builds the GeminiCLI User-Agent dynamically, varying the platform:
504
+ * `GeminiCLI/<cliVersion>/<model> (<platform>; <arch>; GitHub)
505
+ * google-api-nodejs-client/<gclientVersion>`. Canonical of the
506
+ * buildGeminiUserAgent family (v2.1.15 Phase B: config.ts lineage body —
507
+ * overrides accepted; the auth.ts win32->"windows" mapping is expressed by
508
+ * auth.ts passing the pre-mapped platform, so one implementation serves
509
+ * both historical call shapes).
510
+ */
511
+ export function buildGeminiUserAgent(overrides?: {
512
+ cliVersion?: string;
513
+ model?: string;
514
+ platform?: string;
515
+ arch?: string;
516
+ gclientVersion?: string;
517
+ }): string {
518
+ const cliVersion = overrides?.cliVersion ?? CURRENT_GEMINI_CLI_VERSION;
519
+ const model = overrides?.model ?? UA_DEFAULT_MODEL;
520
+ const platform = overrides?.platform ?? normalizePlatform(os.platform());
521
+ const arch = overrides?.arch ?? normalizeArch(os.arch());
522
+ const gclientVersion = overrides?.gclientVersion ?? CURRENT_GAPI_CLIENT_VERSION;
523
+
524
+ // Exact format of the real Gemini CLI
525
+ return `GeminiCLI/${cliVersion}/${model} (${platform}; ${arch}; GitHub) google-api-nodejs-client/${gclientVersion}`;
526
+ }
527
+
528
+ /**
529
+ * Antigravity User-Agent short form (auth.ts lineage):
530
+ * `antigravity/{version} {platform}/{arch}`. Thin alias of
531
+ * {@link getDynamicUserAgent} kept under its historical auth surface name;
532
+ * the auth call sites always invoke it without arguments, where the two
533
+ * implementations are byte-identical.
534
+ */
535
+ export const ua = (v?: string): string => getDynamicUserAgent(v ?? ANTIGRAVITY_VERSION_FALLBACK);
536
+
537
+ /**
538
+ * Antigravity X-Goog-Api-Client short form (auth.ts lineage):
539
+ * `antigravity/{version} gl-node/{node} gccl/1.0.0 gax/1.0.0`. Distinct
540
+ * segment chain from {@link getXGoogApiClient} (gccl/gax-1.0.0 vs
541
+ * gax-4.9.0/grpc) — the live auth retrieveQuota path sends exactly this
542
+ * form, so it keeps its own accurate implementation here.
543
+ */
544
+ export const apiClient = (v = "1.19.2"): string =>
545
+ `antigravity/${v} gl-node/${process.versions.node} gccl/1.0.0 gax/1.0.0`;
546
+
547
+ /**
548
+ * Builds a platform string in the format expected by the Gemini CLI
549
+ * User-Agent, e.g. "linux; x64; GitHub" (absorbed from constants.ts in
550
+ * v2.1.15 Phase B).
551
+ */
552
+ export function getRandomPlatformString(platformOverride?: SupportedPlatform, archOverride?: SupportedArch): string {
553
+ const plat: SupportedPlatform = platformOverride ?? pickRandom(PLATFORM_POOL);
554
+ const arch: SupportedArch = archOverride ?? pickRandom(ARCH_POOL);
555
+
556
+ const source = pickRandom(FINGERPRINT_USER_AGENT_SOURCES);
557
+
558
+ // win32 maps to win in the gemini-cli UA
559
+ const uaPlatform = plat === "win32" ? "win32" : plat;
560
+ return `${uaPlatform}; ${arch}; ${source}`;
561
+ }
562
+
563
+ // ---------------------------------------------------------------------------
564
+ // Gemini CLI header generators (absorbed from constants.ts)
565
+ // ---------------------------------------------------------------------------
566
+
567
+ export interface GeminiCliHeaderOptions {
568
+ /** model to include in the User-Agent (e.g. gemini-3-pro-preview) */
569
+ model?: string | undefined;
570
+ /** platform override (randomizes / uses process.platform when absent) */
571
+ platform?: SupportedPlatform | undefined;
572
+ /** arch override */
573
+ arch?: SupportedArch;
574
+ /** specific Gemini CLI version to spoof */
575
+ cliVersion?: string;
576
+ /** specific gapi client version */
577
+ gapiVersion?: string;
578
+ /** specific gl-node version */
579
+ glNodeVersion?: string;
580
+ /** access token for Authorization */
581
+ accessToken?: string;
582
+ /** custom ideType */
583
+ ideType?: (typeof FINGERPRINT_IDE_TYPES)[number];
584
+ /** custom platform metadata */
585
+ platformMeta?: (typeof FINGERPRINT_PLATFORMS)[number];
586
+ /** custom pluginType */
587
+ pluginType?: (typeof FINGERPRINT_PLUGIN_TYPES)[number];
588
+ }
589
+
590
+ /**
591
+ * Generates headers identical to the official Gemini CLI for detection
592
+ * bypass (canonical of the family: the richer constants.ts WithOptions
593
+ * lineage; plugin.ts's legacy terminal-platform variant had zero callers
594
+ * and was deleted in v2.1.15 Phase B).
595
+ *
596
+ * @param model - optional, defaults to gemini-3-pro-preview
597
+ * @param platform - optional, randomized with linux bias by default
598
+ */
599
+ export function getGeminiCLIHeaders(model?: string, platform?: SupportedPlatform): Record<string, string> {
600
+ // opts-object overload also supported for compat
601
+ return getGeminiCLIHeadersWithOptions({
602
+ model,
603
+ platform,
604
+ });
605
+ }
606
+
607
+ /** Object-style variant, more robust for internal plugin usage. */
608
+ export function getGeminiCLIHeadersWithOptions(opts: GeminiCliHeaderOptions = {}): Record<string, string> {
609
+ const {
610
+ model = UA_DEFAULT_MODEL,
611
+ platform,
612
+ arch,
613
+ cliVersion = pickRandom(GEMINI_CLI_VERSION_POOL),
614
+ gapiVersion = pickRandom(GAPI_CLIENT_VERSION_POOL),
615
+ glNodeVersion = pickRandom(GL_NODE_VERSION_POOL),
616
+ accessToken,
617
+ ideType = "IDE_UNSPECIFIED",
618
+ platformMeta = "PLATFORM_UNSPECIFIED",
619
+ pluginType = "GEMINI",
620
+ } = opts;
621
+
622
+ const platformString = getRandomPlatformString(platform, arch);
623
+
624
+ const userAgent = `GeminiCLI/${cliVersion}/${model} (${platformString}) google-api-nodejs-client/${gapiVersion}`;
625
+
626
+ const headers: Record<string, string> = {
627
+ "User-Agent": userAgent,
628
+ "X-Goog-Api-Client": `gl-node/${glNodeVersion}`,
629
+ "Client-Metadata": `ideType=${ideType},platform=${platformMeta},pluginType=${pluginType}`,
630
+ "Content-Type": "application/json",
631
+ Accept: "application/json",
632
+ "Accept-Encoding": "gzip, deflate, br",
633
+ "Accept-Language": "en-US,en;q=0.9",
634
+ };
635
+
636
+ if (accessToken) {
637
+ headers["Authorization"] = `Bearer ${accessToken}`;
638
+ }
639
+
640
+ return headers;
641
+ }
642
+
643
+ /** Headers specialized for Antigravity versioning. */
644
+ export interface AntigravityHeaderOptions {
645
+ version?: AntigravityVersion;
646
+ accessToken?: string;
647
+ cliVersion?: string;
648
+ model?: string;
649
+ platform?: SupportedPlatform;
650
+ }
651
+
652
+ export function getAntigravityHeaders(version?: AntigravityVersion | string): Record<string, string> {
653
+ return getAntigravityHeadersWithOptions({ version: version as AntigravityVersion });
654
+ }
655
+
656
+ export function getAntigravityHeadersWithOptions(opts: AntigravityHeaderOptions = {}): Record<string, string> {
657
+ const {
658
+ version = "v1internal" as AntigravityVersion,
659
+ accessToken,
660
+ cliVersion = CURRENT_GEMINI_CLI_VERSION,
661
+ model = UA_DEFAULT_MODEL,
662
+ platform,
663
+ } = opts;
664
+
665
+ // Base Gemini CLI headers + antigravity overrides
666
+ const base = getGeminiCLIHeadersWithOptions({
667
+ model,
668
+ platform,
669
+ cliVersion,
670
+ });
671
+
672
+ const versionMeta = version === "daily-v1internal" ? "daily" : version === "v1" ? "v1" : "v1internal";
673
+
674
+ return {
675
+ ...base,
676
+ "X-Goog-Api-Client": base["X-Goog-Api-Client"] ?? `gl-node/${pickRandom(GL_NODE_VERSION_POOL)}`,
677
+ "X-Antigravity-Version": versionMeta,
678
+ "X-Antigravity-Client": `antigravity-gemini-cli/${cliVersion}`,
679
+ ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
680
+ };
681
+ }
682
+
683
+ // ---------------------------------------------------------------------------
684
+ // Antigravity content header builder (canonical of the five-variant family)
685
+ // ---------------------------------------------------------------------------
686
+
687
+ /**
688
+ * Options for {@link buildAntagravityHeaders} — the canonical of the
689
+ * buildAntagravityHeaders family (v2.1.15 Phase B). The flags preserve each
690
+ * historical variant's exact effective header set:
691
+ * - plugin.ts / project.ts lineage (default): Authorization, Content-Type,
692
+ * User-Agent + extras;
693
+ * - quota.ts lineage: `accept: true` adds Accept: application/json;
694
+ * - search.ts lineage: `identityHeaders: true` adds X-Goog-Api-Client
695
+ * (dynamic getXGoogApiClient) and Client-Metadata (buildClientMetadata);
696
+ * - the models.ts strip-variant had zero consumers and was deleted.
697
+ */
698
+ export interface AntigravityHeadersOptions {
699
+ userAgent?: string | undefined;
700
+ extra?: Record<string, string>;
701
+ /** Include Accept: application/json (quota/search lineage). Default false. */
702
+ accept?: boolean;
703
+ /** Include X-Goog-Api-Client + Client-Metadata (search lineage). Default false. */
704
+ identityHeaders?: boolean;
705
+ }
706
+
707
+ /**
708
+ * Builds the Antigravity content-request header map (Authorization bearer,
709
+ * JSON content type, dynamic Antigravity UA, optional Accept and bypass
710
+ * identity headers, extras merged last with x-goog-user-project always
711
+ * filtered per the observer requirement).
712
+ */
713
+ export function buildAntagravityHeaders(
714
+ accessToken: string,
715
+ opts: AntigravityHeadersOptions = {},
716
+ ): Record<string, string> {
717
+ const userAgent = opts.userAgent ?? getDynamicUserAgent();
718
+ const headers: Record<string, string> = {
719
+ Authorization: `Bearer ${accessToken}`,
720
+ "Content-Type": "application/json",
721
+ "User-Agent": userAgent,
722
+ };
723
+ if (opts.accept) {
724
+ headers["Accept"] = "application/json";
725
+ }
726
+ if (opts.identityHeaders) {
727
+ headers["X-Goog-Api-Client"] = getXGoogApiClient();
728
+ headers["Client-Metadata"] = buildClientMetadata();
729
+ }
730
+ if (opts.extra) {
731
+ for (const [k, v] of Object.entries(opts.extra)) {
732
+ if (!k) continue;
733
+ if (k.toLowerCase() === "x-goog-user-project") continue; // observer strips this header per spec
734
+ headers[k] = v;
735
+ }
736
+ }
737
+ return headers;
738
+ }
739
+
740
+ // ============================================================================
741
+ // Section: Main class - unified FingerprintGenerator
742
+ // ============================================================================
743
+
744
+ export interface FingerprintOptions {
745
+ /** Directory for deterministic seed (defaults to cwd) */
746
+ directory?: string;
747
+ /** Allow rare extended platform combos (default false) */
748
+ allowExtendedPlatforms?: boolean;
749
+ /** Pin a specific platform (for tests) */
750
+ fixedPlatform?: PlatformProfile | undefined;
751
+ /** Pin the Gemini CLI version */
752
+ fixedCliVersion?: string | undefined;
753
+ /** Pin the google-api-client version */
754
+ fixedGoogleApiVersion?: string | undefined;
755
+ /** Pin the gl-node version */
756
+ fixedNodeVersion?: string | undefined;
757
+ /**
758
+ * Antigravity app version embedded in observer-style UAs.
759
+ * Accepts bare semver ("1.19.2") or full UA strings
760
+ * ("antigravity/1.19.2 darwin/arm64"); invalid values fall back to
761
+ * ANTIGRAVITY_VERSION_FALLBACK.
762
+ */
763
+ antigravityVersion?: string;
764
+ }
765
+
766
+ export interface FullHeadersResult {
767
+ /** Exact Gemini CLI User-Agent */
768
+ "User-Agent": string;
769
+ /** X-Goog-Api-Client fingerprint */
770
+ "X-Goog-Api-Client": string;
771
+ /** Hardened Client-Metadata */
772
+ "Client-Metadata": string;
773
+ /** Standard Content-Type */
774
+ "Content-Type": string;
775
+ /** Accept */
776
+ Accept: string;
777
+ /** Optional for some endpoints */
778
+ "X-Client-Details"?: string;
779
+ /** Extra metadata for internal debug/tracing */
780
+ __meta: {
781
+ platform: PlatformProfile;
782
+ cliVersion: string;
783
+ googleApiVersion: string;
784
+ glNodeVersion: string;
785
+ gaxVersion: string;
786
+ sessionId: string;
787
+ model: string;
788
+ type: FingerprintType;
789
+ jitterMs: number;
790
+ };
791
+ }
792
+
793
+ /**
794
+ * Anti-detection generator that mimics Gemini CLI exactly while also exposing
795
+ * the observer-style Antigravity header set.
796
+ *
797
+ * Coherence guarantees:
798
+ * - Chosen platform drives the UA segment consistently
799
+ * - Node / gax / google-api-client versions pair like a real release
800
+ * - session_id is deterministic per directory (avoids device hopping)
801
+ * - Observer mode never mutates global state
802
+ *
803
+ * Usage:
804
+ * ```ts
805
+ * const fp = new FingerprintGenerator({ directory: process.cwd() });
806
+ * const headers = fp.getFullHeaders('gemini-3-pro-preview', 'gemini-cli');
807
+ * // or observer style:
808
+ * const fp2 = new FingerprintGenerator('1.19.2');
809
+ * const h2 = fp2.getFingerprintHeaders('gemini-cli');
810
+ * ```
811
+ */
812
+ export class FingerprintGenerator {
813
+ // Legacy weighted-pool state
814
+ private readonly directory: string;
815
+ private readonly allowExtended: boolean;
816
+ private readonly fixedPlatform?: PlatformProfile | undefined;
817
+ private readonly fixedCliVersion?: string | undefined;
818
+ private readonly fixedGoogleApiVersion?: string | undefined;
819
+ private readonly fixedNodeVersion?: string | undefined;
820
+ private readonly sessionId: string;
821
+ private cachedPlatform: PlatformProfile | null = null;
822
+
823
+ // Observer-style state
824
+ /** Antigravity app version used in observer User-Agents. */
825
+ private readonly version: string;
826
+ private poolCache: PlatformPool | null = null;
827
+
828
+ /**
829
+ * Unified constructor accepting BOTH historical option shapes:
830
+ *
831
+ * @param opts - either an options object ({@link FingerprintOptions}) or a
832
+ * plain version string ("1.19.2", "antigravity/1.19.2 ...",
833
+ * falls back to ANTIGRAVITY_VERSION_FALLBACK when invalid).
834
+ */
835
+ constructor(opts?: FingerprintOptions | string) {
836
+ const o: FingerprintOptions = typeof opts === "string" ? { antigravityVersion: opts } : (opts ?? {});
837
+
838
+ this.directory = o.directory ? path.resolve(o.directory) : process.cwd();
839
+ this.allowExtended = o.allowExtendedPlatforms ?? false;
840
+ this.fixedPlatform = o.fixedPlatform;
841
+ this.fixedCliVersion = o.fixedCliVersion;
842
+ this.fixedGoogleApiVersion = o.fixedGoogleApiVersion;
843
+ this.fixedNodeVersion = o.fixedNodeVersion;
844
+ this.sessionId = generateSessionUuid(this.directory);
845
+ this.version = this.normalizeAntigravityVersion(o.antigravityVersion);
846
+ }
847
+
848
+ /** Validates / extracts the Antigravity version from raw input. */
849
+ private normalizeAntigravityVersion(raw?: string): string {
850
+ const v = (raw ?? "").trim();
851
+ if (!v) return ANTIGRAVITY_VERSION_FALLBACK;
852
+ if (/^\d+\.\d+\.\d+/.test(v)) return v;
853
+ if (/^antigravity\//i.test(v)) {
854
+ // Allow passing a full UA string - extract the version part.
855
+ const m = v.match(/antigravity\/([0-9]+\.[0-9]+\.[0-9]+[^\s]*)/i);
856
+ return m && m[1] ? m[1] : ANTIGRAVITY_VERSION_FALLBACK;
857
+ }
858
+ return ANTIGRAVITY_VERSION_FALLBACK;
859
+ }
860
+
861
+ // --------------------------------------------------------------------------
862
+ // Hashing helpers (instance level)
863
+ // --------------------------------------------------------------------------
864
+
865
+ /** Numeric 32-bit FNV-1a (deterministic, fast, no crypto overhead). */
866
+ private fnv1a32m(input: string): number {
867
+ if (typeof input !== "string") throw new TypeError("fnv1a32 input must be string");
868
+ return fnv1a32(input);
869
+ }
870
+
871
+ /** Numeric 64-bit FNV-1a for longer session identifiers. */
872
+ private fnv1a64b(input: string): bigint {
873
+ if (typeof input !== "string") throw new TypeError("fnv1a64 input must be string");
874
+ return fnv1a64Big(input);
875
+ }
876
+
877
+ /** Hex representation (8 chars) of the 32-bit FNV-1a hash. */
878
+ public fnv1aHex(input: string): string {
879
+ return this.fnv1a32m(input).toString(16).padStart(8, "0");
880
+ }
881
+
882
+ /**
883
+ * Deterministic session id from a seed (sess_ prefixed, FNV-based). When no
884
+ * seed is supplied, falls back to randomUUID + hostname + timestamp hashed
885
+ * into the same format. Never throws.
886
+ *
887
+ * v2.1.16 divergence note: {@link generateUserPromptId} below is the
888
+ * structural twin of this method, kept separate (not parametrized into one
889
+ * prefix+salt helper) because the outputs intentionally differ in four
890
+ * places: the prefix (sess_ vs up_), the secondary hash salt (`:sess` vs
891
+ * `:up`), the unseeded base (Date.now() vs process.pid) and the catch-path
892
+ * fallback shape (sess_ appends a base36 timestamp, up_ does not).
893
+ */
894
+ public generateSessionId(seed?: string): string {
895
+ try {
896
+ if (seed && typeof seed === "string" && seed.length > 0) {
897
+ const h = this.fnv1a64b(seed);
898
+ const hex = h.toString(16).padStart(16, "0");
899
+ const h2 = this.fnv1a32m(`${seed}:sess`);
900
+ const suffix = h2.toString(16).padStart(8, "0");
901
+ return `sess_${hex}${suffix}`;
902
+ }
903
+ const randomBase = `${crypto.randomUUID()}:${os.hostname()}:${Date.now()}`;
904
+ const h = this.fnv1a64b(randomBase);
905
+ const timeFrag = Date.now().toString(36);
906
+ return `sess_${h.toString(16).padStart(16, "0")}_${timeFrag}`;
907
+ } catch {
908
+ const fallback = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
909
+ return `sess_${this.fnv1aHex(fallback)}_${Date.now().toString(36)}`;
910
+ }
911
+ }
912
+
913
+ /**
914
+ * Deterministic user_prompt_id mirroring Antigravity Manager behavior where
915
+ * prompt IDs stay stable per request content. Never throws.
916
+ */
917
+ public generateUserPromptId(seed?: string): string {
918
+ try {
919
+ if (seed && typeof seed === "string" && seed.length > 0) {
920
+ const h = this.fnv1a64b(seed);
921
+ const hex = h.toString(16).padStart(16, "0");
922
+ const h2 = this.fnv1a32m(`${seed}:up`);
923
+ return `up_${hex}${h2.toString(16).padStart(8, "0")}`;
924
+ }
925
+ const randomBase = `${crypto.randomUUID()}:${os.hostname()}:${process.pid}`;
926
+ const h = this.fnv1a64b(randomBase);
927
+ return `up_${h.toString(16).padStart(16, "0")}_${Date.now().toString(36)}`;
928
+ } catch {
929
+ const fallback = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
930
+ return `up_${this.fnv1aHex(fallback)}`;
931
+ }
932
+ }
933
+
934
+ // --------------------------------------------------------------------------
935
+ // Platform selection - coherent + weighted
936
+ // --------------------------------------------------------------------------
937
+
938
+ /**
939
+ * Returns a coherent platform with per-instance caching for stability.
940
+ * A fixedPlatform always wins (deterministic tests); an explicit override
941
+ * bypasses the cache entirely.
942
+ */
943
+ getPlatform(override?: PlatformProfile): PlatformProfile {
944
+ if (override) return override;
945
+ if (this.fixedPlatform) return this.fixedPlatform;
946
+ if (this.cachedPlatform) return this.cachedPlatform;
947
+
948
+ const pool = this.allowExtended ? PLATFORM_POOL_EXTENDED : PLATFORM_PROFILE_POOL;
949
+ const chosen = pickRandomWeighted(pool);
950
+ this.cachedPlatform = chosen;
951
+ return chosen;
952
+ }
953
+
954
+ /** Forces a platform rotation (useful for anti-ban testing). */
955
+ rotatePlatform(): PlatformProfile {
956
+ const pool = this.allowExtended ? PLATFORM_POOL_EXTENDED : PLATFORM_PROFILE_POOL;
957
+ const chosen = pickRandomWeighted(pool);
958
+ this.cachedPlatform = chosen;
959
+ return chosen;
960
+ }
961
+
962
+ /**
963
+ * Runtime-detected keyed pool (cached):
964
+ * win32 -> windows/amd64, darwin -> darwin/arm64 (even under Rosetta),
965
+ * everything else -> linux/x64.
966
+ */
967
+ public getPlatformPool(): PlatformPool {
968
+ if (this.poolCache) return this.poolCache;
969
+
970
+ const plat = os.platform();
971
+ let key: string;
972
+ if (plat === "win32") key = "windows/amd64";
973
+ else if (plat === "darwin") key = "darwin/arm64";
974
+ else key = "linux/x64";
975
+
976
+ const pool = PLATFORM_POOLS[key];
977
+ const fallbackPool = PLATFORM_POOLS["linux/x64"];
978
+ // Defensive: both entries are static constants, so at least one exists.
979
+ this.poolCache = (pool ?? fallbackPool) as PlatformPool;
980
+ return this.poolCache;
981
+ }
982
+
983
+ /** Static accessor for all keyed pools (testing / CLI listing). */
984
+ public static getPlatformPools(): PoolMap {
985
+ return { ...PLATFORM_POOLS };
986
+ }
987
+
988
+ /** Current keyed pool key, e.g. "darwin/arm64". */
989
+ public getCurrentPoolKey(): string {
990
+ return this.getPlatformPool().key;
991
+ }
992
+
993
+ // --------------------------------------------------------------------------
994
+ // Jitter (instance API)
995
+ // --------------------------------------------------------------------------
996
+
997
+ /** Cryptographically strong jitter in the inclusive 0..80ms range. */
998
+ public getJitterMs(): number {
999
+ try {
1000
+ const v = crypto.randomInt(0, 81);
1001
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1002
+ } catch {
1003
+ // fall through to Math.random
1004
+ }
1005
+ return Math.floor(Math.random() * 81);
1006
+ }
1007
+
1008
+ /** Applies jitter asynchronously - awaits 0..80ms. */
1009
+ public async applyJitter(): Promise<void> {
1010
+ const ms = this.getJitterMs();
1011
+ if (ms <= 0) return;
1012
+ await new Promise<void>((resolve) => setTimeout(resolve, ms));
1013
+ }
1014
+
1015
+ // --------------------------------------------------------------------------
1016
+ // Version pairs - anti-ban randomized
1017
+ // --------------------------------------------------------------------------
1018
+
1019
+ /**
1020
+ * Produces a coherent version set that actually shipped together.
1021
+ * 70% recent-user slice, 30% long-tail for realistic distribution.
1022
+ */
1023
+ private getCoherentVersionPair(): {
1024
+ cliVersion: string;
1025
+ googleApiVersion: string;
1026
+ glNodeVersion: string;
1027
+ gaxVersion: string;
1028
+ } {
1029
+ if (this.fixedCliVersion || this.fixedGoogleApiVersion || this.fixedNodeVersion) {
1030
+ return {
1031
+ cliVersion: this.fixedCliVersion ?? pickRandom(GEMINI_CLI_VERSION_POOL),
1032
+ googleApiVersion: this.fixedGoogleApiVersion ?? pickRandom(GOOGLE_API_CLIENT_VERSION_POOL),
1033
+ glNodeVersion: this.fixedNodeVersion ?? pickRandom(GL_NODE_VERSION_POOL),
1034
+ gaxVersion: pickRandom(GAX_VERSION_POOL),
1035
+ };
1036
+ }
1037
+
1038
+ const isRecentUser = secureRandom() < 0.7;
1039
+
1040
+ if (isRecentUser) {
1041
+ return {
1042
+ cliVersion: pickRandom(GEMINI_CLI_VERSION_POOL.slice(0, 3)),
1043
+ googleApiVersion: pickRandom(GOOGLE_API_CLIENT_VERSION_POOL.slice(0, 2)),
1044
+ glNodeVersion: pickRandom(GL_NODE_VERSION_POOL.slice(0, 3)),
1045
+ gaxVersion: GAX_VERSION_POOL[0],
1046
+ };
1047
+ }
1048
+ return {
1049
+ cliVersion: pickRandom(GEMINI_CLI_VERSION_POOL.slice(2)),
1050
+ googleApiVersion: pickRandom(GOOGLE_API_CLIENT_VERSION_POOL.slice(2)),
1051
+ glNodeVersion: pickRandom(GL_NODE_VERSION_POOL.slice(2)),
1052
+ gaxVersion: pickRandom(GAX_VERSION_POOL.slice(1)),
1053
+ };
1054
+ }
1055
+
1056
+ // --------------------------------------------------------------------------
1057
+ // User-Agent generators
1058
+ // --------------------------------------------------------------------------
1059
+
1060
+ /**
1061
+ * User-Agent identical to the official Gemini CLI:
1062
+ * `GeminiCLI/0.57.0/gemini-3-pro-preview (win32; x64; GitHub) google-api-nodejs-client/9.15.1`
1063
+ */
1064
+ getGeminiCLIUserAgent(model: string = UA_DEFAULT_MODEL, platform?: PlatformProfile): string {
1065
+ const plat = this.getPlatform(platform);
1066
+ const { cliVersion, googleApiVersion } = this.getCoherentVersionPair();
1067
+
1068
+ // antigravity-* models are kept whole; the backend accepts them.
1069
+ const modelSegment = model.trim();
1070
+ return `GeminiCLI/${cliVersion}/${modelSegment} (${plat.uaSegment}; ${plat.source}) google-api-nodejs-client/${googleApiVersion}`;
1071
+ }
1072
+
1073
+ /**
1074
+ * Antigravity-flavored User-Agent observed in fetchAvailableModels traces:
1075
+ * `Antigravity/1.12.8 (linux; x64) CodeAssist/0.9.2 google-api-nodejs-client/9.15.1 gl-node/22.19.0`
1076
+ */
1077
+ getAntigravityUserAgent(version?: string, platform?: PlatformProfile): string {
1078
+ const plat = this.getPlatform(platform);
1079
+ const coherent = this.getCoherentVersionPair();
1080
+ const agVersion = version ?? pickRandom(ANTIGRAVITY_VERSION_POOL);
1081
+ const googleVer = coherent.googleApiVersion;
1082
+ const glNode = coherent.glNodeVersion;
1083
+
1084
+ // Real traces include a CodeAssist sub-version.
1085
+ const codeAssistVersions = ["0.9.2", "0.9.1", "0.8.7"];
1086
+ const caVer = pickRandom(codeAssistVersions);
1087
+
1088
+ return `Antigravity/${agVersion} (${plat.uaSegment}) CodeAssist/${caVer} google-api-nodejs-client/${googleVer} gl-node/${glNode}`;
1089
+ }
1090
+
1091
+ // --------------------------------------------------------------------------
1092
+ // X-Goog-Api-Client + Client-Metadata
1093
+ // --------------------------------------------------------------------------
1094
+
1095
+ /**
1096
+ * X-Goog-Api-Client identical to the Node.js Google client. 80% simplified
1097
+ * (`gl-node/22.19.0`), 20% full chain with gccl/gax/grpc segments.
1098
+ */
1099
+ getXGoogApiClient(): string {
1100
+ const { glNodeVersion, gaxVersion } = this.getCoherentVersionPair();
1101
+
1102
+ const useFull = secureRandom() < 0.2;
1103
+ if (!useFull) {
1104
+ return `gl-node/${glNodeVersion}`;
1105
+ }
1106
+
1107
+ const gcclPool = ["0.2.0", "0.2.1"];
1108
+ const grpcPool = ["1.13.4", "1.13.3", "1.10.8"];
1109
+ const gccl = pickRandom(gcclPool);
1110
+ const grpc = pickRandom(grpcPool);
1111
+
1112
+ return `gl-node/${glNodeVersion} gccl/${gccl} gax/${gaxVersion} grpc/${grpc}`;
1113
+ }
1114
+
1115
+ /**
1116
+ * Hardened fixed Client-Metadata validated by onboarding/loadCodeAssist:
1117
+ * `ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI`
1118
+ */
1119
+ getClientMetadata(): string {
1120
+ return "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI";
1121
+ }
1122
+
1123
+ /**
1124
+ * Structured JSON Client-Metadata used by observer-style requests
1125
+ * (ideType ANTIGRAVITY + plugin/arch/node details). Falls back to a compact
1126
+ * key=value form and never throws.
1127
+ */
1128
+ private buildClientMetadata(pool?: PlatformPool): string {
1129
+ const p = pool ?? this.getPlatformPool();
1130
+ try {
1131
+ const nodeVer = process.version;
1132
+ const payload = {
1133
+ ideType: "ANTIGRAVITY",
1134
+ ideVersion: this.version,
1135
+ platform: p.os.toUpperCase(),
1136
+ arch: p.arch.toUpperCase(),
1137
+ pluginVersion: PLUGIN_VERSION_FALLBACK,
1138
+ nodeVersion: nodeVer,
1139
+ osDisplay: p.displayOs,
1140
+ };
1141
+ return JSON.stringify(payload);
1142
+ } catch {
1143
+ return `ideType=antigravity,platform=${p.os},arch=${p.arch},version=${this.version}`;
1144
+ }
1145
+ }
1146
+
1147
+ /**
1148
+ * Observer-style X-Goog-Api-Client:
1149
+ * `gl-node/<node> antigravity/<version> gccl/1.0.0`. Never throws.
1150
+ */
1151
+ private buildApiClient(): string {
1152
+ try {
1153
+ const nodeVer = process.version.replace(/^v/, "");
1154
+ return `gl-node/${nodeVer} antigravity/${this.version} gccl/1.0.0`;
1155
+ } catch {
1156
+ return `antigravity/${this.version}`;
1157
+ }
1158
+ }
1159
+
1160
+ // --------------------------------------------------------------------------
1161
+ // Full headers - legacy Gemini CLI shape
1162
+ // --------------------------------------------------------------------------
1163
+
1164
+ /**
1165
+ * Complete Gemini CLI-compatible header set including coherent UA,
1166
+ * X-Goog-Api-Client jitter, hardened Client-Metadata and Content-Type.
1167
+ */
1168
+ getFullHeaders(
1169
+ model: string = UA_DEFAULT_MODEL,
1170
+ type: FingerprintType = "gemini-cli",
1171
+ platformOverride?: PlatformProfile,
1172
+ ): FullHeadersResult {
1173
+ const plat = this.getPlatform(platformOverride);
1174
+ const versions = this.getCoherentVersionPair();
1175
+ const jitterMs = getJitterMs(0, 80);
1176
+
1177
+ const userAgent =
1178
+ type === "gemini-cli"
1179
+ ? this.getGeminiCLIUserAgent(model, plat)
1180
+ : this.getAntigravityUserAgent(versions.cliVersion, plat);
1181
+
1182
+ const xGoogClient = this.getXGoogApiClient();
1183
+ const clientMetadata = this.getClientMetadata();
1184
+ const xClientDetails = `session_id=${this.sessionId},platform=${plat.os}`;
1185
+
1186
+ return {
1187
+ "User-Agent": userAgent,
1188
+ "X-Goog-Api-Client": xGoogClient,
1189
+ "Client-Metadata": clientMetadata,
1190
+ "Content-Type": "application/json",
1191
+ Accept: "application/json",
1192
+ "X-Client-Details": xClientDetails,
1193
+ __meta: {
1194
+ platform: plat,
1195
+ cliVersion: versions.cliVersion,
1196
+ googleApiVersion: versions.googleApiVersion,
1197
+ glNodeVersion: versions.glNodeVersion,
1198
+ gaxVersion: versions.gaxVersion,
1199
+ sessionId: this.sessionId,
1200
+ model,
1201
+ type,
1202
+ jitterMs,
1203
+ },
1204
+ };
1205
+ }
1206
+
1207
+ /** Native Fetch API Headers instance ready for direct fetch() usage. */
1208
+ toFetchHeaders(model?: string, type?: FingerprintType): Headers {
1209
+ const full = this.getFullHeaders(model, type);
1210
+ const h = new Headers();
1211
+ h.set("User-Agent", full["User-Agent"]);
1212
+ h.set("X-Goog-Api-Client", full["X-Goog-Api-Client"]);
1213
+ h.set("Client-Metadata", full["Client-Metadata"]);
1214
+ h.set("Content-Type", full["Content-Type"]);
1215
+ h.set("Accept", full["Accept"]);
1216
+ if (full["X-Client-Details"]) h.set("X-Client-Details", full["X-Client-Details"]);
1217
+ return h;
1218
+ }
1219
+
1220
+ // --------------------------------------------------------------------------
1221
+ // Observer-style fingerprint headers
1222
+ // --------------------------------------------------------------------------
1223
+
1224
+ /**
1225
+ * Generates observer-style fingerprint headers.
1226
+ *
1227
+ * Enforced requirements:
1228
+ * - User-Agent: `antigravity/{version} {os}/{arch}` (coherent keyed pool)
1229
+ * - Client-Metadata: always included (structured JSON, AM observed)
1230
+ * - X-Goog-Api-Client ONLY when headerStyle === 'gemini-cli'
1231
+ * - Never includes X-Goog-QuotaUser nor X-Client-Device-Id
1232
+ *
1233
+ * @param headerStyle - 'antigravity' (default content path) or 'gemini-cli'
1234
+ */
1235
+ public getFingerprintHeaders(headerStyle: HeaderStyle = "antigravity"): Record<string, string> {
1236
+ if (headerStyle !== "antigravity" && headerStyle !== "gemini-cli") {
1237
+ throw new TypeError(`headerStyle must be 'antigravity' or 'gemini-cli', got ${String(headerStyle)}`);
1238
+ }
1239
+
1240
+ const pool = this.getPlatformPool();
1241
+
1242
+ // Coherence-critical: outdated versions triggered backend bans.
1243
+ const userAgent = `antigravity/${this.version} ${pool.os}/${pool.arch}`;
1244
+
1245
+ const headers: Record<string, string> = {
1246
+ "User-Agent": userAgent,
1247
+ "Content-Type": "application/json",
1248
+ "Client-Metadata": this.buildClientMetadata(pool),
1249
+ Accept: "application/json",
1250
+ };
1251
+
1252
+ if (headerStyle === "gemini-cli") {
1253
+ headers["X-Goog-Api-Client"] = this.buildApiClient();
1254
+ }
1255
+
1256
+ return headers;
1257
+ }
1258
+
1259
+ /** Antigravity version currently embedded in observer fingerprints. */
1260
+ public getVersion(): string {
1261
+ return this.version;
1262
+ }
1263
+
1264
+ // --------------------------------------------------------------------------
1265
+ // Accessors
1266
+ // --------------------------------------------------------------------------
1267
+
1268
+ /** Deterministic session_id of this instance. */
1269
+ getSessionId(): string {
1270
+ return this.sessionId;
1271
+ }
1272
+
1273
+ /** Full OAuth config for the auth flow. */
1274
+ getOAuthConfig(): {
1275
+ clientId: string;
1276
+ clientSecret: string;
1277
+ scopes: readonly string[];
1278
+ projectFallback: string;
1279
+ } {
1280
+ return {
1281
+ clientId: OAUTH_CLIENT_ID,
1282
+ clientSecret: OAUTH_CLIENT_SECRET,
1283
+ scopes: GEMINI_CLI_SCOPES,
1284
+ projectFallback: PROJECT_FALLBACK,
1285
+ };
1286
+ }
1287
+
1288
+ /** Endpoint map (v2.1.15 Phase D: derives from the constants.js parts). */
1289
+ getEndpoints(): typeof FINGERPRINT_API_PATHS {
1290
+ return FINGERPRINT_API_PATHS;
1291
+ }
1292
+ }
1293
+
1294
+ // ============================================================================
1295
+ // Section: Standalone exported API (short blind helpers)
1296
+ // ============================================================================
1297
+
1298
+ /** Lazy singleton bound to the current cwd; stable session_id per process. */
1299
+ let cachedgenerator: FingerprintGenerator | null = null;
1300
+
1301
+ function getDefaultInstance(): FingerprintGenerator {
1302
+ if (!cachedgenerator) {
1303
+ cachedgenerator = new FingerprintGenerator();
1304
+ }
1305
+ return cachedgenerator;
1306
+ }
1307
+
1308
+ /**
1309
+ * Default shared generator using ANTIGRAVITY_VERSION_FALLBACK until
1310
+ * version.ts resolves the remote version. Consumers may create their own
1311
+ * instances with a dynamic version. The grand merge folded this module into
1312
+ * the shared core graph the platform-portable bundles ship, so the singleton
1313
+ * constructs on first use (the constructor resolves the cwd through the node
1314
+ * path builtin): under node and bun the behavior is identical while a stubbed
1315
+ * browser bundle never instantiates the generator at import time.
1316
+ */
1317
+ export const defaultFingerprintGenerator: FingerprintGenerator = new Proxy({} as FingerprintGenerator, {
1318
+ get(_target: FingerprintGenerator, property: string | symbol): unknown {
1319
+ if (!cachedgenerator) {
1320
+ cachedgenerator = new FingerprintGenerator(ANTIGRAVITY_VERSION_FALLBACK);
1321
+ }
1322
+ const value = Reflect.get(cachedgenerator, property, cachedgenerator) as unknown;
1323
+ return typeof value === "function" ? (value as (...args: unknown[]) => unknown).bind(cachedgenerator) : value;
1324
+ },
1325
+ });
1326
+
1327
+ /**
1328
+ * Convenience wrapper matching the original plugin export names. Uses the
1329
+ * default singleton, or a temporary generator when a version is supplied.
1330
+ *
1331
+ * @param style - 'antigravity' (default) or 'gemini-cli'
1332
+ * @param version - optional Antigravity version override
1333
+ */
1334
+ export function getFingerprintHeaders(style: HeaderStyle = "antigravity", version?: string): Record<string, string> {
1335
+ if (version) {
1336
+ return new FingerprintGenerator(version).getFingerprintHeaders(style);
1337
+ }
1338
+ return defaultFingerprintGenerator.getFingerprintHeaders(style);
1339
+ }
1340
+
1341
+ /**
1342
+ * Short helper generating Gemini CLI compatible headers for project bypass.
1343
+ *
1344
+ * @param model - model id (default gemini-3-pro-preview)
1345
+ * @param type - 'gemini-cli' or 'antigravity'
1346
+ * @param platform - profile object, "win32 x64" string, or plain OS string
1347
+ */
1348
+ export function generateHeaders(
1349
+ model: string = UA_DEFAULT_MODEL,
1350
+ type: FingerprintType = "gemini-cli",
1351
+ platform?: PlatformProfile | `${PlatformOS} ${PlatformArch}` | PlatformOS,
1352
+ ): Record<string, string> {
1353
+ const fp = getDefaultInstance();
1354
+
1355
+ let platformProfile: PlatformProfile | undefined;
1356
+ if (platform) {
1357
+ if (typeof platform === "object") {
1358
+ platformProfile = platform;
1359
+ } else if (typeof platform === "string") {
1360
+ // Short parser: "win32 x64" or "win32".
1361
+ const parts = platform.toLowerCase().split(/\s+/);
1362
+ const osPart = parts[0] as PlatformOS;
1363
+ const archPart = (parts[1] as PlatformArch) ?? (osPart === "darwin" ? "arm64" : "x64");
1364
+ const found = PLATFORM_PROFILE_POOL.find((p) => p.os === osPart && p.arch === archPart);
1365
+ if (found) platformProfile = found;
1366
+ }
1367
+ }
1368
+
1369
+ const full = fp.getFullHeaders(model, type, platformProfile);
1370
+ // Strip the internal __meta block - only real HTTP headers are returned.
1371
+ const { __meta, ...httpHeaders } = full;
1372
+ return httpHeaders as Record<string, string>;
1373
+ }
1374
+
1375
+ /** Variant returning a native Fetch API Headers instance (Node 20+). */
1376
+ export function generateFetchHeaders(
1377
+ model?: string,
1378
+ type?: FingerprintType,
1379
+ platform?: PlatformProfile | `${PlatformOS} ${PlatformArch}`,
1380
+ ): Headers {
1381
+ const record = generateHeaders(model, type, platform);
1382
+ const h = new Headers();
1383
+ for (const [k, v] of Object.entries(record)) {
1384
+ if (v) h.set(k, v);
1385
+ }
1386
+ return h;
1387
+ }
1388
+
1389
+ /**
1390
+ * Full fingerprint with metadata for debugging/tracing; validates bypasses
1391
+ * during development.
1392
+ */
1393
+ export function generateFullFingerprint(model?: string, type?: FingerprintType, directory?: string): FullHeadersResult {
1394
+ const fp = directory ? new FingerprintGenerator({ directory }) : getDefaultInstance();
1395
+ return fp.getFullHeaders(model, type);
1396
+ }
1397
+
1398
+ /**
1399
+ * Creates an instance bound to a specific directory (deterministic
1400
+ * session_id). Essential for multi-workspace setups.
1401
+ */
1402
+ export function createFingerprintForDirectory(directory: string): FingerprintGenerator {
1403
+ return new FingerprintGenerator({ directory });
1404
+ }
1405
+
1406
+ /**
1407
+ * Bypass helper producing headers plus the base body for loadCodeAssist,
1408
+ * including the blinded project fallback.
1409
+ */
1410
+ export function buildLoadCodeAssistRequest(
1411
+ projectId: string = PROJECT_FALLBACK,
1412
+ model: string = UA_DEFAULT_MODEL,
1413
+ ): { url: string; method: "POST"; headers: Record<string, string>; body: string; sessionId: string } {
1414
+ const fp = getDefaultInstance();
1415
+ const headersRec = generateHeaders(model, "gemini-cli");
1416
+ const sessionId = fp.getSessionId();
1417
+
1418
+ return {
1419
+ url: FINGERPRINT_API_PATHS.loadCodeAssist,
1420
+ method: "POST",
1421
+ headers: headersRec,
1422
+ body: JSON.stringify({
1423
+ cloudaicompanionProject: projectId,
1424
+ metadata: {
1425
+ ideType: "IDE_UNSPECIFIED",
1426
+ platform: "PLATFORM_UNSPECIFIED",
1427
+ pluginType: "GEMINI",
1428
+ },
1429
+ session_id: sessionId,
1430
+ }),
1431
+ sessionId,
1432
+ };
1433
+ }
1434
+
1435
+ // ============================================================================
1436
+ // Section: Re-export barrel for plugin consumption
1437
+ // ============================================================================
1438
+
1439
+ export const FingerprintConstants = {
1440
+ OAUTH_CLIENT_ID,
1441
+ OAUTH_CLIENT_SECRET,
1442
+ GEMINI_CLI_SCOPES,
1443
+ PROJECT_FALLBACK,
1444
+ CLOUDCODE_BASE_URL,
1445
+ /** Lazy through the models.js import cycle: the aggregate reads the owner at access time, so every module evaluation order stays legal (the models → core → config → fingerprint cycle of the merged graph never evaluates this read during import). */
1446
+ get MODELS_2026() {
1447
+ return MODELS_2026;
1448
+ },
1449
+ PLATFORM_PROFILE_POOL,
1450
+ PLATFORM_POOLS,
1451
+ GEMINI_CLI_VERSION_POOL,
1452
+ GOOGLE_API_CLIENT_VERSION_POOL,
1453
+ GL_NODE_VERSION_POOL,
1454
+ ANTIGRAVITY_VERSION_FALLBACK,
1455
+ PLUGIN_VERSION_FALLBACK,
1456
+ } as const;
1457
+
1458
+ export default FingerprintGenerator;