@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151

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 (327) hide show
  1. package/dist/api.js +9 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +182 -0
  4. package/dist/cli.js +86 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +77 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +50 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +155 -0
  16. package/dist/service.js +242 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -28
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +40 -0
  26. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  27. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  28. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  29. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  30. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  31. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  32. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  33. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  35. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  36. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  37. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  38. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  40. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  46. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  47. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  48. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  49. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +39 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +225 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +364 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +30 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +380 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +354 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +263 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +313 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +304 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +247 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +148 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +242 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +186 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +370 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +65 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +97 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  192. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  193. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  194. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  196. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +106 -0
  197. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +150 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +223 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +37 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -139
  298. package/dist/broker/claims.js +0 -144
  299. package/dist/broker/history-stream.js +0 -149
  300. package/dist/cli/config.js +0 -179
  301. package/dist/cli/index.js +0 -175
  302. package/dist/cli/messenger-api.js +0 -214
  303. package/dist/cli/messenger-transcribe.js +0 -43
  304. package/dist/cli/messenger-uploads.js +0 -116
  305. package/dist/cli/monitor-api.js +0 -205
  306. package/dist/cli/tail.js +0 -95
  307. package/dist/cli/util.js +0 -72
  308. package/dist/cli/webhook.js +0 -181
  309. package/dist/codex-rc/client.js +0 -225
  310. package/dist/codex-rc/protocol.js +0 -38
  311. package/dist/dispatcher/server.js +0 -122
  312. package/dist/dispatcher.js +0 -97
  313. package/dist/history.js +0 -143
  314. package/dist/ipc.js +0 -90
  315. package/dist/lines.js +0 -54
  316. package/dist/local-identity.js +0 -80
  317. package/dist/log.js +0 -15
  318. package/dist/paths.js +0 -114
  319. package/dist/trains/protocol.js +0 -99
  320. package/dist/trains/supervisor.js +0 -210
  321. package/dist/tunnel.js +0 -102
  322. package/docs/broker.md +0 -166
  323. package/docs/monitor.md +0 -241
  324. package/docs/uri-scheme.md +0 -127
  325. package/examples/README.md +0 -32
  326. package/examples/telegram.ts +0 -121
  327. package/skills/metro/SKILL.md +0 -110
@@ -0,0 +1,148 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { encodeSignature, newCallId, rememberSignature } from './gemini-translate.js';
3
+
4
+ type Item = Record<string, unknown>;
5
+
6
+ interface Open {
7
+ index: number;
8
+ kind: 'text' | 'thinking';
9
+ signature: string | null;
10
+ }
11
+
12
+ interface Usage {
13
+ input_tokens: number;
14
+ output_tokens: number;
15
+ cache_read_input_tokens: number;
16
+ }
17
+
18
+ const frame = (event: string, data: unknown): string => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
19
+ const str = (value: unknown): string => (typeof value === 'string' ? value : '');
20
+ const num = (value: unknown): number => (typeof value === 'number' ? value : 0);
21
+
22
+ const STOP_OF: Record<string, string> = { STOP: 'end_turn', MAX_TOKENS: 'max_tokens' };
23
+ const REFUSALS = new Set(['SAFETY', 'RECITATION', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII', 'MALFORMED_FUNCTION_CALL']);
24
+
25
+ function usageOf(meta: Item): Usage {
26
+ const cached = num(meta.cachedContentTokenCount);
27
+ return {
28
+ input_tokens: Math.max(0, num(meta.promptTokenCount) - cached),
29
+ output_tokens: num(meta.candidatesTokenCount) + num(meta.thoughtsTokenCount),
30
+ cache_read_input_tokens: cached,
31
+ };
32
+ }
33
+
34
+ export class GeminiStreamTranslator {
35
+ private next = 0;
36
+ private open: Open | null = null;
37
+ private started = false;
38
+ private done = false;
39
+ private toolCalls = 0;
40
+ private usage: Usage = { input_tokens: 0, output_tokens: 0, cache_read_input_tokens: 0 };
41
+ private stop = 'end_turn';
42
+ private refusal: string | null = null;
43
+ private readonly model: string;
44
+ private readonly restore: (name: string) => string;
45
+
46
+ constructor(model: string, restore: (name: string) => string = (name) => name) {
47
+ this.model = model;
48
+ this.restore = restore;
49
+ }
50
+
51
+ get finished(): boolean {
52
+ return this.done;
53
+ }
54
+
55
+ private start(): string {
56
+ if (this.started) return '';
57
+ this.started = true;
58
+ return frame('message_start', {
59
+ type: 'message_start',
60
+ message: { id: 'msg_gemini', type: 'message', role: 'assistant', model: this.model, content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } },
61
+ });
62
+ }
63
+
64
+ private closeOpen(): string {
65
+ const block = this.open;
66
+ if (block === null) return '';
67
+ this.open = null;
68
+ const signature = block.kind === 'thinking' ? frame('content_block_delta', { type: 'content_block_delta', index: block.index, delta: { type: 'signature_delta', signature: encodeSignature(block.signature ?? '') } }) : '';
69
+ return signature + frame('content_block_stop', { type: 'content_block_stop', index: block.index });
70
+ }
71
+
72
+ private ensure(kind: Open['kind']): string {
73
+ if (this.open?.kind === kind) return '';
74
+ const closed = this.closeOpen();
75
+ this.open = { index: this.next, kind, signature: null };
76
+ this.next += 1;
77
+ const content = kind === 'text' ? { type: 'text', text: '' } : { type: 'thinking', thinking: '' };
78
+ return closed + frame('content_block_start', { type: 'content_block_start', index: this.open.index, content_block: content });
79
+ }
80
+
81
+ private textPart(part: Item): string {
82
+ const kind = part.thought === true ? 'thinking' : 'text';
83
+ const text = str(part.text);
84
+ let out = this.ensure(kind);
85
+ const block = this.open;
86
+ if (block !== null && str(part.thoughtSignature) !== '') block.signature = str(part.thoughtSignature);
87
+ if (text === '') return out;
88
+ const delta = kind === 'text' ? { type: 'text_delta', text } : { type: 'thinking_delta', thinking: text };
89
+ out += frame('content_block_delta', { type: 'content_block_delta', index: block?.index ?? 0, delta });
90
+ return out;
91
+ }
92
+
93
+ private callPart(call: Item, signature: string): string {
94
+ this.toolCalls += 1;
95
+ const id = str(call.id) || newCallId();
96
+ if (signature !== '') rememberSignature(id, signature);
97
+ const index = this.next;
98
+ this.next += 1;
99
+ return (
100
+ this.closeOpen() +
101
+ frame('content_block_start', { type: 'content_block_start', index, content_block: { type: 'tool_use', id, name: this.restore(str(call.name)), input: {} } }) +
102
+ frame('content_block_delta', { type: 'content_block_delta', index, delta: { type: 'input_json_delta', partial_json: JSON.stringify(isRecord(call.args) ? call.args : {}) } }) +
103
+ frame('content_block_stop', { type: 'content_block_stop', index })
104
+ );
105
+ }
106
+
107
+ private part(part: Item): string {
108
+ if (isRecord(part.functionCall)) return this.callPart(part.functionCall, str(part.thoughtSignature));
109
+ if (typeof part.text === 'string') return this.textPart(part);
110
+ return '';
111
+ }
112
+
113
+ private candidate(candidate: Item): string {
114
+ const content = isRecord(candidate.content) ? candidate.content : {};
115
+ const parts = Array.isArray(content.parts) ? content.parts.filter(isRecord) : [];
116
+ const out = parts.map((part) => this.part(part)).join('');
117
+ const finish = str(candidate.finishReason);
118
+ const stop = STOP_OF[finish];
119
+ if (stop !== undefined) this.stop = stop;
120
+ else if (REFUSALS.has(finish)) this.refusal = `Gemini stopped the answer (${finish})`;
121
+ return out;
122
+ }
123
+
124
+ push(data: Item): string {
125
+ if (this.done) return '';
126
+ const response = isRecord(data.response) ? data.response : data;
127
+ const out = this.start();
128
+ if (isRecord(response.usageMetadata)) this.usage = usageOf(response.usageMetadata);
129
+ const candidate = Array.isArray(response.candidates) ? response.candidates.find(isRecord) : undefined;
130
+ if (candidate !== undefined) return out + this.candidate(candidate);
131
+ const feedback = isRecord(response.promptFeedback) ? str(response.promptFeedback.blockReason) : '';
132
+ if (feedback !== '') this.refusal = `Gemini blocked the prompt (${feedback})`;
133
+ return out;
134
+ }
135
+
136
+ close(error?: string): string {
137
+ if (this.done) return '';
138
+ this.done = true;
139
+ let out = this.start() + this.closeOpen();
140
+ const failure = error ?? this.refusal;
141
+ if (failure !== null && failure !== undefined && this.toolCalls === 0 && this.next === 0)
142
+ return out + frame('error', { type: 'error', error: { type: 'api_error', message: failure } });
143
+ const stop = this.toolCalls > 0 ? 'tool_use' : this.stop;
144
+ out += frame('message_delta', { type: 'message_delta', delta: { stop_reason: stop, stop_sequence: null }, usage: this.usage });
145
+ out += frame('message_stop', { type: 'message_stop' });
146
+ return out;
147
+ }
148
+ }
@@ -0,0 +1,201 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { isRecord } from '@metro-labs/core/is-record';
3
+ import { ToolNames } from './codex-translate.js';
4
+ import { CLIENT_NAME, requestId, SYSTEM_PREFIX } from './gemini-client.js';
5
+
6
+ type Item = Record<string, unknown>;
7
+
8
+ export const SIGNATURE_PREFIX = 'metro-gemini:';
9
+ export const SKIP_SIGNATURE = 'skip_thought_signature_validator';
10
+ const IMAGE_NOTE = '[an image was attached here; this model cannot see it]';
11
+ const SCHEMA_DROP = new Set(['$schema', '$id', 'additionalProperties', 'examples', 'default', 'title']);
12
+ const SIGNATURES_MAX = 2000;
13
+ const MAX_OUTPUT_TOKENS = 16384;
14
+
15
+ const textOf = (value: unknown): string => (typeof value === 'string' ? value : '');
16
+
17
+ const callSignatures = new Map<string, string>();
18
+
19
+ export function rememberSignature(id: string, signature: string): void {
20
+ callSignatures.set(id, signature);
21
+ if (callSignatures.size > SIGNATURES_MAX) {
22
+ const first = callSignatures.keys().next().value;
23
+ if (first !== undefined) callSignatures.delete(first);
24
+ }
25
+ }
26
+
27
+ export const newCallId = (): string => `toolu_gm_${randomBytes(12).toString('base64url')}`;
28
+
29
+ export const encodeSignature = (signature: string): string => `${SIGNATURE_PREFIX}${Buffer.from(signature).toString('base64url')}`;
30
+
31
+ export function decodeSignature(signature: unknown): string | null {
32
+ if (typeof signature !== 'string' || !signature.startsWith(SIGNATURE_PREFIX)) return null;
33
+ const raw = Buffer.from(signature.slice(SIGNATURE_PREFIX.length), 'base64url').toString('utf8');
34
+ return raw === '' ? null : raw;
35
+ }
36
+
37
+ export function systemText(system: unknown): string {
38
+ if (typeof system === 'string') return system.trim();
39
+ if (!Array.isArray(system)) return '';
40
+ return system
41
+ .filter(isRecord)
42
+ .map((block) => textOf(block.text))
43
+ .filter((t) => t !== '')
44
+ .join('\n\n')
45
+ .trim();
46
+ }
47
+
48
+ function userPart(block: Item): Item | null {
49
+ if (block.type === 'text') return { text: textOf(block.text) };
50
+ if (block.type === 'image' && isRecord(block.source) && block.source.type === 'base64')
51
+ return { inlineData: { mimeType: textOf(block.source.media_type), data: textOf(block.source.data) } };
52
+ return null;
53
+ }
54
+
55
+ const partText = (part: Item): string => (part.type === 'text' ? textOf(part.text) : part.type === 'image' ? IMAGE_NOTE : '');
56
+
57
+ function resultText(block: Item): string {
58
+ const content = block.content;
59
+ const text = typeof content === 'string' ? content : Array.isArray(content) ? content.filter(isRecord).map(partText).filter((t) => t !== '').join('\n') : '';
60
+ return block.is_error === true ? `[tool error] ${text}` : text;
61
+ }
62
+
63
+ function userParts(content: unknown, calls: Map<string, string>): Item[] {
64
+ if (typeof content === 'string') return content === '' ? [] : [{ text: content }];
65
+ if (!Array.isArray(content)) return [];
66
+ const out: Item[] = [];
67
+ for (const block of content.filter(isRecord)) {
68
+ if (block.type === 'tool_result') {
69
+ const id = textOf(block.tool_use_id);
70
+ out.push({ functionResponse: { id, name: calls.get(id) ?? 'tool', response: { result: resultText(block) } } });
71
+ continue;
72
+ }
73
+ const part = userPart(block);
74
+ if (part !== null) out.push(part);
75
+ }
76
+ return out;
77
+ }
78
+
79
+ function callPart(block: Item, names: ToolNames, calls: Map<string, string>): Item {
80
+ const id = textOf(block.id);
81
+ const name = names.alias(textOf(block.name));
82
+ calls.set(id, name);
83
+ return { functionCall: { id, name, args: isRecord(block.input) ? block.input : {} }, thoughtSignature: callSignatures.get(id) ?? SKIP_SIGNATURE };
84
+ }
85
+
86
+ function assistantPart(block: Item, names: ToolNames, calls: Map<string, string>): Item | null {
87
+ if (block.type === 'text') return { text: textOf(block.text) };
88
+ if (block.type === 'tool_use') return callPart(block, names, calls);
89
+ if (block.type !== 'thinking') return null;
90
+ const signature = decodeSignature(block.signature);
91
+ return signature === null ? null : { text: textOf(block.thinking) || ' ', thought: true, thoughtSignature: signature };
92
+ }
93
+
94
+ function assistantParts(content: unknown, names: ToolNames, calls: Map<string, string>): Item[] {
95
+ if (typeof content === 'string') return content === '' ? [] : [{ text: content }];
96
+ if (!Array.isArray(content)) return [];
97
+ const out: Item[] = [];
98
+ for (const block of content.filter(isRecord)) {
99
+ const part = assistantPart(block, names, calls);
100
+ if (part !== null) out.push(part);
101
+ }
102
+ return out;
103
+ }
104
+
105
+ function appendTurn(out: Item[], role: string, parts: Item[]): void {
106
+ if (parts.length === 0) return;
107
+ const last = out.at(-1);
108
+ const held = last?.role === role && Array.isArray(last.parts) ? (last.parts as Item[]) : null;
109
+ if (held !== null) held.push(...parts);
110
+ else out.push({ role, parts });
111
+ }
112
+
113
+ export function contentsOf(messages: unknown, names = new ToolNames()): Item[] {
114
+ const out: Item[] = [];
115
+ if (!Array.isArray(messages)) return out;
116
+ const calls = new Map<string, string>();
117
+ for (const message of messages.filter(isRecord)) {
118
+ const model = message.role === 'assistant';
119
+ appendTurn(out, model ? 'model' : 'user', model ? assistantParts(message.content, names, calls) : userParts(message.content, calls));
120
+ }
121
+ return out;
122
+ }
123
+
124
+ export function cleanSchema(schema: unknown): unknown {
125
+ if (Array.isArray(schema)) return schema.map(cleanSchema);
126
+ if (!isRecord(schema)) return schema;
127
+ const out: Item = {};
128
+ for (const [key, value] of Object.entries(schema)) {
129
+ if (SCHEMA_DROP.has(key)) continue;
130
+ if (key === 'const') {
131
+ out.enum = [value];
132
+ continue;
133
+ }
134
+ out[key] = cleanSchema(value);
135
+ }
136
+ return out;
137
+ }
138
+
139
+ export function toolDeclarations(tools: unknown, names = new ToolNames()): Item[] {
140
+ if (!Array.isArray(tools)) return [];
141
+ return tools
142
+ .filter(isRecord)
143
+ .filter((tool) => typeof tool.name === 'string' && (tool.type === undefined || tool.type === 'custom'))
144
+ .map((tool) => ({
145
+ name: names.alias(textOf(tool.name)),
146
+ description: textOf(tool.description),
147
+ parameters: cleanSchema(isRecord(tool.input_schema) ? tool.input_schema : { type: 'object', properties: {} }),
148
+ }));
149
+ }
150
+
151
+ function toolConfig(choice: unknown, names: ToolNames): Item | null {
152
+ if (!isRecord(choice)) return null;
153
+ if (choice.type === 'any') return { functionCallingConfig: { mode: 'ANY' } };
154
+ if (choice.type === 'none') return { functionCallingConfig: { mode: 'NONE' } };
155
+ if (choice.type === 'tool') return { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [names.alias(textOf(choice.name))] } };
156
+ return null;
157
+ }
158
+
159
+ function generationConfig(body: Item): Item {
160
+ const out: Item = {};
161
+ if (typeof body.max_tokens === 'number') out.maxOutputTokens = Math.min(body.max_tokens, MAX_OUTPUT_TOKENS);
162
+ if (typeof body.temperature === 'number') out.temperature = body.temperature;
163
+ if (typeof body.top_p === 'number') out.topP = body.top_p;
164
+ if (isRecord(body.thinking) && body.thinking.type === 'enabled') out.thinkingConfig = { includeThoughts: true };
165
+ return out;
166
+ }
167
+
168
+ export interface GeminiRequest {
169
+ model: string;
170
+ project: string;
171
+ userAgent: string;
172
+ requestType: string;
173
+ requestId: string;
174
+ request: Item;
175
+ }
176
+
177
+ const systemParts = (system: string): Item[] => [
178
+ { text: SYSTEM_PREFIX },
179
+ { text: `Please ignore the following [ignore]${SYSTEM_PREFIX}[/ignore]` },
180
+ ...(system === '' ? [] : [{ text: system }]),
181
+ ];
182
+
183
+ export function toGeminiRequest(body: Item, model: string, project: string, promptId: string, names = new ToolNames()): GeminiRequest {
184
+ const declarations = toolDeclarations(body.tools, names);
185
+ const config = toolConfig(body.tool_choice, names);
186
+ return {
187
+ model,
188
+ project,
189
+ userAgent: CLIENT_NAME,
190
+ requestType: 'agent',
191
+ requestId: requestId(),
192
+ request: {
193
+ contents: contentsOf(body.messages, names),
194
+ systemInstruction: { role: 'user', parts: systemParts(systemText(body.system)) },
195
+ ...(declarations.length === 0 ? {} : { tools: [{ functionDeclarations: declarations }] }),
196
+ ...(config === null ? {} : { toolConfig: config }),
197
+ generationConfig: generationConfig(body),
198
+ sessionId: promptId,
199
+ },
200
+ };
201
+ }
@@ -0,0 +1,257 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { isRecord } from '@metro-labs/core/is-record';
3
+ import { errMsg } from '@metro-labs/core/log';
4
+ import { refreshTokens, tokensStale, type GeminiTokens } from './gemini-auth.js';
5
+ import { API, clientHeaders, generateBases, machineSessionId, type Bases } from './gemini-client.js';
6
+ import { GeminiStreamTranslator } from './gemini-stream.js';
7
+ import { toGeminiRequest } from './gemini-translate.js';
8
+ import { ToolNames } from './codex-translate.js';
9
+ import { assembleMessage, SseParser } from './codex-stream.js';
10
+ import { GatewayError, idleMessage, providerStatus, sendError, upstreamMessage, type Watch } from './forward.js';
11
+ import type { ModelConfig } from './model-config.js';
12
+ import { UsageScanner } from './usage.js';
13
+
14
+ const PING_MS = 25_000;
15
+ const STATUS_OF: Record<string, number> = { rate_limit_error: 429, invalid_request_error: 400, permission_error: 403, overloaded_error: 529 };
16
+
17
+ export interface GeminiDeps {
18
+ base?: Bases;
19
+ tokenBase?: string;
20
+ fetchImpl?: typeof fetch;
21
+ save: (tokens: GeminiTokens) => void;
22
+ }
23
+
24
+ export interface GeminiState {
25
+ refreshing: Promise<GeminiTokens> | null;
26
+ latest: GeminiTokens | null;
27
+ }
28
+
29
+ export const freshGeminiState = (): GeminiState => ({ refreshing: null, latest: null });
30
+
31
+ const newerThan = (a: GeminiTokens, b: GeminiTokens): boolean => Date.parse(a.savedAt) > Date.parse(b.savedAt);
32
+
33
+ function refreshed(tokens: GeminiTokens, deps: GeminiDeps, state: GeminiState): Promise<GeminiTokens> {
34
+ const latest = state.latest;
35
+ if (latest !== null && newerThan(latest, tokens) && !tokensStale(latest)) return Promise.resolve(latest);
36
+ if (state.refreshing !== null) return state.refreshing;
37
+ const run = refreshTokens(tokens, deps.tokenBase, deps.fetchImpl)
38
+ .then((fresh) => {
39
+ deps.save(fresh);
40
+ state.latest = fresh;
41
+ return fresh;
42
+ })
43
+ .catch((err: unknown) => {
44
+ throw new GatewayError(403, 'permission_error', `Google sign-in expired (${errMsg(err)}): connect again on the Model page`);
45
+ })
46
+ .finally(() => {
47
+ state.refreshing = null;
48
+ });
49
+ state.refreshing = run;
50
+ return run;
51
+ }
52
+
53
+ export function currentGeminiTokens(cfg: ModelConfig, deps: GeminiDeps, state: GeminiState): Promise<GeminiTokens> {
54
+ const tokens = cfg.gemini.auth;
55
+ if (tokens === null) throw new GatewayError(400, 'invalid_request_error', 'Gemini is not connected: sign in with Google on the Model page.');
56
+ return tokensStale(tokens) ? refreshed(tokens, deps, state) : Promise.resolve(tokens);
57
+ }
58
+
59
+ function errorKind(status: number): string {
60
+ if (status === 401 || status === 403) return 'permission_error';
61
+ if (status === 429) return 'rate_limit_error';
62
+ if (status === 400 || status === 404) return 'invalid_request_error';
63
+ return 'api_error';
64
+ }
65
+
66
+ const promptIdOf = (req: IncomingMessage): string => {
67
+ const raw = req.headers['x-claude-code-session-id'];
68
+ const given = (Array.isArray(raw) ? raw[0] : raw)?.trim() ?? '';
69
+ return given === '' ? 'metro' : given;
70
+ };
71
+
72
+ interface Call {
73
+ body: Record<string, unknown>;
74
+ model: string;
75
+ promptId: string;
76
+ watch: Watch;
77
+ deps: GeminiDeps;
78
+ names: ToolNames;
79
+ }
80
+
81
+ const headersFor = (token: string, stream: boolean): Record<string, string> => ({
82
+ authorization: `Bearer ${token}`,
83
+ 'content-type': 'application/json',
84
+ ...clientHeaders(),
85
+ 'x-machine-session-id': machineSessionId(),
86
+ ...(stream ? { accept: 'text/event-stream' } : {}),
87
+ });
88
+
89
+ async function sendTo(base: string, call: Call, tokens: GeminiTokens, stream: boolean): Promise<Response> {
90
+ const request = toGeminiRequest(call.body, call.model, tokens.project, call.promptId, call.names);
91
+ const method = stream ? 'streamGenerateContent?alt=sse' : 'generateContent';
92
+ return (call.deps.fetchImpl ?? fetch)(`${base}/${API}:${method}`, {
93
+ method: 'POST',
94
+ headers: headersFor(tokens.accessToken, stream),
95
+ body: JSON.stringify(request),
96
+ signal: call.watch.signal,
97
+ redirect: 'manual',
98
+ });
99
+ }
100
+
101
+ async function send(call: Call, tokens: GeminiTokens, stream: boolean): Promise<Response> {
102
+ const bases = generateBases(call.deps.base);
103
+ let last: Response | null = null;
104
+ for (const base of bases) {
105
+ if (last !== null) await last.body?.cancel();
106
+ try {
107
+ last = await sendTo(base, call, tokens, stream);
108
+ } catch (err) {
109
+ if (base === bases.at(-1) || call.watch.signal.aborted) throw err;
110
+ continue;
111
+ }
112
+ if (last.status < 500) return last;
113
+ }
114
+ if (last === null) throw new GatewayError(502, 'api_error', 'no Code Assist endpoint to call');
115
+ return last;
116
+ }
117
+
118
+ async function reach(call: Call, cfg: ModelConfig, state: GeminiState, stream: boolean): Promise<Response> {
119
+ let tokens = await currentGeminiTokens(cfg, call.deps, state);
120
+ let upstream = await send(call, tokens, stream);
121
+ if (upstream.status === 401) {
122
+ await upstream.body?.cancel();
123
+ tokens = await refreshed(tokens, call.deps, state);
124
+ upstream = await send(call, tokens, stream);
125
+ }
126
+ return upstream;
127
+ }
128
+
129
+ export interface GeminiModel {
130
+ id: string;
131
+ name: string;
132
+ remaining: number | null;
133
+ resetAt: string | null;
134
+ }
135
+
136
+ const modelOf = (id: string, raw: unknown): GeminiModel => {
137
+ const entry = isRecord(raw) ? raw : {};
138
+ const quota = isRecord(entry.quotaInfo) ? entry.quotaInfo : null;
139
+ const fraction = quota !== null && typeof quota.remainingFraction === 'number' ? quota.remainingFraction : null;
140
+ const resetAt = quota !== null && typeof quota.resetTime === 'string' ? quota.resetTime : null;
141
+ return { id, name: typeof entry.displayName === 'string' ? entry.displayName : id, remaining: fraction ?? (resetAt === null ? null : 0), resetAt };
142
+ };
143
+
144
+ export async function listGeminiModels(tokens: GeminiTokens, deps: GeminiDeps): Promise<GeminiModel[]> {
145
+ let last = 'no Code Assist endpoint to call';
146
+ for (const base of generateBases(deps.base)) {
147
+ try {
148
+ const res = await (deps.fetchImpl ?? fetch)(`${base}/${API}:fetchAvailableModels`, {
149
+ method: 'POST',
150
+ headers: headersFor(tokens.accessToken, false),
151
+ body: JSON.stringify({ project: tokens.project }),
152
+ signal: AbortSignal.timeout(30_000),
153
+ });
154
+ const body: unknown = await res.json().catch(() => null);
155
+ if (!res.ok) {
156
+ last = upstreamMessage(JSON.stringify(body), `Google answered ${String(res.status)}`);
157
+ continue;
158
+ }
159
+ const models = isRecord(body) && isRecord(body.models) ? body.models : {};
160
+ return Object.entries(models)
161
+ .filter(([id]) => id.startsWith('gemini'))
162
+ .map(([id, raw]) => modelOf(id, raw));
163
+ } catch (err) {
164
+ last = errMsg(err);
165
+ }
166
+ }
167
+ throw new GatewayError(502, 'api_error', `could not list the Gemini models: ${last}`);
168
+ }
169
+
170
+ const parseData = (raw: string): Record<string, unknown> | null => {
171
+ try {
172
+ const parsed: unknown = JSON.parse(raw);
173
+ return isRecord(parsed) ? parsed : null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ };
178
+
179
+ async function relayStream(upstream: Response, res: ServerResponse, call: Call): Promise<void> {
180
+ res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive' });
181
+ const translator = new GeminiStreamTranslator(call.model, (name) => call.names.restore(name));
182
+ const body = upstream.body;
183
+ if (body === null) {
184
+ res.end(translator.close());
185
+ return;
186
+ }
187
+ const ping = setInterval(() => res.write('event: ping\ndata: {"type":"ping"}\n\n'), PING_MS);
188
+ const scanner = new UsageScanner('gemini');
189
+ const emit = (frames: string): void => {
190
+ if (frames === '') return;
191
+ scanner.feed(frames);
192
+ res.write(frames);
193
+ };
194
+ try {
195
+ const parser = new SseParser();
196
+ const decoder = new TextDecoder();
197
+ const reader = body.getReader();
198
+ for (;;) {
199
+ const { done, value } = await reader.read();
200
+ if (done) break;
201
+ call.watch.touch();
202
+ for (const raw of parser.push(decoder.decode(value, { stream: true }))) {
203
+ const data = parseData(raw.data);
204
+ if (data !== null) emit(translator.push(data));
205
+ }
206
+ }
207
+ emit(translator.close());
208
+ } catch (err) {
209
+ if (!call.watch.idle()) throw err;
210
+ res.write(translator.finished ? '' : translator.close(idleMessage(call.watch.ms)));
211
+ } finally {
212
+ clearInterval(ping);
213
+ call.watch.stop();
214
+ res.end();
215
+ scanner.done();
216
+ }
217
+ }
218
+
219
+ async function relayWhole(upstream: Response, res: ServerResponse, call: Call): Promise<void> {
220
+ const translator = new GeminiStreamTranslator(call.model, (name) => call.names.restore(name));
221
+ const data = parseData(await upstream.text());
222
+ const frames = (data === null ? '' : translator.push(data)) + translator.close();
223
+ const scanner = new UsageScanner('gemini');
224
+ scanner.feed(frames);
225
+ scanner.done();
226
+ const message = assembleMessage(frames);
227
+ const kind = isRecord(message.error) ? String(message.error.type) : '';
228
+ res.writeHead(message.type === 'error' ? (STATUS_OF[kind] ?? 502) : 200, { 'content-type': 'application/json' });
229
+ res.end(JSON.stringify(message));
230
+ }
231
+
232
+ export async function geminiMessages(
233
+ req: IncomingMessage,
234
+ res: ServerResponse,
235
+ body: Record<string, unknown>,
236
+ model: string,
237
+ cfg: ModelConfig,
238
+ deps: GeminiDeps,
239
+ state: GeminiState,
240
+ watch: Watch,
241
+ ): Promise<void> {
242
+ const stream = body.stream === true;
243
+ const call: Call = { body, model, promptId: promptIdOf(req), watch, deps, names: new ToolNames() };
244
+ const upstream = await reach(call, cfg, state, stream);
245
+ if (!upstream.ok) {
246
+ const text = await upstream.text();
247
+ sendError(res, providerStatus(upstream.status), errorKind(upstream.status), upstreamMessage(text, `Gemini answered ${String(upstream.status)}`));
248
+ return;
249
+ }
250
+ if (stream) await relayStream(upstream, res, call);
251
+ else await relayWhole(upstream, res, call);
252
+ }
253
+
254
+ export function geminiCount(res: ServerResponse, body: Record<string, unknown>): void {
255
+ res.writeHead(200, { 'content-type': 'application/json' });
256
+ res.end(JSON.stringify({ input_tokens: Math.ceil(JSON.stringify(body).length / 4) }));
257
+ }