@stage-labs/metro 0.1.0-beta.21 → 0.1.0-beta.210

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 (436) hide show
  1. package/dist/api.js +9 -101
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +152 -0
  4. package/dist/cli.js +52 -109
  5. package/dist/control.js +47 -75
  6. package/dist/hold.js +210 -0
  7. package/dist/local.js +43 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +44 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +58 -0
  13. package/dist/runtime-install.js +96 -0
  14. package/dist/runtime.js +29 -52
  15. package/dist/serve.js +157 -0
  16. package/dist/service.js +304 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +13 -1
  19. package/package.json +2 -23
  20. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  21. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
  22. package/runtime/marketplace/plugin/.mcp.json +1 -0
  23. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  24. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  25. package/runtime/marketplace/plugin/bin/policy.mjs +97 -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 +26 -0
  29. package/runtime/node_modules/@metro-labs/core/package.json +47 -0
  30. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  31. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/events.ts +11 -7
  32. package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
  33. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  34. package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +24 -28
  35. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
  36. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -0
  37. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  38. package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
  39. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +83 -0
  40. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
  41. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  44. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/station-runtime.ts +6 -11
  45. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +84 -0
  47. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
  48. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  49. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  50. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +89 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +80 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +144 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +37 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +89 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +142 -0
  73. package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
  74. package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-map.ts → daemon/src/agents/map.ts} +58 -1
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  76. package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +150 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +218 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +36 -0
  84. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
  85. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  86. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
  87. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  88. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +70 -45
  89. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
  90. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
  91. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
  92. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +355 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +32 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +284 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +273 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  104. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
  105. package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  108. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
  109. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
  110. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
  111. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
  112. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +347 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  120. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
  121. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
  122. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  123. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
  124. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
  125. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +208 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +195 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +128 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +77 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +274 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +224 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +199 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +278 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +193 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +70 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +66 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +14 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
  155. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
  156. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  157. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
  158. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
  159. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
  160. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
  161. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +24 -79
  162. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  163. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
  164. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +79 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +82 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
  173. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +33 -42
  174. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +88 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  176. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
  177. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +68 -119
  178. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
  179. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  180. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
  181. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  186. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  187. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/routes}/http.ts +59 -51
  188. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +205 -0
  189. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +66 -0
  190. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  191. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  192. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  193. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  194. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  195. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  196. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  198. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
  199. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +9 -36
  200. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
  201. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
  202. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
  203. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
  204. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +75 -9
  205. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
  206. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +22 -6
  207. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  208. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
  209. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
  210. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  211. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
  212. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  213. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  214. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  215. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  216. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  217. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  218. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +9 -0
  219. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  220. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  221. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  222. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +147 -0
  223. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
  224. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
  225. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/package.json +3 -5
  226. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/accounts.ts +8 -28
  227. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/actions.ts +19 -187
  228. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/attachments.ts +3 -3
  229. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/format.ts +31 -72
  230. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group-actions.ts +2 -2
  231. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group.ts +2 -2
  232. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/index.ts +15 -21
  233. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/members.ts +2 -2
  234. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  235. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/send-files.ts +12 -13
  236. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +23 -0
  237. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  238. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
  239. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
  240. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  241. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  242. package/runtime/node_modules/@metro-labs/{telegram-user → outlook}/package.json +2 -5
  243. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +93 -0
  244. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
  245. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +97 -0
  246. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +150 -0
  247. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +53 -0
  248. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +36 -0
  249. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +140 -0
  250. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +60 -0
  251. package/runtime/node_modules/@metro-labs/outlook/src/html.ts +72 -0
  252. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +104 -0
  253. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  254. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +150 -0
  255. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +41 -0
  256. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +116 -0
  257. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +119 -0
  258. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +43 -0
  259. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  260. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +70 -0
  261. package/runtime/node_modules/@metro-labs/telegram/package.json +3 -2
  262. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +38 -96
  263. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +214 -253
  264. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +67 -108
  265. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/client.ts +6 -6
  266. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +101 -147
  267. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/group.ts +4 -4
  268. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/history.ts +1 -1
  269. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
  270. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +6 -102
  271. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/login.ts +20 -20
  272. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +64 -151
  273. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +61 -33
  274. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  275. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/reactions.ts +3 -3
  276. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +11 -1
  277. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +5 -47
  278. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  279. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +86 -0
  280. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +240 -0
  281. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  282. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +170 -0
  283. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  284. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +90 -0
  285. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  286. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
  287. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +21 -0
  288. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
  289. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  290. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +95 -0
  291. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -0
  292. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  293. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  294. package/runtime/node_modules/@metro-labs/threema/src/files.ts +47 -0
  295. package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -0
  296. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  297. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  298. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +89 -0
  299. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  300. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  301. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  302. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  303. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  304. package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
  305. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
  306. package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
  307. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
  308. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
  309. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
  310. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
  311. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
  312. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
  313. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
  314. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
  315. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
  316. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
  317. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  318. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
  319. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
  320. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
  321. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  322. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
  323. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
  324. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
  325. package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
  326. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
  327. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
  328. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
  329. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -43
  330. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +41 -219
  331. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
  332. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +3 -3
  333. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
  334. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
  335. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
  336. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
  337. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
  338. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
  339. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +1 -19
  340. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
  341. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  342. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  343. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  344. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  345. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +5 -1
  346. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +4 -51
  347. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +30 -44
  348. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
  349. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
  350. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
  351. package/runtime/runtime.json +1 -1
  352. package/runtime/server.ts +1 -0
  353. package/runtime/stations.json +37 -0
  354. package/dist/prompt.js +0 -10
  355. package/dist/store.js +0 -70
  356. package/runtime/node_modules/@metro-labs/discord/src/station.ts +0 -19
  357. package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +0 -195
  358. package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +0 -292
  359. package/runtime/node_modules/@metro-labs/discord/src/voice.ts +0 -197
  360. package/runtime/node_modules/@metro-labs/discord/src/wire.ts +0 -5
  361. package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
  362. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
  363. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
  364. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -335
  365. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
  366. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -322
  367. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
  368. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
  369. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
  370. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
  371. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
  372. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
  373. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
  374. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  375. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
  376. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
  377. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
  378. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
  379. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
  380. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
  381. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
  382. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
  383. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
  384. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
  385. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
  386. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
  387. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
  388. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
  389. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
  390. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
  391. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +0 -364
  392. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
  393. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
  394. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
  395. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
  396. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
  397. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
  398. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
  399. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
  400. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
  401. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
  402. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
  403. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
  404. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
  405. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
  406. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
  407. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
  408. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
  409. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -3
  410. package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +0 -81
  411. package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +0 -254
  412. package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +0 -87
  413. package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +0 -141
  414. package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +0 -78
  415. package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +0 -46
  416. package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +0 -80
  417. package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +0 -75
  418. package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +0 -22
  419. package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +0 -24
  420. package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +0 -7
  421. package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +0 -1
  422. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
  423. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
  424. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
  425. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
  426. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
  427. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
  428. /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
  429. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
  430. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
  431. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
  432. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/api-base.ts +0 -0
  433. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/verify.ts +0 -0
  434. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
  435. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
  436. /package/runtime/node_modules/@metro-labs/{telegram → telegram-bot}/src/verify.ts +0 -0
@@ -0,0 +1,193 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import { ApiError } from '@metro-labs/http/api-error';
3
+ import { readJsonBody } from '@metro-labs/http/api-http';
4
+ import { isRecord } from '@metro-labs/core/is-record';
5
+ import { errMsg, log } from '@metro-labs/core/log';
6
+ import { beginLogin, finishLogin, readCodexCliAuth, type CodexTokens } from './codex-auth.js';
7
+ import { beginDeviceLogin, pollDeviceLogin } from './codex-device.js';
8
+ import { codexModels, currentTokens, sharedCodexState } from './codex.js';
9
+ import { beginLogin as beginGeminiLogin, exchangeCode as exchangeGeminiCode, userEmail, type GeminiTokens } from './gemini-auth.js';
10
+ import { onboard, parseGeminiProject } from './gemini-setup.js';
11
+ import { currentGeminiTokens, listGeminiModels, sharedGeminiState, type GeminiDeps } from './gemini.js';
12
+ import { openrouterCredits } from './openrouter.js';
13
+ import { lastServed } from './served.js';
14
+ import { geminiUsage, noteUsage, openrouterUsage, usageOf } from './usage.js';
15
+ import {
16
+ addConnection,
17
+ connectionOf,
18
+ setCodexAuth,
19
+ setGeminiAuth,
20
+ type Connection,
21
+ type ModelConfig,
22
+ type Provider,
23
+ } from './model-config.js';
24
+ import { asApiError, askedConnection, BODY_MAX, connectionFor, CREDITS_TTL_MS, settingsBody, type ModelApiDeps, type Route, type Store } from './model-store.js';
25
+
26
+
27
+ function connectionToFill(store: Store, req: IncomingMessage, provider: Provider): { cfg: ModelConfig; id: string } {
28
+ const asked = askedConnection(req);
29
+ const current = store.read();
30
+ if (asked !== '') {
31
+ if (connectionOf(current, asked)?.provider !== provider) throw new ApiError('no such connection', 404);
32
+ return { cfg: current, id: asked };
33
+ }
34
+ const cfg = addConnection(current, { provider });
35
+ return { cfg, id: cfg.connections.at(-1)?.id ?? '' };
36
+ }
37
+
38
+ const saved = (store: Store, cfg: ModelConfig, note: string, fields: Record<string, unknown>): unknown => {
39
+ store.write(cfg);
40
+ log.info(fields, note);
41
+ return settingsBody(cfg);
42
+ };
43
+
44
+ function keepCodex(store: Store, req: IncomingMessage, tokens: CodexTokens | null, note: string): unknown {
45
+ const { cfg, id } = connectionToFill(store, req, 'codex');
46
+ const next = setCodexAuth(cfg, id, tokens);
47
+ return saved(store, next, note, { connection: id, plan: tokens?.plan ?? null });
48
+ }
49
+
50
+ async function pollDevice(id: string, req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
51
+ const result = await pollDeviceLogin(id, deps.fetchImpl).catch(asApiError);
52
+ if (result.status !== 'done') return result;
53
+ return { status: 'done', settings: keepCodex(store, req, result.tokens, 'model-api: Codex connected by device code') };
54
+ }
55
+
56
+ const codexDepsFor = (deps: ModelApiDeps, store: Store, id: string): { issuer?: string; fetchImpl?: typeof fetch; base?: string; save: (connId: string, t: CodexTokens) => void } => ({
57
+ issuer: deps.issuer,
58
+ fetchImpl: deps.fetchImpl,
59
+ base: deps.codexBase,
60
+ save: (connId, t) => {
61
+ store.write(setCodexAuth(store.read(), connId === '' ? id : connId, t));
62
+ },
63
+ });
64
+
65
+ export const CODEX_ROUTES: Record<string, Route> = {
66
+ device: {
67
+ method: 'POST',
68
+ run: async (_req, deps) => {
69
+ const login = await beginDeviceLogin(deps.issuer, deps.fetchImpl).catch(asApiError);
70
+ return { id: login.id, user_code: login.userCode, verify_url: login.verifyUrl, interval: login.interval };
71
+ },
72
+ },
73
+ login: { method: 'POST', run: (_req, deps) => Promise.resolve({ url: beginLogin(deps.issuer).url }) },
74
+ callback: {
75
+ method: 'POST',
76
+ run: async (req, deps, store) => {
77
+ const body = await readJsonBody(req, BODY_MAX);
78
+ const raw = isRecord(body) && typeof body.url === 'string' ? body.url : '';
79
+ const tokens = await finishLogin(raw, deps.issuer, deps.fetchImpl).catch(asApiError);
80
+ return keepCodex(store, req, tokens, 'model-api: Codex connected');
81
+ },
82
+ },
83
+ import: {
84
+ method: 'POST',
85
+ run: (_req, deps, store) => {
86
+ let tokens;
87
+ try {
88
+ tokens = readCodexCliAuth(deps.codexHome);
89
+ } catch (err) {
90
+ asApiError(err);
91
+ }
92
+ return Promise.resolve(keepCodex(store, _req, tokens, 'model-api: Codex CLI login imported'));
93
+ },
94
+ },
95
+ models: {
96
+ method: 'GET',
97
+ run: async (req, deps, store) => {
98
+ const conn = connectionFor(store.read(), req, 'codex');
99
+ if (conn.codex === null) throw new ApiError('this connection is not signed in yet', 400);
100
+ const codexDeps = codexDepsFor(deps, store, conn.id);
101
+ const auth = await currentTokens(conn, codexDeps, sharedCodexState).catch(asApiError);
102
+ return { models: await codexModels(auth, codexDeps).catch(asApiError) };
103
+ },
104
+ },
105
+ };
106
+
107
+ const geminiDeps = (deps: ModelApiDeps, store: Store): GeminiDeps => ({
108
+ base: deps.geminiBase,
109
+ tokenBase: deps.geminiTokenBase,
110
+ fetchImpl: deps.fetchImpl,
111
+ save: (id, t) => {
112
+ store.write(setGeminiAuth(store.read(), id, t));
113
+ },
114
+ });
115
+
116
+ async function geminiModelsOf(conn: Connection, deps: ModelApiDeps, store: Store): Promise<Awaited<ReturnType<typeof listGeminiModels>>> {
117
+ if (conn.gemini === null) throw new ApiError('this connection is not signed in yet', 400);
118
+ const tokens = await currentGeminiTokens(conn, geminiDeps(deps, store), sharedGeminiState);
119
+ return listGeminiModels(tokens, geminiDeps(deps, store));
120
+ }
121
+
122
+ function projectOf(body: unknown): string | null {
123
+ try {
124
+ return parseGeminiProject(isRecord(body) ? body.project : null);
125
+ } catch (err) {
126
+ return asApiError(err);
127
+ }
128
+ }
129
+
130
+ async function connectGemini(req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
131
+ const body = await readJsonBody(req, BODY_MAX);
132
+ const code = isRecord(body) && typeof body.code === 'string' ? body.code : '';
133
+ const state = isRecord(body) && typeof body.state === 'string' ? body.state : '';
134
+ const project = projectOf(body);
135
+ const tokens = await exchangeGeminiCode(code, state, deps.geminiTokenBase, deps.fetchImpl).catch(asApiError);
136
+ const email = await userEmail(tokens, deps.geminiUserBase, deps.fetchImpl);
137
+ const onboarded = await onboard(tokens, project, deps.geminiBase, deps.fetchImpl).catch(asApiError);
138
+ const { cfg, id } = connectionToFill(store, req, 'gemini');
139
+ const full: GeminiTokens = { ...tokens, email, project: onboarded.project, tier: onboarded.tier };
140
+ return saved(store, setGeminiAuth(cfg, id, full), 'model-api: Gemini connected', { connection: id, tier: onboarded.tier });
141
+ }
142
+
143
+ export const GEMINI_ROUTES: Record<string, Route> = {
144
+ login: { method: 'POST', run: (_req, deps) => Promise.resolve(beginGeminiLogin(deps.geminiAuthBase)) },
145
+ code: { method: 'POST', run: connectGemini },
146
+ models: {
147
+ method: 'GET',
148
+ run: async (req, deps, store) => ({ models: (await geminiModelsOf(connectionFor(store.read(), req, 'gemini'), deps, store).catch(asApiError)).map((m) => m.id) }),
149
+ },
150
+ };
151
+
152
+ export function codexDeviceRoute(id: string, method: string | undefined): Route | number {
153
+ return method === 'GET' ? { method: 'GET', run: (req, deps, store) => pollDevice(id, req, deps, store) } : 405;
154
+ }
155
+
156
+ async function refreshOne(conn: Connection, deps: ModelApiDeps, now: number): Promise<void> {
157
+ if (conn.apiKey === '') return;
158
+ const seen = usageOf(conn.id);
159
+ if (seen !== undefined && now - Date.parse(seen.at) < CREDITS_TTL_MS) return;
160
+ try {
161
+ const credits = await openrouterCredits(conn.apiKey, deps.openrouterBase, deps.fetchImpl);
162
+ noteUsage(conn.id, openrouterUsage(credits.total, credits.spent, new Date(now)));
163
+ } catch (err) {
164
+ log.warn({ err: errMsg(err) }, 'model-api: could not read the OpenRouter credits');
165
+ }
166
+ }
167
+
168
+ const modelsInUse = (conn: Connection): Set<string> => {
169
+ const served = lastServed();
170
+ return new Set([conn.model, served?.connection === conn.id ? served.model : ''].filter((m) => m !== ''));
171
+ };
172
+
173
+ async function refreshQuota(conn: Connection, deps: ModelApiDeps, store: Store, now: number): Promise<void> {
174
+ if (conn.gemini === null) return;
175
+ const inUse = modelsInUse(conn);
176
+ const seen = usageOf(conn.id);
177
+ const covers = seen !== undefined && [...inUse].every((id) => seen.windows.some((w) => w.label === id));
178
+ if (seen !== undefined && covers && now - Date.parse(seen.at) < CREDITS_TTL_MS) return;
179
+ try {
180
+ const rows = (await geminiModelsOf(conn, deps, store)).filter((m) => inUse.has(m.id));
181
+ const usage = geminiUsage(rows, new Date(now));
182
+ if (usage !== null) noteUsage(conn.id, usage);
183
+ } catch (err) {
184
+ log.warn({ err: errMsg(err) }, 'model-api: could not read the Gemini quota');
185
+ }
186
+ }
187
+
188
+ export async function refreshUsage(deps: ModelApiDeps, store: Store, now = Date.now()): Promise<void> {
189
+ for (const conn of store.read().connections) {
190
+ if (conn.provider === 'openrouter') await refreshOne(conn, deps, now);
191
+ if (conn.provider === 'gemini') await refreshQuota(conn, deps, store, now);
192
+ }
193
+ }
@@ -0,0 +1,70 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import { ApiError } from '@metro-labs/http/api-error';
3
+ import { CodexAuthError } from './codex-auth.js';
4
+ import { GeminiAuthError } from './gemini-auth.js';
5
+ import { GatewayError } from './forward.js';
6
+ import { lastServed } from './served.js';
7
+ import { usageSeen } from './usage.js';
8
+ import { ModelConfigError, connectionOf, publicModelConfig, routedConnection, type Connection, type ModelConfig, type Provider } from './model-config.js';
9
+ import type { SetupDeps } from '../claude/setup.js';
10
+
11
+ export interface ModelApiDeps {
12
+ read?: () => ModelConfig;
13
+ write?: (cfg: ModelConfig) => void;
14
+ issuer?: string;
15
+ fetchImpl?: typeof fetch;
16
+ codexHome?: string;
17
+ codexBase?: string;
18
+ geminiAuthBase?: string;
19
+ geminiTokenBase?: string;
20
+ geminiUserBase?: string;
21
+ geminiBase?: string;
22
+ openrouterBase?: string;
23
+ anthropicBase?: string;
24
+ bedrockControlBase?: string;
25
+ setup?: SetupDeps;
26
+ restartSession?: () => boolean;
27
+ }
28
+
29
+ export interface Store {
30
+ read: () => ModelConfig;
31
+ write: (cfg: ModelConfig) => void;
32
+ }
33
+
34
+ type Handler = (req: IncomingMessage, deps: ModelApiDeps, store: Store) => Promise<unknown>;
35
+
36
+ export interface Route {
37
+ method: 'GET' | 'POST';
38
+ run: Handler;
39
+ }
40
+
41
+ export const BODY_MAX = 16 * 1024;
42
+ export const CREDITS_TTL_MS = 5 * 60_000;
43
+
44
+ export const settingsBody = (cfg: ModelConfig): Record<string, unknown> => ({
45
+ ...publicModelConfig(cfg),
46
+ lastServed: lastServed(),
47
+ usage: usageSeen(),
48
+ });
49
+
50
+ export function asApiError(err: unknown): never {
51
+ if (err instanceof ModelConfigError || err instanceof CodexAuthError || err instanceof GeminiAuthError) throw new ApiError(err.message, 400);
52
+ if (err instanceof GatewayError) throw new ApiError(err.message, err.status >= 400 && err.status < 500 ? 400 : 502);
53
+ throw err;
54
+ }
55
+
56
+ export const askedConnection = (req: IncomingMessage): string => new URL(req.url ?? '', 'http://metro').searchParams.get('connection') ?? '';
57
+
58
+ export function connectionFor(cfg: ModelConfig, req: IncomingMessage, provider: Provider): Connection {
59
+ const asked = askedConnection(req);
60
+ if (asked !== '') {
61
+ const found = connectionOf(cfg, asked);
62
+ if (found?.provider !== provider) throw new ApiError('no such connection', 404);
63
+ return found;
64
+ }
65
+ const routed = routedConnection(cfg);
66
+ if (routed?.provider === provider) return routed;
67
+ const first = cfg.connections.find((c) => c.provider === provider);
68
+ if (first === undefined) throw new ApiError(`no ${provider} connection yet`, 400);
69
+ return first;
70
+ }
@@ -0,0 +1,90 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { GatewayError } from './forward.js';
3
+ import { stringOf } from '@metro-labs/http/api-http';
4
+
5
+ export const OPENROUTER_BASE = 'https://openrouter.ai/api';
6
+ const MODELS_MAX = 2000;
7
+
8
+ export interface OpenRouterModel {
9
+ id: string;
10
+ name: string;
11
+ prompt: number | null;
12
+ completion: number | null;
13
+ created: number | null;
14
+ }
15
+
16
+
17
+ function price(raw: unknown): number | null {
18
+ const value = typeof raw === 'string' ? Number(raw) : typeof raw === 'number' ? raw : Number.NaN;
19
+ return Number.isFinite(value) && value >= 0 ? value : null;
20
+ }
21
+
22
+ function released(raw: unknown): number | null {
23
+ return typeof raw === 'number' && Number.isFinite(raw) && raw > 0 ? raw : null;
24
+ }
25
+
26
+ function modelOf(entry: unknown): OpenRouterModel | null {
27
+ if (!isRecord(entry)) return null;
28
+ const id = stringOf(entry.id);
29
+ if (id === '') return null;
30
+ const pricing = isRecord(entry.pricing) ? entry.pricing : {};
31
+ return {
32
+ id,
33
+ name: stringOf(entry.name) || id,
34
+ prompt: price(pricing.prompt),
35
+ completion: price(pricing.completion),
36
+ created: released(entry.created),
37
+ };
38
+ }
39
+
40
+ function newestFirst(a: OpenRouterModel, b: OpenRouterModel): number {
41
+ const when = (b.created ?? 0) - (a.created ?? 0);
42
+ return when !== 0 ? when : a.id.localeCompare(b.id);
43
+ }
44
+
45
+ export async function openrouterModels(base = OPENROUTER_BASE, fetchImpl: typeof fetch = fetch): Promise<OpenRouterModel[]> {
46
+ const res = await fetchImpl(`${base}/v1/models`, { headers: { accept: 'application/json' }, redirect: 'manual' });
47
+ if (!res.ok) throw new GatewayError(res.status, 'api_error', `OpenRouter would not list its models (${String(res.status)})`);
48
+ const body: unknown = await res.json();
49
+ const data = typeof body === 'object' && body !== null ? (body as { data?: unknown }).data : undefined;
50
+ if (!Array.isArray(data)) throw new GatewayError(502, 'api_error', 'OpenRouter answered with no model list');
51
+ return data
52
+ .map(modelOf)
53
+ .filter((model): model is OpenRouterModel => model !== null)
54
+ .sort(newestFirst)
55
+ .slice(0, MODELS_MAX);
56
+ }
57
+
58
+ export interface OpenRouterCredits {
59
+ total: number;
60
+ spent: number;
61
+ }
62
+
63
+ export async function openrouterCredits(
64
+ apiKey: string,
65
+ base = OPENROUTER_BASE,
66
+ fetchImpl: typeof fetch = fetch,
67
+ ): Promise<OpenRouterCredits> {
68
+ const res = await fetchImpl(`${base}/v1/credits`, {
69
+ headers: { accept: 'application/json', authorization: `Bearer ${apiKey}` },
70
+ redirect: 'manual',
71
+ });
72
+ if (!res.ok) throw new GatewayError(res.status, 'api_error', `OpenRouter would not report the credits (${String(res.status)})`);
73
+ const body: unknown = await res.json();
74
+ const data = isRecord(body) && isRecord(body.data) ? body.data : {};
75
+ const total = Number(data.total_credits);
76
+ const spent = Number(data.total_usage);
77
+ if (!Number.isFinite(total) || !Number.isFinite(spent)) throw new GatewayError(502, 'api_error', 'OpenRouter answered with no credit figures');
78
+ return { total, spent };
79
+ }
80
+
81
+ export async function openrouterZdrModels(base = OPENROUTER_BASE, fetchImpl: typeof fetch = fetch): Promise<string[]> {
82
+ const res = await fetchImpl(`${base}/v1/endpoints/zdr`, { headers: { accept: 'application/json' }, redirect: 'manual' });
83
+ if (!res.ok) throw new GatewayError(res.status, 'api_error', `OpenRouter would not list its zero data retention endpoints (${String(res.status)})`);
84
+ const body: unknown = await res.json();
85
+ const data = typeof body === 'object' && body !== null ? (body as { data?: unknown }).data : undefined;
86
+ if (!Array.isArray(data)) throw new GatewayError(502, 'api_error', 'OpenRouter answered with no endpoint list');
87
+ const ids = new Set<string>();
88
+ for (const entry of data) if (isRecord(entry) && stringOf(entry.model_id) !== '') ids.add(stringOf(entry.model_id));
89
+ return [...ids].sort();
90
+ }
@@ -0,0 +1,36 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+
3
+ const PENDING_TTL_MS = 10 * 60_000;
4
+
5
+ const b64url = (buf: Buffer): string => buf.toString('base64url');
6
+
7
+ export function newPkce(): { verifier: string; challenge: string } {
8
+ const verifier = b64url(randomBytes(32));
9
+ return { verifier, challenge: b64url(createHash('sha256').update(verifier).digest()) };
10
+ }
11
+
12
+ export class PendingLogins {
13
+ private readonly pending = new Map<string, { verifier: string; at: number }>();
14
+
15
+ constructor(private readonly stateBytes: number) {}
16
+
17
+ private sweep(now: number): void {
18
+ for (const [state, entry] of this.pending) if (now - entry.at > PENDING_TTL_MS) this.pending.delete(state);
19
+ }
20
+
21
+ begin(now: number): { state: string; challenge: string } {
22
+ this.sweep(now);
23
+ const { verifier, challenge } = newPkce();
24
+ const state = b64url(randomBytes(this.stateBytes));
25
+ this.pending.set(state, { verifier, at: now });
26
+ return { state, challenge };
27
+ }
28
+
29
+ take(state: string, now: number): string | null {
30
+ this.sweep(now);
31
+ const entry = this.pending.get(state);
32
+ if (entry === undefined) return null;
33
+ this.pending.delete(state);
34
+ return entry.verifier;
35
+ }
36
+ }
@@ -0,0 +1,66 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { GatewayError } from './forward.js';
3
+ import type { Connection } from './model-config.js';
4
+ import { stringOf } from '@metro-labs/http/api-http';
5
+
6
+ const ANTHROPIC_API = 'https://api.anthropic.com';
7
+ const ANTHROPIC_VERSION = '2023-06-01';
8
+ const LIST_MAX = 1000;
9
+
10
+ export interface ProviderModel {
11
+ id: string;
12
+ name: string;
13
+ }
14
+
15
+ const KNOWN_CLAUDE: ProviderModel[] = [
16
+ { id: 'claude-fable-5-1', name: 'Claude Fable 5.1' },
17
+ { id: 'claude-opus-5-5', name: 'Claude Opus 5.5' },
18
+ { id: 'claude-opus-5', name: 'Claude Opus 5' },
19
+ { id: 'claude-sonnet-5', name: 'Claude Sonnet 5' },
20
+ { id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5' },
21
+ ];
22
+
23
+
24
+ export async function anthropicModels(settings: Connection, base = ANTHROPIC_API, fetchImpl: typeof fetch = fetch): Promise<ProviderModel[]> {
25
+ if (settings.apiKey === '') return KNOWN_CLAUDE;
26
+ const res = await fetchImpl(`${base}/v1/models?limit=${String(LIST_MAX)}`, {
27
+ headers: { 'x-api-key': settings.apiKey, 'anthropic-version': ANTHROPIC_VERSION, accept: 'application/json' },
28
+ redirect: 'manual',
29
+ });
30
+ if (!res.ok) throw new GatewayError(res.status, 'api_error', `Anthropic would not list its models with the stored key (${String(res.status)})`);
31
+ const body: unknown = await res.json();
32
+ const data = isRecord(body) && Array.isArray(body.data) ? body.data : null;
33
+ if (data === null) throw new GatewayError(502, 'api_error', 'Anthropic answered with no model list');
34
+ return data
35
+ .flatMap((entry) => (isRecord(entry) && stringOf(entry.id) !== '' ? [{ id: stringOf(entry.id), name: stringOf(entry.display_name) || stringOf(entry.id) }] : []))
36
+ .sort((a, b) => a.id.localeCompare(b.id));
37
+ }
38
+
39
+ export const bedrockControlBase = (region: string): string => `https://bedrock.${region}.amazonaws.com`;
40
+
41
+ export async function bedrockModels(settings: Connection, base?: string, fetchImpl: typeof fetch = fetch): Promise<ProviderModel[]> {
42
+ if (settings.region === '') throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs a region before its models can be listed');
43
+ if (settings.apiKey === '') throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs an API key before its models can be listed');
44
+ const res = await fetchImpl(`${base ?? bedrockControlBase(settings.region)}/inference-profiles?maxResults=${String(LIST_MAX)}`, {
45
+ headers: { authorization: `Bearer ${settings.apiKey}`, accept: 'application/json' },
46
+ redirect: 'manual',
47
+ });
48
+ if (!res.ok)
49
+ throw new GatewayError(
50
+ res.status,
51
+ 'api_error',
52
+ `Bedrock would not list its inference profiles (${String(res.status)}); the stored key may cover the runtime only, so type the model id instead`,
53
+ );
54
+ const body: unknown = await res.json();
55
+ const list = isRecord(body) && Array.isArray(body.inferenceProfileSummaries) ? body.inferenceProfileSummaries : null;
56
+ if (list === null) throw new GatewayError(502, 'api_error', 'Bedrock answered with no inference profile list');
57
+ return list
58
+ .flatMap((entry) => {
59
+ if (!isRecord(entry)) return [];
60
+ const id = stringOf(entry.inferenceProfileId);
61
+ const status = stringOf(entry.status);
62
+ if (id === '' || !id.includes('anthropic') || (status !== '' && status !== 'ACTIVE')) return [];
63
+ return [{ id, name: stringOf(entry.inferenceProfileName) || id }];
64
+ })
65
+ .sort((a, b) => a.id.localeCompare(b.id));
66
+ }
@@ -0,0 +1,18 @@
1
+ export interface Served {
2
+ connection: string;
3
+ provider: string;
4
+ model: string;
5
+ at: string;
6
+ }
7
+
8
+ let last: Served | null = null;
9
+
10
+ export const noteServed = (served: Served): void => {
11
+ last = served;
12
+ };
13
+
14
+ export const lastServed = (): Served | null => last;
15
+
16
+ export const forgetServed = (): void => {
17
+ last = null;
18
+ };
@@ -0,0 +1,120 @@
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 { assembleMessage, SseParser, type SseEvent } from './frames.js';
5
+ import { GatewayError, relayFrames, type Watch } from './forward.js';
6
+ import { UsageScanner } from './usage.js';
7
+
8
+ const STATUS_OF: Record<string, number> = { rate_limit_error: 429, invalid_request_error: 400, permission_error: 403, overloaded_error: 529 };
9
+
10
+ interface Slot<T> {
11
+ refreshing: Promise<T> | null;
12
+ latest: T | null;
13
+ }
14
+
15
+ export type TokenState<T> = Map<string, Slot<T>>;
16
+
17
+ export interface TokenSource<T extends { savedAt: string }> {
18
+ label: string;
19
+ stale: (tokens: T) => boolean;
20
+ refresh: (tokens: T) => Promise<T>;
21
+ save: (id: string, tokens: T) => void;
22
+ }
23
+
24
+ function slotFor<T>(state: TokenState<T>, id: string): Slot<T> {
25
+ const found = state.get(id);
26
+ if (found !== undefined) return found;
27
+ const made: Slot<T> = { refreshing: null, latest: null };
28
+ state.set(id, made);
29
+ return made;
30
+ }
31
+
32
+ const newerThan = (a: { savedAt: string }, b: { savedAt: string }): boolean => Date.parse(a.savedAt) > Date.parse(b.savedAt);
33
+
34
+ export function refreshed<T extends { savedAt: string }>(state: TokenState<T>, id: string, tokens: T, source: TokenSource<T>): Promise<T> {
35
+ const slot = slotFor(state, id);
36
+ const latest = slot.latest;
37
+ if (latest !== null && newerThan(latest, tokens) && !source.stale(latest)) return Promise.resolve(latest);
38
+ if (slot.refreshing !== null) return slot.refreshing;
39
+ const run = source
40
+ .refresh(tokens)
41
+ .then((fresh) => {
42
+ source.save(id, fresh);
43
+ slot.latest = fresh;
44
+ return fresh;
45
+ })
46
+ .catch((err: unknown) => {
47
+ throw new GatewayError(403, 'permission_error', `${source.label} sign-in expired (${errMsg(err)}): connect again on the Model page`);
48
+ })
49
+ .finally(() => {
50
+ slot.refreshing = null;
51
+ });
52
+ slot.refreshing = run;
53
+ return run;
54
+ }
55
+
56
+ export function currentOf<T extends { savedAt: string }>(state: TokenState<T>, id: string, tokens: T | null, source: TokenSource<T>, missing: string): Promise<T> {
57
+ if (tokens === null) throw new GatewayError(400, 'invalid_request_error', missing);
58
+ return source.stale(tokens) ? refreshed(state, id, tokens, source) : Promise.resolve(tokens);
59
+ }
60
+
61
+ export async function reach<T>(current: () => Promise<T>, again: (tokens: T) => Promise<T>, send: (tokens: T) => Promise<Response>): Promise<Response> {
62
+ const tokens = await current();
63
+ const upstream = await send(tokens);
64
+ if (upstream.status !== 401) return upstream;
65
+ await upstream.body?.cancel();
66
+ return send(await again(tokens));
67
+ }
68
+
69
+ export function errorKind(status: number, invalid: readonly number[] = [400, 404]): string {
70
+ if (status === 401 || status === 403) return 'permission_error';
71
+ if (status === 429) return 'rate_limit_error';
72
+ if (invalid.includes(status)) return 'invalid_request_error';
73
+ return 'api_error';
74
+ }
75
+
76
+ export function sessionHeader(req: IncomingMessage): string {
77
+ const raw = req.headers['x-claude-code-session-id'];
78
+ return (Array.isArray(raw) ? raw[0] : raw)?.trim() ?? '';
79
+ }
80
+
81
+ interface Translator {
82
+ readonly finished: boolean;
83
+ close: (message?: string) => string;
84
+ }
85
+
86
+ export function relayTranslated(
87
+ upstream: Response,
88
+ res: ServerResponse,
89
+ watch: Watch,
90
+ key: string,
91
+ translator: Translator,
92
+ onEvent: (raw: SseEvent) => string,
93
+ ): Promise<void> {
94
+ const parser = new SseParser();
95
+ const decoder = new TextDecoder();
96
+ return relayFrames(upstream, res, watch, key, {
97
+ push: (chunk, emit) => {
98
+ for (const raw of parser.push(decoder.decode(chunk, { stream: true }))) emit(onEvent(raw));
99
+ },
100
+ close: () => translator.close(),
101
+ idle: (message) => (translator.finished ? '' : translator.close(message)),
102
+ });
103
+ }
104
+
105
+ export function answerWhole(res: ServerResponse, frames: string, key: string): void {
106
+ const scanner = new UsageScanner(key);
107
+ scanner.feed(frames);
108
+ scanner.done();
109
+ const message = assembleMessage(frames);
110
+ const kind = isRecord(message.error) ? String(message.error.type) : '';
111
+ res.writeHead(message.type === 'error' ? (STATUS_OF[kind] ?? 502) : 200, { 'content-type': 'application/json' });
112
+ res.end(JSON.stringify(message));
113
+ }
114
+
115
+ export const estimateTokens = (body: Record<string, unknown>): number => Math.ceil(JSON.stringify(body).length / 4);
116
+
117
+ export function countTokens(res: ServerResponse, body: Record<string, unknown>): void {
118
+ res.writeHead(200, { 'content-type': 'application/json' });
119
+ res.end(JSON.stringify({ input_tokens: estimateTokens(body) }));
120
+ }
@@ -0,0 +1,14 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { stringOf } from '@metro-labs/http/api-http';
3
+
4
+ const IMAGE_NOTE = '[an image was attached here; this model cannot see it]';
5
+
6
+ export const nonEmpty = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
7
+
8
+ const partText = (part: Record<string, unknown>): string => (part.type === 'text' ? stringOf(part.text) : part.type === 'image' ? IMAGE_NOTE : '');
9
+
10
+ export function resultText(block: Record<string, unknown>): string {
11
+ const content = block.content;
12
+ const text = typeof content === 'string' ? content : Array.isArray(content) ? content.filter(isRecord).map(partText).filter((t) => t !== '').join('\n') : '';
13
+ return block.is_error === true ? `[tool error] ${text}` : text;
14
+ }