@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.
- package/dist/api.js +9 -101
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +152 -0
- package/dist/cli.js +52 -109
- package/dist/control.js +47 -75
- package/dist/hold.js +210 -0
- package/dist/local.js +43 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +44 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +58 -0
- package/dist/runtime-install.js +96 -0
- package/dist/runtime.js +29 -52
- package/dist/serve.js +157 -0
- package/dist/service.js +304 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +13 -1
- package/package.json +2 -23
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
- package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
- package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
- package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
- package/runtime/marketplace/plugin/orchestrator.md +26 -0
- package/runtime/node_modules/@metro-labs/core/package.json +47 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/events.ts +11 -7
- package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +24 -28
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +83 -0
- package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
- package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/station-runtime.ts +6 -11
- package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +84 -0
- package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
- package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
- package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +89 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +80 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +144 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +89 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +142 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
- package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-map.ts → daemon/src/agents/map.ts} +58 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +150 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +218 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +36 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +70 -45
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +355 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +284 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +273 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
- package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +347 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +195 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +128 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +274 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +224 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +199 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +278 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +193 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +24 -79
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +33 -42
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +88 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +68 -119
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
- package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/routes}/http.ts +59 -51
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +205 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +9 -36
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +75 -9
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +22 -6
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +9 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +147 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/package.json +3 -5
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/accounts.ts +8 -28
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/actions.ts +19 -187
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/attachments.ts +3 -3
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/format.ts +31 -72
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group-actions.ts +2 -2
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group.ts +2 -2
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/index.ts +15 -21
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/members.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/send-files.ts +12 -13
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +23 -0
- package/runtime/node_modules/@metro-labs/http/package.json +29 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
- package/runtime/node_modules/@metro-labs/{telegram-user → outlook}/package.json +2 -5
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +93 -0
- package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
- package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +97 -0
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +150 -0
- package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +53 -0
- package/runtime/node_modules/@metro-labs/outlook/src/config.ts +36 -0
- package/runtime/node_modules/@metro-labs/outlook/src/format.ts +140 -0
- package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +60 -0
- package/runtime/node_modules/@metro-labs/outlook/src/html.ts +72 -0
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +104 -0
- package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
- package/runtime/node_modules/@metro-labs/outlook/src/login.ts +150 -0
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +41 -0
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +116 -0
- package/runtime/node_modules/@metro-labs/outlook/src/query.ts +119 -0
- package/runtime/node_modules/@metro-labs/outlook/src/state.ts +43 -0
- package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
- package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +70 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +3 -2
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +38 -96
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +214 -253
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +67 -108
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/client.ts +6 -6
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +101 -147
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/group.ts +4 -4
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/history.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +6 -102
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/login.ts +20 -20
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +64 -151
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +61 -33
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/reactions.ts +3 -3
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +11 -1
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +5 -47
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +86 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +240 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +170 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +90 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +21 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +95 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/files.ts +47 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -0
- package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +89 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
- package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
- package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
- package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -43
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +41 -219
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +3 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +1 -19
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +5 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +4 -51
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +30 -44
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
- package/runtime/runtime.json +1 -1
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +37 -0
- package/dist/prompt.js +0 -10
- package/dist/store.js +0 -70
- package/runtime/node_modules/@metro-labs/discord/src/station.ts +0 -19
- package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +0 -195
- package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +0 -292
- package/runtime/node_modules/@metro-labs/discord/src/voice.ts +0 -197
- package/runtime/node_modules/@metro-labs/discord/src/wire.ts +0 -5
- package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
- package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -335
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -322
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
- package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
- package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +0 -364
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
- package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
- package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
- package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
- package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
- package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
- package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -3
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +0 -81
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +0 -254
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +0 -87
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +0 -141
- package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +0 -78
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +0 -46
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +0 -80
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +0 -75
- package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +0 -24
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +0 -7
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +0 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/api-base.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/verify.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
- /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
|
+
}
|