@stage-labs/metro 0.1.0-beta.23 → 0.1.0-beta.231

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 (457) hide show
  1. package/dist/api.js +9 -101
  2. package/dist/background.js +123 -0
  3. package/dist/claude-settings.js +40 -0
  4. package/dist/claude.js +197 -0
  5. package/dist/cli.js +52 -110
  6. package/dist/control.js +19 -103
  7. package/dist/hold.js +210 -0
  8. package/dist/local.js +43 -0
  9. package/dist/mcp-config.js +25 -0
  10. package/dist/memory.js +222 -0
  11. package/dist/node-name.js +71 -0
  12. package/dist/onboarding.js +44 -0
  13. package/dist/provider-flags.js +9 -0
  14. package/dist/route.js +58 -0
  15. package/dist/runtime-install.js +96 -0
  16. package/dist/runtime.js +29 -52
  17. package/dist/serve.js +157 -0
  18. package/dist/service.js +304 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +13 -1
  21. package/package.json +2 -23
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/MEMORY.md +78 -0
  26. package/runtime/marketplace/plugin/METRO.md +26 -0
  27. package/runtime/marketplace/plugin/STAGE.md +42 -0
  28. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  29. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  30. package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
  31. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  32. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  33. package/runtime/node_modules/@metro-labs/core/package.json +51 -0
  34. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  35. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/events.ts +11 -7
  36. package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +22 -26
  39. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
  40. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -0
  41. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +14 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +83 -0
  44. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
  45. package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +72 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/mail-trust.ts +29 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +212 -0
  48. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
  49. package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +126 -0
  50. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  51. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  52. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/station-runtime.ts +6 -11
  53. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  54. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +84 -0
  55. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
  56. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  58. package/runtime/node_modules/@metro-labs/daemon/package.json +49 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/crontab.ts +44 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +89 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +80 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +144 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +43 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +142 -0
  82. package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
  83. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +133 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  85. package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
  86. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +150 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +223 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +36 -0
  93. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
  94. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
  96. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  97. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +75 -45
  98. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
  99. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
  100. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
  101. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +380 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory-routine.ts +114 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +32 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +295 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +346 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/claude/usage-probe.ts +127 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  115. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
  116. package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
  117. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  119. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
  120. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
  121. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
  122. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
  123. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +347 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-api.ts +116 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-crypto.ts +44 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-store.ts +48 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  134. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
  135. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
  136. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  137. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
  138. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
  139. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +214 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +209 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +155 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +93 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +307 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +235 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +203 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +285 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-lists.ts +57 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +161 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +72 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +104 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +118 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +35 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/gateway/tool-search.ts +126 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage-refresh.ts +102 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +348 -0
  172. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
  173. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  174. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
  175. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
  176. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
  177. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
  178. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +34 -80
  179. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  180. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
  181. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +88 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +82 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  186. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  187. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  188. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  189. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +71 -0
  190. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +47 -44
  191. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +93 -0
  192. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  193. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
  194. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +64 -113
  195. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
  196. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
  198. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  199. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  200. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  201. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  202. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  203. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  204. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/routes}/http.ts +63 -51
  205. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +209 -0
  206. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +69 -0
  207. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  208. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  209. package/runtime/node_modules/@metro-labs/daemon/src/server/cloudwatch.ts +178 -0
  210. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  211. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  212. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  213. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  214. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  215. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-browser.ts +102 -0
  216. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  217. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
  218. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +5 -31
  219. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
  220. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
  221. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
  222. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +69 -3
  223. package/runtime/node_modules/@metro-labs/{mcp/src/db → daemon/src/stations}/materialize.ts +60 -179
  224. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +20 -2
  225. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  226. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
  227. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
  228. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  229. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
  230. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  231. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  232. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  233. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  234. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  235. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  236. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +9 -0
  237. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  238. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  239. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  240. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +147 -0
  241. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
  242. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
  243. package/runtime/node_modules/@metro-labs/discord-bot/package.json +2 -4
  244. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +3 -23
  245. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +19 -187
  246. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +2 -2
  247. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +26 -67
  248. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +1 -1
  249. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +2 -2
  250. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +8 -14
  251. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +2 -2
  252. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  253. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +12 -13
  254. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +5 -1
  255. package/runtime/node_modules/@metro-labs/gmail/package.json +25 -0
  256. package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +81 -0
  257. package/runtime/node_modules/@metro-labs/gmail/src/actions.ts +14 -0
  258. package/runtime/node_modules/@metro-labs/gmail/src/api.ts +31 -0
  259. package/runtime/node_modules/@metro-labs/gmail/src/attachments.ts +35 -0
  260. package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +105 -0
  261. package/runtime/node_modules/@metro-labs/gmail/src/config.ts +13 -0
  262. package/runtime/node_modules/@metro-labs/gmail/src/inbound.ts +96 -0
  263. package/runtime/node_modules/@metro-labs/gmail/src/index.ts +12 -0
  264. package/runtime/node_modules/@metro-labs/gmail/src/login.ts +37 -0
  265. package/runtime/node_modules/@metro-labs/gmail/src/message.ts +125 -0
  266. package/runtime/node_modules/@metro-labs/gmail/src/mime.ts +70 -0
  267. package/runtime/node_modules/@metro-labs/gmail/src/outbound.ts +124 -0
  268. package/runtime/node_modules/@metro-labs/gmail/src/query.ts +99 -0
  269. package/runtime/node_modules/@metro-labs/gmail/src/state.ts +24 -0
  270. package/runtime/node_modules/@metro-labs/gmail/src/station.ts +15 -0
  271. package/runtime/node_modules/@metro-labs/gmail/src/trust.ts +42 -0
  272. package/runtime/node_modules/@metro-labs/http/package.json +30 -0
  273. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
  274. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
  275. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  276. package/runtime/node_modules/@metro-labs/http/src/sigv4.ts +90 -0
  277. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  278. package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
  279. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +78 -0
  280. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
  281. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +81 -0
  282. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +120 -0
  283. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +42 -0
  284. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +23 -0
  285. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +59 -0
  286. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +55 -0
  287. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +96 -0
  288. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  289. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +129 -0
  290. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +31 -0
  291. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +103 -0
  292. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +84 -0
  293. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +28 -0
  294. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  295. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +41 -0
  296. package/runtime/node_modules/@metro-labs/telegram/package.json +1 -3
  297. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +5 -24
  298. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +8 -6
  299. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
  300. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +2 -2
  301. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +19 -34
  302. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +3 -3
  303. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +13 -34
  304. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +4 -43
  305. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +2 -3
  306. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +1 -1
  307. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  308. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +2 -2
  309. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +4 -1
  310. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +0 -1
  311. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +1 -1
  312. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +7 -41
  313. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +22 -77
  314. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +2 -2
  315. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +46 -48
  316. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +15 -15
  317. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +6 -82
  318. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -1
  319. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
  320. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +5 -1
  321. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +20 -12
  322. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  323. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +96 -0
  324. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -0
  325. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  326. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  327. package/runtime/node_modules/@metro-labs/threema/src/files.ts +47 -0
  328. package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -0
  329. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  330. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  331. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +87 -0
  332. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  333. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  334. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  335. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  336. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  337. package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
  338. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
  339. package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
  340. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
  341. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
  342. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
  343. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
  344. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
  345. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
  346. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
  347. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
  348. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
  349. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
  350. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  351. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
  352. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
  353. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
  354. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  355. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
  356. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
  357. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
  358. package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
  359. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
  360. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
  361. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
  362. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +40 -53
  363. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +72 -220
  364. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
  365. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +27 -0
  366. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +11 -7
  367. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
  368. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
  369. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
  370. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
  371. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
  372. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
  373. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +57 -33
  374. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
  375. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  376. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  377. package/runtime/node_modules/@metro-labs/xmtp/src/resolve.ts +44 -0
  378. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  379. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  380. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +7 -2
  381. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +22 -58
  382. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +55 -48
  383. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
  384. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
  385. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
  386. package/runtime/runtime.json +1 -1
  387. package/runtime/server.ts +1 -0
  388. package/runtime/stations.json +38 -0
  389. package/dist/prompt.js +0 -61
  390. package/dist/store.js +0 -70
  391. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +0 -195
  392. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +0 -292
  393. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +0 -197
  394. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +0 -5
  395. package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
  396. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
  397. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
  398. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -341
  399. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
  400. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -320
  401. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
  402. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
  403. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
  404. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
  405. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
  406. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
  407. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
  408. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  409. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
  410. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
  411. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
  412. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
  413. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
  414. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
  415. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
  416. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
  417. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
  418. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
  419. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
  420. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
  421. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
  422. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +0 -71
  423. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
  424. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
  425. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
  426. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
  427. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
  428. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
  429. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
  430. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
  431. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
  432. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
  433. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
  434. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
  435. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
  436. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
  437. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
  438. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
  439. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
  440. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
  441. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
  442. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
  443. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +0 -22
  444. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -1
  445. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -3
  446. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
  447. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
  448. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
  449. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
  450. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
  451. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
  452. /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
  453. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
  454. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
  455. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
  456. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
  457. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
@@ -0,0 +1,307 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { agentIdForKey } from '../agents/keys.js';
4
+ import {
5
+ assertBedrockReady,
6
+ bedrockBase,
7
+ bedrockCount,
8
+ bedrockMessages,
9
+ freshAdaptations,
10
+ type Adaptations,
11
+ } from './bedrock.js';
12
+ import { addBeta, anthropicHeaders, forwardedHeaders, GatewayError, parseJson, pipeResponse, readBody, sendError, upstreamMessage, watchUpstream } from './forward.js';
13
+ import {
14
+ BINDING_BETA,
15
+ cappedEffort,
16
+ effortToApply,
17
+ forgetLearnedThinking,
18
+ learnAlwaysThinks,
19
+ plannedEffort,
20
+ thinkingOff,
21
+ withBlockBinding,
22
+ withEffort,
23
+ withThinkingFor,
24
+ } from './effort.js';
25
+ import { notReady, PASSTHROUGH_ID, readModelConfig, resolveRoute, routeLabel, setCodexAuth, setGeminiAuth, writeModelConfig, type Connection, type ModelConfig, type Route } from './model-config.js';
26
+ import { codexMessages, sharedCodexState } from './codex.js';
27
+ import { geminiMessages, sharedGeminiState } from './gemini.js';
28
+ import { countTokens } from './subscription.js';
29
+ import type { GeminiDeps } from './gemini.js';
30
+ import type { CodexDeps } from './codex.js';
31
+ import type { GeminiTokens } from './gemini-auth.js';
32
+ import { OPENROUTER_BASE } from './openrouter.js';
33
+ import { isRecord } from '@metro-labs/core/is-record';
34
+ import { forgetServed, noteServed } from './served.js';
35
+ import { forgetUsage, noteUsageHeaders, UsageScanner } from './usage.js';
36
+ import { fitToolSearch } from './tool-search.js';
37
+ import { ANTHROPIC_API, refreshLoginModels } from './provider-models.js';
38
+ import type { CodexTokens } from './codex-auth.js';
39
+
40
+ const GATEWAY_PREFIX = '/gateway';
41
+ const MESSAGES = '/v1/messages';
42
+ const COUNT = '/v1/messages/count_tokens';
43
+ const MODELS = '/v1/models';
44
+ const HELLO = '/api/hello';
45
+
46
+ export interface GatewayDeps {
47
+ config: () => ModelConfig;
48
+ identify?: (key: string) => boolean;
49
+ anthropicBase?: string;
50
+ bedrockBase?: string;
51
+ openrouterBase?: string;
52
+ codex?: Partial<CodexDeps>;
53
+ gemini?: Partial<GeminiDeps>;
54
+ }
55
+
56
+ const learned: Adaptations = freshAdaptations();
57
+
58
+ export function resetGatewayState(): void {
59
+ forgetServed();
60
+ forgetUsage();
61
+ learned.fields.clear();
62
+ learned.dropBetas = false;
63
+ forgetLearnedThinking();
64
+ sharedCodexState.clear();
65
+ sharedGeminiState.clear();
66
+ }
67
+
68
+ const saveCodexTokens = (id: string, tokens: CodexTokens): void => {
69
+ writeModelConfig(setCodexAuth(readModelConfig(), id, tokens));
70
+ };
71
+
72
+ const saveGeminiTokens = (id: string, tokens: GeminiTokens): void => {
73
+ writeModelConfig(setGeminiAuth(readModelConfig(), id, tokens));
74
+ };
75
+
76
+ const keyOf = (req: IncomingMessage): string => {
77
+ const raw = req.headers['x-metro-key'];
78
+ return (Array.isArray(raw) ? raw[0] : raw)?.trim() ?? '';
79
+ };
80
+
81
+ const defaultIdentify = (key: string): boolean => key !== '' && agentIdForKey(key) !== undefined;
82
+
83
+ function modelsBody(cfg: ModelConfig): Record<string, unknown> {
84
+ const data = cfg.connections
85
+ .filter((c) => c.provider !== 'anthropic' && c.model !== '')
86
+ .map((c) => ({ id: `${c.provider}:${c.model}`, display_name: `${c.label} · ${c.model}`, description: 'Through metro' }));
87
+ return { data, has_more: false, first_id: data[0]?.id ?? null, last_id: data.at(-1)?.id ?? null };
88
+ }
89
+
90
+ const PASSTHROUGH: Connection = { id: PASSTHROUGH_ID, provider: 'anthropic', label: 'Claude Code login', model: '', apiKey: '', region: '', zdr: false, codex: null, gemini: null };
91
+
92
+ const passthrough = (body: Record<string, unknown>): Route => ({ connection: PASSTHROUGH, model: requestedModel(body) });
93
+
94
+ function requestedModel(body: Record<string, unknown>): string {
95
+ const model = body.model;
96
+ if (typeof model !== 'string' || model === '') throw new GatewayError(400, 'invalid_request_error', 'model is required');
97
+ return model;
98
+ }
99
+
100
+ const standsInFor = (req: IncomingMessage): boolean => {
101
+ const metroKey = req.headers['x-metro-key'];
102
+ return typeof metroKey === 'string' && metroKey !== '' && req.headers.authorization === `Bearer ${metroKey}`;
103
+ };
104
+
105
+ interface Payloads {
106
+ metro: Buffer;
107
+ asSent: Buffer | null;
108
+ bound: boolean;
109
+ }
110
+
111
+ function anthropicPayloads(raw: Buffer, sent: Record<string, unknown>, shaped: Record<string, unknown>, model: string): Payloads {
112
+ const runnable = withThinkingFor(sent, model);
113
+ const rewrite = runnable !== sent || (typeof sent.model === 'string' && sent.model !== model);
114
+ const asSent = rewrite ? Buffer.from(JSON.stringify({ ...runnable, model })) : raw;
115
+ const bound = withBlockBinding(withThinkingFor(shaped, model));
116
+ if (bound === sent) return { metro: asSent, asSent: null, bound: false };
117
+ return { metro: Buffer.from(JSON.stringify(rewrite ? { ...bound, model } : bound)), asSent, bound: bound !== shaped };
118
+ }
119
+
120
+ type Send = (payload: Buffer, headers: Record<string, string>) => Promise<Response>;
121
+
122
+ async function asAnthropicWants(send: Send, base: Record<string, string>, payloads: Payloads, connection: string, model: string): Promise<Response> {
123
+ const { metro, asSent, bound } = payloads;
124
+ const upstream = await send(metro, bound ? addBeta(base, BINDING_BETA) : base);
125
+ if (upstream.status !== 400 || asSent === null) return upstream;
126
+ await upstream.body?.cancel();
127
+ log.warn({ connection, model }, 'gateway: Anthropic refused the request metro shaped, so it was sent again as Claude Code wrote it');
128
+ return send(asSent, base);
129
+ }
130
+
131
+ async function refusedThinkingOff(upstream: Response, shaped: Record<string, unknown>, model: string): Promise<boolean> {
132
+ if (upstream.status !== 400 || !thinkingOff(shaped)) return false;
133
+ if (!learnAlwaysThinks(model, upstreamMessage(await upstream.clone().text(), ''))) return false;
134
+ await upstream.body?.cancel();
135
+ log.info({ model }, 'gateway: this model cannot run with thinking off, so metro leaves that setting out for it from now on');
136
+ return true;
137
+ }
138
+
139
+ function listLoginModels(req: IncomingMessage, anthropicBase: string): void {
140
+ refreshLoginModels(req.headers, anthropicBase).catch((err: unknown) => {
141
+ log.warn({ err: errMsg(err) }, 'gateway: could not list the models of the Claude Code login');
142
+ });
143
+ }
144
+
145
+ async function toAnthropic(
146
+ req: IncomingMessage,
147
+ res: ServerResponse,
148
+ raw: Buffer,
149
+ sent: Record<string, unknown>,
150
+ shaped: Record<string, unknown>,
151
+ route: Route,
152
+ deps: GatewayDeps,
153
+ ): Promise<void> {
154
+ const conn = route.connection;
155
+ const anthropicBase = deps.anthropicBase ?? ANTHROPIC_API;
156
+ const url = `${anthropicBase}${(req.url ?? '').slice(GATEWAY_PREFIX.length)}`;
157
+ const key = conn.apiKey;
158
+ if (key === '' && standsInFor(req))
159
+ throw new GatewayError(
160
+ 403,
161
+ 'permission_error',
162
+ 'Claude Code on this machine has no Anthropic login of its own; choose Bedrock, OpenRouter, Codex or Gemini on the Model page, or sign in on the Claude tab',
163
+ );
164
+ const watch = watchUpstream(res);
165
+ if (key === '') listLoginModels(req, anthropicBase);
166
+ const base = key === '' ? forwardedHeaders(req) : anthropicHeaders(req, key);
167
+ const send = (payload: Buffer, headers: Record<string, string>): Promise<Response> =>
168
+ fetch(url, { method: 'POST', headers, body: new Uint8Array(payload), signal: watch.signal, redirect: 'manual' });
169
+ const attempt = (): Promise<Response> => asAnthropicWants(send, base, anthropicPayloads(raw, sent, shaped, route.model), conn.label, route.model);
170
+ const first = await attempt();
171
+ const upstream = (await refusedThinkingOff(first, shaped, route.model)) ? await attempt() : first;
172
+ noteRefusal(conn.label, route.model, upstream);
173
+ noteUsageHeaders('anthropic', conn.id, upstream.headers);
174
+ const scanner = new UsageScanner(conn.id);
175
+ await pipeResponse(upstream, res, watch, key === '' ? { scanner } : { ownCredential: true, scanner });
176
+ }
177
+
178
+ async function toOpenRouter(
179
+ req: IncomingMessage,
180
+ res: ServerResponse,
181
+ body: Record<string, unknown>,
182
+ route: Route,
183
+ cfg: ModelConfig,
184
+ deps: GatewayDeps,
185
+ ): Promise<void> {
186
+ const conn = route.connection;
187
+ const reason = notReady(cfg, conn);
188
+ if (reason !== null) throw new GatewayError(400, 'invalid_request_error', reason);
189
+ const headers: Record<string, string> = {
190
+ 'content-type': 'application/json',
191
+ accept: req.headers.accept ?? 'application/json',
192
+ authorization: `Bearer ${conn.apiKey}`,
193
+ 'anthropic-version': typeof req.headers['anthropic-version'] === 'string' ? req.headers['anthropic-version'] : '2023-06-01',
194
+ 'http-referer': 'https://metro.box',
195
+ 'x-title': 'metro',
196
+ };
197
+ const beta = req.headers['anthropic-beta'];
198
+ if (typeof beta === 'string' && beta !== '') headers['anthropic-beta'] = beta;
199
+ const watch = watchUpstream(res);
200
+ const upstream = await fetch(`${deps.openrouterBase ?? OPENROUTER_BASE}${MESSAGES}`, {
201
+ method: 'POST',
202
+ headers,
203
+ body: JSON.stringify(openrouterBody(body, route.model, conn.zdr)),
204
+ signal: watch.signal,
205
+ redirect: 'manual',
206
+ });
207
+ noteRefusal(conn.label, route.model, upstream);
208
+ await pipeResponse(upstream, res, watch, { keepalive: true, ownCredential: true, scanner: new UsageScanner(conn.id) });
209
+ }
210
+
211
+ function openrouterBody(body: Record<string, unknown>, model: string, zdr: boolean): Record<string, unknown> {
212
+ const sent: Record<string, unknown> = { ...body, model };
213
+ if (thinkingOff(body)) delete sent.thinking;
214
+ const effort = effortToApply(body);
215
+ if (effort !== null) sent.reasoning = { ...(isRecord(body.reasoning) ? body.reasoning : {}), effort: cappedEffort(effort) };
216
+ if (!zdr) return sent;
217
+ const provider = isRecord(body.provider) ? body.provider : {};
218
+ return { ...sent, provider: { ...provider, zdr: true } };
219
+ }
220
+
221
+ function noteRefusal(connection: string, model: string, upstream: Response): void {
222
+ if (!upstream.ok) log.warn({ connection, model, status: upstream.status }, 'gateway: the provider refused the request');
223
+ }
224
+
225
+ async function toSubscription(req: IncomingMessage, res: ServerResponse, path: string, body: Record<string, unknown>, route: Route, deps: GatewayDeps): Promise<void> {
226
+ const conn = route.connection;
227
+ if (path === COUNT) countTokens(res, body);
228
+ else if (conn.provider === 'gemini') await geminiMessages(req, res, body, route.model, conn, { save: saveGeminiTokens, ...deps.gemini }, sharedGeminiState, watchUpstream(res));
229
+ else await codexMessages(req, res, body, route.model, conn, { save: saveCodexTokens, ...deps.codex }, sharedCodexState, watchUpstream(res));
230
+ }
231
+
232
+ function shapedFor(req: IncomingMessage, sent: Record<string, unknown>): Record<string, unknown> {
233
+ const effort = plannedEffort(req, sent);
234
+ return effort === null ? sent : withEffort(sent, effort);
235
+ }
236
+
237
+ const bytesOf = (raw: Buffer, parsed: Record<string, unknown>, sent: Record<string, unknown>): Buffer => (sent === parsed ? raw : Buffer.from(JSON.stringify(sent)));
238
+
239
+ async function dispatch(req: IncomingMessage, res: ServerResponse, path: string, deps: GatewayDeps): Promise<void> {
240
+ const cfg = deps.config();
241
+ const raw = await readBody(req);
242
+ const parsed = parseJson(raw);
243
+ const sent = fitToolSearch(req, parsed);
244
+ const route = resolveRoute(requestedModel(sent), cfg) ?? passthrough(sent);
245
+ const body = shapedFor(req, sent);
246
+ const conn = route.connection;
247
+ log.info({ route: routeLabel(route), connection: conn.label, path }, 'gateway: routing');
248
+ if (path === MESSAGES) noteServed({ connection: conn.id, provider: conn.provider, model: route.model, at: new Date().toISOString() });
249
+ if (conn.provider === 'bedrock') {
250
+ assertBedrockReady(conn);
251
+ const up = { settings: conn, base: deps.bedrockBase ?? bedrockBase(conn.region), learned, watch: watchUpstream(res) };
252
+ if (path === COUNT) await bedrockCount(req, res, body, route.model, up);
253
+ else await bedrockMessages(req, res, body, route.model, up);
254
+ return;
255
+ }
256
+ if (conn.provider === 'openrouter') {
257
+ if (path === COUNT) throw new GatewayError(404, 'not_found_error', 'OpenRouter does not count tokens');
258
+ await toOpenRouter(req, res, body, route, cfg, deps);
259
+ return;
260
+ }
261
+ if (conn.provider === 'codex' || conn.provider === 'gemini') {
262
+ await toSubscription(req, res, path, body, route, deps);
263
+ return;
264
+ }
265
+ await toAnthropic(req, res, bytesOf(raw, parsed, sent), sent, body, route, deps);
266
+ }
267
+
268
+ function failed(res: ServerResponse, err: unknown): void {
269
+ if (res.headersSent) {
270
+ res.end();
271
+ return;
272
+ }
273
+ if (err instanceof GatewayError) {
274
+ sendError(res, err.status, err.kind, err.message);
275
+ return;
276
+ }
277
+ log.warn({ err: errMsg(err) }, 'gateway: request failed');
278
+ sendError(res, 502, 'api_error', `metro gateway: ${errMsg(err)}`);
279
+ }
280
+
281
+ function answer(req: IncomingMessage, res: ServerResponse, path: string, deps: GatewayDeps): void {
282
+ if (req.method === 'GET' && path === MODELS) {
283
+ res.writeHead(200, { 'content-type': 'application/json' });
284
+ res.end(JSON.stringify(modelsBody(deps.config())));
285
+ return;
286
+ }
287
+ if (req.method === 'POST' && (path === MESSAGES || path === COUNT)) {
288
+ dispatch(req, res, path, deps).catch((err: unknown) => {
289
+ failed(res, err);
290
+ });
291
+ return;
292
+ }
293
+ sendError(res, 404, 'not_found_error', 'not found');
294
+ }
295
+
296
+ export function handleGatewayRequest(req: IncomingMessage, res: ServerResponse, deps: GatewayDeps): boolean {
297
+ const full = req.url ?? '';
298
+ if (full !== GATEWAY_PREFIX && !full.startsWith(`${GATEWAY_PREFIX}/`)) return false;
299
+ const path = (full.split('?')[0] ?? '').slice(GATEWAY_PREFIX.length);
300
+ if (req.method === 'HEAD' && path === HELLO) {
301
+ res.writeHead(200).end();
302
+ return true;
303
+ }
304
+ if ((deps.identify ?? defaultIdentify)(keyOf(req))) answer(req, res, path, deps);
305
+ else sendError(res, 401, 'authentication_error', 'metro gateway: no agent key (x-metro-key); start Claude Code with metro claude');
306
+ return true;
307
+ }
@@ -0,0 +1,160 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { PendingLogins } from './pkce.js';
3
+ import { nonEmpty } from './text.js';
4
+
5
+ const GEMINI_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
6
+ const GEMINI_CLIENT_SECRET = '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';
7
+ const GEMINI_AUTH_BASE = 'https://accounts.google.com';
8
+ const GEMINI_TOKEN_BASE = 'https://oauth2.googleapis.com';
9
+ const GEMINI_REDIRECT = 'http://localhost:51121/oauth-callback';
10
+ const SCOPE = [
11
+ 'https://www.googleapis.com/auth/cloud-platform',
12
+ 'https://www.googleapis.com/auth/userinfo.email',
13
+ 'https://www.googleapis.com/auth/userinfo.profile',
14
+ 'https://www.googleapis.com/auth/cclog',
15
+ 'https://www.googleapis.com/auth/experimentsandconfigs',
16
+ ].join(' ');
17
+ const EXPIRY_MARGIN_MS = 5 * 60_000;
18
+ const DEFAULT_TTL_MS = 55 * 60_000;
19
+
20
+ export interface GeminiTokens {
21
+ accessToken: string;
22
+ refreshToken: string;
23
+ expiresAt: number;
24
+ email: string | null;
25
+ project: string;
26
+ tier: string | null;
27
+ savedAt: string;
28
+ }
29
+
30
+ export class GeminiAuthError extends Error {}
31
+
32
+ const pending = new PendingLogins(16);
33
+
34
+ export function authorizeUrl(state: string, challenge: string, base = GEMINI_AUTH_BASE): string {
35
+ const params = new URLSearchParams({
36
+ client_id: GEMINI_CLIENT_ID,
37
+ redirect_uri: GEMINI_REDIRECT,
38
+ response_type: 'code',
39
+ scope: SCOPE,
40
+ access_type: 'offline',
41
+ prompt: 'consent',
42
+ code_challenge: challenge,
43
+ code_challenge_method: 'S256',
44
+ state,
45
+ });
46
+ return `${base}/o/oauth2/v2/auth?${params.toString()}`;
47
+ }
48
+
49
+ export function beginLogin(base = GEMINI_AUTH_BASE, now = Date.now()): { url: string; state: string } {
50
+ const { state, challenge } = pending.begin(now);
51
+ return { url: authorizeUrl(state, challenge, base), state };
52
+ }
53
+
54
+
55
+ const refusalIn = (body: Record<string, unknown>): string => nonEmpty(body.error_description) ?? nonEmpty(body.error) ?? 'Google answered without an access token';
56
+
57
+ function grantedTokens(body: unknown, previous: GeminiTokens | null): { accessToken: string; refreshToken: string; ttl: number } {
58
+ if (!isRecord(body)) throw new GeminiAuthError('Google answered without tokens');
59
+ const accessToken = nonEmpty(body.access_token);
60
+ if (accessToken === null) throw new GeminiAuthError(refusalIn(body));
61
+ const refreshToken = nonEmpty(body.refresh_token) ?? previous?.refreshToken ?? '';
62
+ if (refreshToken === '') throw new GeminiAuthError('Google issued no refresh token; sign in again');
63
+ return { accessToken, refreshToken, ttl: typeof body.expires_in === 'number' ? body.expires_in * 1000 : DEFAULT_TTL_MS };
64
+ }
65
+
66
+ function tokensOf(body: unknown, previous: GeminiTokens | null, now: number): GeminiTokens {
67
+ const granted = grantedTokens(body, previous);
68
+ return {
69
+ accessToken: granted.accessToken,
70
+ refreshToken: granted.refreshToken,
71
+ expiresAt: now + granted.ttl,
72
+ email: previous?.email ?? null,
73
+ project: previous?.project ?? '',
74
+ tier: previous?.tier ?? null,
75
+ savedAt: new Date(now).toISOString(),
76
+ };
77
+ }
78
+
79
+ async function tokenCall(form: Record<string, string>, base: string, fetchImpl: typeof fetch): Promise<unknown> {
80
+ let res: Response;
81
+ try {
82
+ res = await fetchImpl(`${base}/token`, {
83
+ method: 'POST',
84
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
85
+ body: new URLSearchParams(form).toString(),
86
+ signal: AbortSignal.timeout(30_000),
87
+ });
88
+ } catch (err) {
89
+ throw new GeminiAuthError(`could not reach Google: ${err instanceof Error ? err.message : String(err)}`);
90
+ }
91
+ const body: unknown = await res.json().catch(() => null);
92
+ if (!res.ok) throw new GeminiAuthError(isRecord(body) ? (nonEmpty(body.error_description) ?? nonEmpty(body.error) ?? `Google answered ${String(res.status)}`) : `Google answered ${String(res.status)}`);
93
+ return body;
94
+ }
95
+
96
+ function codeIn(pasted: string, state: string): string {
97
+ const trimmed = pasted.trim();
98
+ if (trimmed === '') throw new GeminiAuthError('paste the address the browser landed on, or the code in it');
99
+ if (!/^https?:\/\//i.test(trimmed)) return trimmed;
100
+ let url: URL;
101
+ try {
102
+ url = new URL(trimmed);
103
+ } catch {
104
+ throw new GeminiAuthError('that is not a full address');
105
+ }
106
+ const code = url.searchParams.get('code') ?? '';
107
+ const carried = url.searchParams.get('state');
108
+ if (code === '') throw new GeminiAuthError(`that address carries no code${url.searchParams.get('error') === null ? '' : ` (Google said ${url.searchParams.get('error') ?? ''})`}`);
109
+ if (carried !== null && carried !== state) throw new GeminiAuthError('that address belongs to another sign-in; start again and paste the new one');
110
+ return code;
111
+ }
112
+
113
+ export async function exchangeCode(pasted: string, state: string, base = GEMINI_TOKEN_BASE, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<GeminiTokens> {
114
+ const verifier = pending.take(state, now);
115
+ if (verifier === null) throw new GeminiAuthError('that sign-in has expired; start it again');
116
+ const code = codeIn(pasted, state);
117
+ const body = await tokenCall(
118
+ { client_id: GEMINI_CLIENT_ID, client_secret: GEMINI_CLIENT_SECRET, grant_type: 'authorization_code', code, code_verifier: verifier, redirect_uri: GEMINI_REDIRECT },
119
+ base,
120
+ fetchImpl,
121
+ );
122
+ return tokensOf(body, null, now);
123
+ }
124
+
125
+ export async function refreshTokens(previous: GeminiTokens, base = GEMINI_TOKEN_BASE, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<GeminiTokens> {
126
+ const body = await tokenCall(
127
+ { client_id: GEMINI_CLIENT_ID, client_secret: GEMINI_CLIENT_SECRET, grant_type: 'refresh_token', refresh_token: previous.refreshToken },
128
+ base,
129
+ fetchImpl,
130
+ );
131
+ return tokensOf(body, previous, now);
132
+ }
133
+
134
+ export const tokensStale = (tokens: GeminiTokens, now = Date.now()): boolean => tokens.expiresAt - EXPIRY_MARGIN_MS <= now;
135
+
136
+ export async function userEmail(tokens: GeminiTokens, base = 'https://www.googleapis.com', fetchImpl: typeof fetch = fetch): Promise<string | null> {
137
+ try {
138
+ const res = await fetchImpl(`${base}/oauth2/v2/userinfo`, { headers: { authorization: `Bearer ${tokens.accessToken}` }, signal: AbortSignal.timeout(15_000) });
139
+ const body: unknown = await res.json().catch(() => null);
140
+ return res.ok && isRecord(body) ? nonEmpty(body.email) : null;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ export function tokensFromDisk(raw: unknown): GeminiTokens | null {
147
+ if (!isRecord(raw)) return null;
148
+ const accessToken = nonEmpty(raw.accessToken);
149
+ const refreshToken = nonEmpty(raw.refreshToken);
150
+ if (accessToken === null || refreshToken === null) return null;
151
+ return {
152
+ accessToken,
153
+ refreshToken,
154
+ expiresAt: typeof raw.expiresAt === 'number' ? raw.expiresAt : 0,
155
+ email: nonEmpty(raw.email),
156
+ project: nonEmpty(raw.project) ?? '',
157
+ tier: nonEmpty(raw.tier),
158
+ savedAt: nonEmpty(raw.savedAt) ?? '',
159
+ };
160
+ }
@@ -0,0 +1,46 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ const CODE_ASSIST_BASE = 'https://cloudcode-pa.googleapis.com';
4
+ const CODE_ASSIST_DAILY = 'https://daily-cloudcode-pa.googleapis.com';
5
+ export const API = 'v1internal';
6
+ const APP_VERSION = '2.0.3';
7
+ const CLIENT_VERSION = '1.110.0';
8
+ export const CLIENT_NAME = 'antigravity';
9
+ const IDE_TYPE = 9;
10
+ const PLUGIN_TYPE = 2;
11
+ export const SYSTEM_PREFIX =
12
+ 'You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**';
13
+
14
+ const PLATFORMS: Record<string, number> = { 'darwin-x64': 1, 'darwin-arm64': 2, 'linux-x64': 3, 'linux-arm64': 4, 'win32-x64': 5 };
15
+
16
+ const osName = (): string => (process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'win32' : 'linux');
17
+
18
+ const platformCode = (): number => PLATFORMS[`${process.platform}-${process.arch}`] ?? 0;
19
+
20
+ export const clientMetadata = (project: string | null): Record<string, unknown> => ({
21
+ ideType: IDE_TYPE,
22
+ platform: platformCode(),
23
+ pluginType: PLUGIN_TYPE,
24
+ ...(project === null ? {} : { duetProject: project }),
25
+ });
26
+
27
+ export const clientHeaders = (): Record<string, string> => ({
28
+ 'user-agent': `${CLIENT_NAME}/${APP_VERSION} ${osName()}/${process.arch}`,
29
+ 'x-client-name': CLIENT_NAME,
30
+ 'x-client-version': CLIENT_VERSION,
31
+ 'x-goog-api-client': 'gl-node/18.18.2 fire/0.8.6 grpc/1.10.x',
32
+ });
33
+
34
+ export type Bases = string | string[];
35
+
36
+ const listed = (override: Bases): string[] => (typeof override === 'string' ? [override] : override);
37
+
38
+ export const generateBases = (override?: Bases): string[] => (override === undefined ? [CODE_ASSIST_DAILY, CODE_ASSIST_BASE] : listed(override));
39
+
40
+ export const setupBases = (override?: Bases): string[] => (override === undefined ? [CODE_ASSIST_BASE, CODE_ASSIST_DAILY] : listed(override));
41
+
42
+ export const requestId = (): string => `agent-${randomUUID()}`;
43
+
44
+ const sessionId = randomUUID();
45
+
46
+ export const machineSessionId = (): string => sessionId;
@@ -0,0 +1,150 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { log } from '@metro-labs/core/log';
3
+ import { GeminiAuthError, type GeminiTokens } from './gemini-auth.js';
4
+ import { API, clientHeaders, clientMetadata, setupBases, type Bases } from './gemini-client.js';
5
+ import { nonEmpty } from './text.js';
6
+
7
+ const FREE_TIER = 'free-tier';
8
+ const PROJECT_RE = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
9
+ const POLL_MS = 5000;
10
+ const POLL_MAX = 24;
11
+
12
+ export interface Onboarded {
13
+ project: string;
14
+ tier: string | null;
15
+ }
16
+
17
+
18
+ export function parseGeminiProject(value: unknown): string | null {
19
+ const project = typeof value === 'string' ? value.trim() : '';
20
+ if (project === '') return null;
21
+ if (!PROJECT_RE.test(project)) throw new GeminiAuthError('that is not a Google Cloud project id: 6 to 30 lowercase letters, digits and dashes, starting with a letter');
22
+ return project;
23
+ }
24
+
25
+ class Retryable extends Error {}
26
+
27
+ async function callOne(base: string, method: string, token: string, body: unknown, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
28
+ let res: Response;
29
+ try {
30
+ res = await fetchImpl(`${base}/${API}:${method}`, {
31
+ method: 'POST',
32
+ headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json', ...clientHeaders() },
33
+ body: JSON.stringify(body),
34
+ signal: AbortSignal.timeout(30_000),
35
+ });
36
+ } catch (err) {
37
+ throw new Retryable(`could not reach Google Code Assist: ${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ const answer: unknown = await res.json().catch(() => null);
40
+ if (!res.ok) {
41
+ const detail = (isRecord(answer) && isRecord(answer.error) ? nonEmpty(answer.error.message) : null) ?? `Google Code Assist answered ${String(res.status)} on ${method}`;
42
+ throw res.status >= 500 ? new Retryable(detail) : new GeminiAuthError(detail);
43
+ }
44
+ return isRecord(answer) ? answer : {};
45
+ }
46
+
47
+ async function call(bases: string[], method: string, token: string, body: unknown, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
48
+ let last = 'no Code Assist endpoint to call';
49
+ for (const base of bases) {
50
+ try {
51
+ return await callOne(base, method, token, body, fetchImpl);
52
+ } catch (err) {
53
+ if (!(err instanceof Retryable)) throw err;
54
+ last = err.message;
55
+ }
56
+ }
57
+ throw new GeminiAuthError(last);
58
+ }
59
+
60
+ const tierOf = (load: Record<string, unknown>): { id: string | null; name: string | null } => {
61
+ const tier = isRecord(load.paidTier) ? load.paidTier : isRecord(load.currentTier) ? load.currentTier : null;
62
+ return { id: tier === null ? null : nonEmpty(tier.id), name: tier === null ? null : nonEmpty(tier.name) };
63
+ };
64
+
65
+ async function waitOperation(base: string, token: string, first: Record<string, unknown>, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
66
+ let op = first;
67
+ for (let i = 0; op.done !== true && i < POLL_MAX; i += 1) {
68
+ const name = nonEmpty(op.name);
69
+ if (name === null) break;
70
+ await new Promise((r) => setTimeout(r, POLL_MS));
71
+ const res = await fetchImpl(`${base}/${API}/${name}`, { headers: { authorization: `Bearer ${token}`, ...clientHeaders() }, signal: AbortSignal.timeout(30_000) });
72
+ const answer: unknown = await res.json().catch(() => null);
73
+ op = isRecord(answer) ? answer : {};
74
+ }
75
+ return op;
76
+ }
77
+
78
+ const REASONS: Record<string, string> = {
79
+ DASHER_USER: 'this is a Google Workspace account; the individual tier needs a personal Google account',
80
+ NON_USER_ACCOUNT: 'this is not a user account',
81
+ RESTRICTED_AGE: 'the account does not meet the age requirement',
82
+ UNSUPPORTED_LOCATION: 'Gemini Code Assist for individuals is not offered where this account is located',
83
+ UNKNOWN_LOCATION: 'Google could not tell where this account is located',
84
+ RESTRICTED_NETWORK: 'the network this box is on is restricted',
85
+ INELIGIBLE_ACCOUNT: 'Google marks this account as not eligible',
86
+ UNSUPPORTED_CLIENT: 'Google no longer serves the client metro presents; nothing on this box can fix that',
87
+ };
88
+
89
+ function refuseIneligible(load: Record<string, unknown>): void {
90
+ if (isRecord(load.currentTier) || !Array.isArray(load.ineligibleTiers)) return;
91
+ const tiers = load.ineligibleTiers.filter(isRecord);
92
+ if (tiers.length === 0) return;
93
+ const validation = tiers.find((t) => t.reasonCode === 'VALIDATION_REQUIRED' && nonEmpty(t.validationUrl) !== null);
94
+ if (validation !== undefined) throw new GeminiAuthError(`Google asks you to validate the account first: open ${nonEmpty(validation.validationUrl) ?? ''} and then connect again`);
95
+ const said = tiers.map((t) => `${nonEmpty(t.reasonMessage) ?? 'not eligible'} [${nonEmpty(t.reasonCode) ?? 'UNKNOWN'}: ${REASONS[nonEmpty(t.reasonCode) ?? ''] ?? 'no known reason'}]`);
96
+ throw new GeminiAuthError(said.join('; '));
97
+ }
98
+
99
+ const NO_PROJECT = 'this account holds a Code Assist licence but Google named no project for it: enter the id of the Google Cloud project that carries the licence and connect again';
100
+
101
+ function tierToOnboard(load: Record<string, unknown>): { id: string; name: string | null } {
102
+ const allowed = Array.isArray(load.allowedTiers) ? load.allowedTiers.filter(isRecord) : [];
103
+ const chosen = allowed.find((t) => t.isDefault === true);
104
+ return { id: nonEmpty(chosen?.id) ?? FREE_TIER, name: nonEmpty(chosen?.name) };
105
+ }
106
+
107
+ const summary = (load: Record<string, unknown>): Record<string, unknown> => ({
108
+ current: isRecord(load.currentTier) ? nonEmpty(load.currentTier.id) : null,
109
+ paid: isRecord(load.paidTier) ? nonEmpty(load.paidTier.id) : null,
110
+ allowed: Array.isArray(load.allowedTiers) ? load.allowedTiers.filter(isRecord).map((t) => `${nonEmpty(t.id) ?? '?'}${t.isDefault === true ? '*' : ''}`) : [],
111
+ ineligible: Array.isArray(load.ineligibleTiers) ? load.ineligibleTiers.filter(isRecord).map((t) => `${nonEmpty(t.tierId) ?? '?'}:${nonEmpty(t.reasonCode) ?? '?'}`) : [],
112
+ project: nonEmpty(load.cloudaicompanionProject),
113
+ });
114
+
115
+ function onboardBody(load: Record<string, unknown>, project: string | null): { body: Record<string, unknown>; name: string | null } {
116
+ const wanted = tierToOnboard(load);
117
+ const paid = wanted.id !== FREE_TIER && project !== null;
118
+ return { body: { tierId: wanted.id, metadata: clientMetadata(paid ? project : null) }, name: wanted.name ?? wanted.id };
119
+ }
120
+
121
+ const NO_ASSIGNMENT = 'Google did not assign a Code Assist project to this account; enter the id of the Google Cloud project that carries your licence and connect again';
122
+
123
+ const withProject = (project: string | null): Record<string, unknown> => (project === null ? {} : { cloudaicompanionProject: project });
124
+
125
+ const assignedProject = (op: Record<string, unknown>): string | null => {
126
+ const response = isRecord(op.response) ? op.response : {};
127
+ return isRecord(response.cloudaicompanionProject) ? nonEmpty(response.cloudaicompanionProject.id) : null;
128
+ };
129
+
130
+ async function onboardNew(load: Record<string, unknown>, tokens: GeminiTokens, project: string | null, bases: string[], fetchImpl: typeof fetch): Promise<Onboarded> {
131
+ const { body, name } = onboardBody(load, project);
132
+ const first = await call(bases, 'onboardUser', tokens.accessToken, body, fetchImpl);
133
+ const op = await waitOperation(bases[0] ?? '', tokens.accessToken, first, fetchImpl);
134
+ const assigned = assignedProject(op) ?? project;
135
+ if (assigned === null) throw new GeminiAuthError(NO_ASSIGNMENT);
136
+ return { project: assigned, tier: name };
137
+ }
138
+
139
+ export async function onboard(tokens: GeminiTokens, project: string | null = null, base?: Bases, fetchImpl: typeof fetch = fetch): Promise<Onboarded> {
140
+ const bases = setupBases(base);
141
+ const asked = { metadata: clientMetadata(project), mode: 1, ...withProject(project) };
142
+ const load = await call(bases, 'loadCodeAssist', tokens.accessToken, asked, fetchImpl);
143
+ log.info(summary(load), 'gemini: Code Assist answered loadCodeAssist');
144
+ refuseIneligible(load);
145
+ if (!isRecord(load.currentTier)) return onboardNew(load, tokens, project, [...bases].reverse(), fetchImpl);
146
+ const known = nonEmpty(load.cloudaicompanionProject) ?? project;
147
+ if (known === null) throw new GeminiAuthError(NO_PROJECT);
148
+ const tier = tierOf(load);
149
+ return { project: known, tier: tier.name ?? tier.id };
150
+ }