@stage-labs/metro 0.1.0-beta.23 → 0.1.0-beta.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +9 -101
- package/dist/background.js +123 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +197 -0
- package/dist/cli.js +52 -110
- package/dist/control.js +19 -103
- package/dist/hold.js +210 -0
- package/dist/local.js +43 -0
- package/dist/mcp-config.js +25 -0
- package/dist/memory.js +222 -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/MEMORY.md +78 -0
- package/runtime/marketplace/plugin/METRO.md +26 -0
- package/runtime/marketplace/plugin/STAGE.md +42 -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/node_modules/@metro-labs/core/package.json +51 -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 +22 -26
- 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 +14 -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/mail-text.ts +72 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/mail-trust.ts +29 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +212 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +126 -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 +49 -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/crontab.ts +44 -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 +39 -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 +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -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/daemon/src/agents/map.ts +133 -0
- 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 +223 -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 +75 -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 +380 -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-routine.ts +114 -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 +295 -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 +346 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/usage-probe.ts +127 -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/transfer-api.ts +116 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-crypto.ts +44 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-store.ts +48 -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 +214 -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 +209 -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 +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +93 -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 +307 -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 +235 -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 +203 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +285 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-lists.ts +57 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +161 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +104 -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 +118 -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 +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/tool-search.ts +126 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage-refresh.ts +102 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +348 -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 +34 -80
- 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 +88 -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 +71 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +47 -44
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +93 -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 +64 -113
- 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 +63 -51
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +209 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +69 -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/cloudwatch.ts +178 -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-browser.ts +102 -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 +5 -31
- 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 +69 -3
- package/runtime/node_modules/@metro-labs/{mcp/src/db → daemon/src/stations}/materialize.ts +60 -179
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +20 -2
- 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-bot/package.json +2 -4
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +3 -23
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +19 -187
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +26 -67
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +1 -1
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +8 -14
- package/runtime/node_modules/@metro-labs/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-bot/src/send-files.ts +12 -13
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +5 -1
- package/runtime/node_modules/@metro-labs/gmail/package.json +25 -0
- package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +81 -0
- package/runtime/node_modules/@metro-labs/gmail/src/actions.ts +14 -0
- package/runtime/node_modules/@metro-labs/gmail/src/api.ts +31 -0
- package/runtime/node_modules/@metro-labs/gmail/src/attachments.ts +35 -0
- package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +105 -0
- package/runtime/node_modules/@metro-labs/gmail/src/config.ts +13 -0
- package/runtime/node_modules/@metro-labs/gmail/src/inbound.ts +96 -0
- package/runtime/node_modules/@metro-labs/gmail/src/index.ts +12 -0
- package/runtime/node_modules/@metro-labs/gmail/src/login.ts +37 -0
- package/runtime/node_modules/@metro-labs/gmail/src/message.ts +125 -0
- package/runtime/node_modules/@metro-labs/gmail/src/mime.ts +70 -0
- package/runtime/node_modules/@metro-labs/gmail/src/outbound.ts +124 -0
- package/runtime/node_modules/@metro-labs/gmail/src/query.ts +99 -0
- package/runtime/node_modules/@metro-labs/gmail/src/state.ts +24 -0
- package/runtime/node_modules/@metro-labs/gmail/src/station.ts +15 -0
- package/runtime/node_modules/@metro-labs/gmail/src/trust.ts +42 -0
- package/runtime/node_modules/@metro-labs/http/package.json +30 -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/sigv4.ts +90 -0
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
- package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +78 -0
- package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
- package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +81 -0
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +120 -0
- package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +42 -0
- package/runtime/node_modules/@metro-labs/outlook/src/config.ts +23 -0
- package/runtime/node_modules/@metro-labs/outlook/src/format.ts +59 -0
- package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +55 -0
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +96 -0
- package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
- package/runtime/node_modules/@metro-labs/outlook/src/login.ts +129 -0
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +31 -0
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +103 -0
- package/runtime/node_modules/@metro-labs/outlook/src/query.ts +84 -0
- package/runtime/node_modules/@metro-labs/outlook/src/state.ts +28 -0
- package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
- package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +41 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +1 -3
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +5 -24
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +8 -6
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +2 -2
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +19 -34
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +3 -3
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +13 -34
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +4 -43
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +2 -3
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +2 -2
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +4 -1
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +1 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +7 -41
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +22 -77
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +46 -48
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +15 -15
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +6 -82
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +5 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +20 -12
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +96 -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 +87 -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 +40 -53
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +72 -220
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +27 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +11 -7
- 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 +57 -33
- 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/resolve.ts +44 -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 +7 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +22 -58
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +55 -48
- 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 +38 -0
- package/dist/prompt.js +0 -61
- package/dist/store.js +0 -70
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +0 -195
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +0 -292
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +0 -197
- package/runtime/node_modules/@metro-labs/discord-bot/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 -341
- 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 -320
- 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/agent-map.ts +0 -71
- 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/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/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -3
- 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/{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
|
@@ -0,0 +1,161 @@
|
|
|
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 { 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 { listCache } from './model-lists.js';
|
|
13
|
+
import { forgetReported } from './usage.js';
|
|
14
|
+
import {
|
|
15
|
+
addConnection,
|
|
16
|
+
connectionOf,
|
|
17
|
+
setCodexAuth,
|
|
18
|
+
setGeminiAuth,
|
|
19
|
+
type Connection,
|
|
20
|
+
type ModelConfig,
|
|
21
|
+
type Provider,
|
|
22
|
+
} from './model-config.js';
|
|
23
|
+
import { asApiError, askedConnection, BODY_MAX, connectionFor, settingsBody, type ModelApiDeps, type Route, type Store } from './model-store.js';
|
|
24
|
+
|
|
25
|
+
function connectionToFill(store: Store, req: IncomingMessage, provider: Provider): { cfg: ModelConfig; id: string } {
|
|
26
|
+
const asked = askedConnection(req);
|
|
27
|
+
const current = store.read();
|
|
28
|
+
if (asked !== '') {
|
|
29
|
+
if (connectionOf(current, asked)?.provider !== provider) throw new ApiError('no such connection', 404);
|
|
30
|
+
return { cfg: current, id: asked };
|
|
31
|
+
}
|
|
32
|
+
const cfg = addConnection(current, { provider });
|
|
33
|
+
return { cfg, id: cfg.connections.at(-1)?.id ?? '' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const saved = (store: Store, cfg: ModelConfig, note: string, fields: { connection: string } & Record<string, unknown>): unknown => {
|
|
37
|
+
store.write(cfg);
|
|
38
|
+
forgetReported(fields.connection);
|
|
39
|
+
log.info(fields, note);
|
|
40
|
+
return settingsBody(cfg);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function keepCodex(store: Store, req: IncomingMessage, tokens: CodexTokens | null, note: string): unknown {
|
|
44
|
+
const { cfg, id } = connectionToFill(store, req, 'codex');
|
|
45
|
+
const next = setCodexAuth(cfg, id, tokens);
|
|
46
|
+
return saved(store, next, note, { connection: id, plan: tokens?.plan ?? null });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function pollDevice(id: string, req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
|
|
50
|
+
const result = await pollDeviceLogin(id, deps.fetchImpl).catch(asApiError);
|
|
51
|
+
if (result.status !== 'done') return result;
|
|
52
|
+
return { status: 'done', settings: keepCodex(store, req, result.tokens, 'model-api: Codex connected by device code') };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const codexDepsFor = (deps: ModelApiDeps, store: Store, id: string): { issuer?: string; fetchImpl?: typeof fetch; base?: string; save: (connId: string, t: CodexTokens) => void } => ({
|
|
56
|
+
issuer: deps.issuer,
|
|
57
|
+
fetchImpl: deps.fetchImpl,
|
|
58
|
+
base: deps.codexBase,
|
|
59
|
+
save: (connId, t) => {
|
|
60
|
+
store.write(setCodexAuth(store.read(), connId === '' ? id : connId, t));
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const CODEX_ROUTES: Record<string, Route> = {
|
|
65
|
+
device: {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
run: async (_req, deps) => {
|
|
68
|
+
const login = await beginDeviceLogin(deps.issuer, deps.fetchImpl).catch(asApiError);
|
|
69
|
+
return { id: login.id, user_code: login.userCode, verify_url: login.verifyUrl, interval: login.interval };
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
login: { method: 'POST', run: (_req, deps) => Promise.resolve({ url: beginLogin(deps.issuer).url }) },
|
|
73
|
+
callback: {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
run: async (req, deps, store) => {
|
|
76
|
+
const body = await readJsonBody(req, BODY_MAX);
|
|
77
|
+
const raw = isRecord(body) && typeof body.url === 'string' ? body.url : '';
|
|
78
|
+
const tokens = await finishLogin(raw, deps.issuer, deps.fetchImpl).catch(asApiError);
|
|
79
|
+
return keepCodex(store, req, tokens, 'model-api: Codex connected');
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
import: {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
run: (_req, deps, store) => {
|
|
85
|
+
let tokens;
|
|
86
|
+
try {
|
|
87
|
+
tokens = readCodexCliAuth(deps.codexHome);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
asApiError(err);
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve(keepCodex(store, _req, tokens, 'model-api: Codex CLI login imported'));
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
models: {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
run: async (req, deps, store) => {
|
|
97
|
+
const conn = connectionFor(store.read(), req, 'codex');
|
|
98
|
+
if (conn.codex === null) throw new ApiError('this connection is not signed in yet', 400);
|
|
99
|
+
const codexDeps = codexDepsFor(deps, store, conn.id);
|
|
100
|
+
const auth = await currentTokens(conn, codexDeps, sharedCodexState).catch(asApiError);
|
|
101
|
+
return { models: await codexModels(auth, codexDeps).catch(asApiError) };
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const geminiDeps = (deps: ModelApiDeps, store: Store): GeminiDeps => ({
|
|
107
|
+
base: deps.geminiBase,
|
|
108
|
+
tokenBase: deps.geminiTokenBase,
|
|
109
|
+
fetchImpl: deps.fetchImpl,
|
|
110
|
+
save: (id, t) => {
|
|
111
|
+
store.write(setGeminiAuth(store.read(), id, t));
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export async function geminiModelsOf(conn: Connection, deps: ModelApiDeps, store: Store): Promise<Awaited<ReturnType<typeof listGeminiModels>>> {
|
|
116
|
+
if (conn.gemini === null) throw new ApiError('this connection is not signed in yet', 400);
|
|
117
|
+
const tokens = await currentGeminiTokens(conn, geminiDeps(deps, store), sharedGeminiState);
|
|
118
|
+
return listGeminiModels(tokens, geminiDeps(deps, store));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function projectOf(body: unknown): string | null {
|
|
122
|
+
try {
|
|
123
|
+
return parseGeminiProject(isRecord(body) ? body.project : null);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return asApiError(err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function connectGemini(req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
|
|
130
|
+
const body = await readJsonBody(req, BODY_MAX);
|
|
131
|
+
const code = isRecord(body) && typeof body.code === 'string' ? body.code : '';
|
|
132
|
+
const state = isRecord(body) && typeof body.state === 'string' ? body.state : '';
|
|
133
|
+
const project = projectOf(body);
|
|
134
|
+
const tokens = await exchangeGeminiCode(code, state, deps.geminiTokenBase, deps.fetchImpl).catch(asApiError);
|
|
135
|
+
const email = await userEmail(tokens, deps.geminiUserBase, deps.fetchImpl);
|
|
136
|
+
const onboarded = await onboard(tokens, project, deps.geminiBase, deps.fetchImpl).catch(asApiError);
|
|
137
|
+
const { cfg, id } = connectionToFill(store, req, 'gemini');
|
|
138
|
+
const full: GeminiTokens = { ...tokens, email, project: onboarded.project, tier: onboarded.tier };
|
|
139
|
+
return saved(store, setGeminiAuth(cfg, id, full), 'model-api: Gemini connected', { connection: id, tier: onboarded.tier });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const geminiLists = listCache<string>('gemini');
|
|
143
|
+
|
|
144
|
+
async function geminiIds(req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<string[]> {
|
|
145
|
+
const conn = connectionFor(store.read(), req, 'gemini');
|
|
146
|
+
const key = `${deps.geminiBase ?? ''}:${conn.id}:${conn.gemini?.email ?? ''}:${conn.gemini?.project ?? ''}`;
|
|
147
|
+
return geminiLists.get(key, async () => (await geminiModelsOf(conn, deps, store)).map((m) => m.id));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const GEMINI_ROUTES: Record<string, Route> = {
|
|
151
|
+
login: { method: 'POST', run: (_req, deps) => Promise.resolve(beginGeminiLogin(deps.geminiAuthBase)) },
|
|
152
|
+
code: { method: 'POST', run: connectGemini },
|
|
153
|
+
models: {
|
|
154
|
+
method: 'GET',
|
|
155
|
+
run: async (req, deps, store) => ({ models: await geminiIds(req, deps, store).catch(asApiError) }),
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export function codexDeviceRoute(id: string, method: string | undefined): Route | number {
|
|
160
|
+
return method === 'GET' ? { method: 'GET', run: (req, deps, store) => pollDevice(id, req, deps, store) } : 405;
|
|
161
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
claudeUsage?: () => Promise<unknown>;
|
|
28
|
+
usageWaitMs?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Store {
|
|
32
|
+
read: () => ModelConfig;
|
|
33
|
+
write: (cfg: ModelConfig) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type Handler = (req: IncomingMessage, deps: ModelApiDeps, store: Store) => Promise<unknown>;
|
|
37
|
+
|
|
38
|
+
export interface Route {
|
|
39
|
+
method: 'GET' | 'POST';
|
|
40
|
+
run: Handler;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const BODY_MAX = 16 * 1024;
|
|
44
|
+
export const CREDITS_TTL_MS = 5 * 60_000;
|
|
45
|
+
|
|
46
|
+
export const settingsBody = (cfg: ModelConfig): Record<string, unknown> => ({
|
|
47
|
+
...publicModelConfig(cfg),
|
|
48
|
+
lastServed: lastServed(),
|
|
49
|
+
usage: usageSeen(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export function asApiError(err: unknown): never {
|
|
53
|
+
if (err instanceof ModelConfigError || err instanceof CodexAuthError || err instanceof GeminiAuthError) throw new ApiError(err.message, 400);
|
|
54
|
+
if (err instanceof GatewayError) throw new ApiError(err.message, err.status >= 400 && err.status < 500 ? 400 : 502);
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const askedConnection = (req: IncomingMessage): string => new URL(req.url ?? '', 'http://metro').searchParams.get('connection') ?? '';
|
|
59
|
+
|
|
60
|
+
export function connectionFor(cfg: ModelConfig, req: IncomingMessage, provider: Provider): Connection {
|
|
61
|
+
const asked = askedConnection(req);
|
|
62
|
+
if (asked !== '') {
|
|
63
|
+
const found = connectionOf(cfg, asked);
|
|
64
|
+
if (found?.provider !== provider) throw new ApiError('no such connection', 404);
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
const routed = routedConnection(cfg);
|
|
68
|
+
if (routed?.provider === provider) return routed;
|
|
69
|
+
const first = cfg.connections.find((c) => c.provider === provider);
|
|
70
|
+
if (first === undefined) throw new ApiError(`no ${provider} connection yet`, 400);
|
|
71
|
+
return first;
|
|
72
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
2
|
+
import { GatewayError } from './forward.js';
|
|
3
|
+
import { listCache } from './model-lists.js';
|
|
4
|
+
import { stringOf } from '@metro-labs/http/api-http';
|
|
5
|
+
import type { KeySpend } from './usage.js';
|
|
6
|
+
|
|
7
|
+
export const OPENROUTER_BASE = 'https://openrouter.ai/api';
|
|
8
|
+
const MODELS_MAX = 2000;
|
|
9
|
+
const KEY_TIMEOUT_MS = 10_000;
|
|
10
|
+
|
|
11
|
+
export interface OpenRouterModel {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
prompt: number | null;
|
|
15
|
+
completion: number | null;
|
|
16
|
+
created: number | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const modelLists = listCache<OpenRouterModel>('openrouter');
|
|
20
|
+
const zdrLists = listCache<string>('openrouter-zdr');
|
|
21
|
+
|
|
22
|
+
function price(raw: unknown): number | null {
|
|
23
|
+
const value = typeof raw === 'string' ? Number(raw) : typeof raw === 'number' ? raw : Number.NaN;
|
|
24
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function released(raw: unknown): number | null {
|
|
28
|
+
return typeof raw === 'number' && Number.isFinite(raw) && raw > 0 ? raw : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const strings = (value: unknown): string[] | null => (Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : null);
|
|
32
|
+
|
|
33
|
+
function chatCapable(entry: Record<string, unknown>): boolean {
|
|
34
|
+
const output = strings(isRecord(entry.architecture) ? entry.architecture.output_modalities : undefined);
|
|
35
|
+
const params = strings(entry.supported_parameters);
|
|
36
|
+
return (output === null || output.includes('text')) && (params === null || params.includes('tools'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function modelOf(entry: unknown): OpenRouterModel | null {
|
|
40
|
+
if (!isRecord(entry) || !chatCapable(entry)) return null;
|
|
41
|
+
const id = stringOf(entry.id);
|
|
42
|
+
if (id === '') return null;
|
|
43
|
+
const pricing = isRecord(entry.pricing) ? entry.pricing : {};
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
name: stringOf(entry.name) || id,
|
|
47
|
+
prompt: price(pricing.prompt),
|
|
48
|
+
completion: price(pricing.completion),
|
|
49
|
+
created: released(entry.created),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function newestFirst(a: OpenRouterModel, b: OpenRouterModel): number {
|
|
54
|
+
const when = (b.created ?? 0) - (a.created ?? 0);
|
|
55
|
+
return when !== 0 ? when : a.id.localeCompare(b.id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function listModels(base: string, fetchImpl: typeof fetch): Promise<OpenRouterModel[]> {
|
|
59
|
+
const res = await fetchImpl(`${base}/v1/models`, { headers: { accept: 'application/json' }, redirect: 'manual' });
|
|
60
|
+
if (!res.ok) throw new GatewayError(res.status, 'api_error', `OpenRouter would not list its models (${String(res.status)})`);
|
|
61
|
+
const body: unknown = await res.json();
|
|
62
|
+
const data = typeof body === 'object' && body !== null ? (body as { data?: unknown }).data : undefined;
|
|
63
|
+
if (!Array.isArray(data)) throw new GatewayError(502, 'api_error', 'OpenRouter answered with no model list');
|
|
64
|
+
return data
|
|
65
|
+
.map(modelOf)
|
|
66
|
+
.filter((model): model is OpenRouterModel => model !== null)
|
|
67
|
+
.sort(newestFirst)
|
|
68
|
+
.slice(0, MODELS_MAX);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const openrouterModels = (base = OPENROUTER_BASE, fetchImpl: typeof fetch = fetch): Promise<OpenRouterModel[]> =>
|
|
72
|
+
modelLists.get(base, () => listModels(base, fetchImpl));
|
|
73
|
+
|
|
74
|
+
const nullableAmount = (raw: unknown): number | null => (typeof raw === 'number' && Number.isFinite(raw) ? raw : null);
|
|
75
|
+
|
|
76
|
+
export async function openrouterKey(apiKey: string, base = OPENROUTER_BASE, fetchImpl: typeof fetch = fetch): Promise<KeySpend> {
|
|
77
|
+
const res = await fetchImpl(`${base}/v1/key`, {
|
|
78
|
+
headers: { accept: 'application/json', authorization: `Bearer ${apiKey}` },
|
|
79
|
+
redirect: 'manual',
|
|
80
|
+
signal: AbortSignal.timeout(KEY_TIMEOUT_MS),
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
await res.body?.cancel();
|
|
84
|
+
throw new GatewayError(res.status, 'api_error', `OpenRouter would not describe the stored key (${String(res.status)})`);
|
|
85
|
+
}
|
|
86
|
+
const body: unknown = await res.json();
|
|
87
|
+
const data = isRecord(body) && isRecord(body.data) ? body.data : {};
|
|
88
|
+
const spent = nullableAmount(data.usage);
|
|
89
|
+
if (spent === null) throw new GatewayError(502, 'api_error', 'OpenRouter answered with no spend for the key');
|
|
90
|
+
return { limit: nullableAmount(data.limit), remaining: nullableAmount(data.limit_remaining), spent };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function listZdr(base: string, fetchImpl: typeof fetch): Promise<string[]> {
|
|
94
|
+
const res = await fetchImpl(`${base}/v1/endpoints/zdr`, { headers: { accept: 'application/json' }, redirect: 'manual' });
|
|
95
|
+
if (!res.ok) throw new GatewayError(res.status, 'api_error', `OpenRouter would not list its zero data retention endpoints (${String(res.status)})`);
|
|
96
|
+
const body: unknown = await res.json();
|
|
97
|
+
const data = typeof body === 'object' && body !== null ? (body as { data?: unknown }).data : undefined;
|
|
98
|
+
if (!Array.isArray(data)) throw new GatewayError(502, 'api_error', 'OpenRouter answered with no endpoint list');
|
|
99
|
+
const ids = new Set<string>();
|
|
100
|
+
for (const entry of data) if (isRecord(entry) && stringOf(entry.model_id) !== '') ids.add(stringOf(entry.model_id));
|
|
101
|
+
return [...ids].sort();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const openrouterZdrModels = (base = OPENROUTER_BASE, fetchImpl: typeof fetch = fetch): Promise<string[]> => zdrLists.get(base, () => listZdr(base, fetchImpl));
|
|
@@ -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,118 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from 'node:http';
|
|
2
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
3
|
+
import { GatewayError } from './forward.js';
|
|
4
|
+
import type { Connection } from './model-config.js';
|
|
5
|
+
import { fingerprint, listCache } from './model-lists.js';
|
|
6
|
+
import { stringOf } from '@metro-labs/http/api-http';
|
|
7
|
+
|
|
8
|
+
export const ANTHROPIC_API = 'https://api.anthropic.com';
|
|
9
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
10
|
+
const LIST_MAX = 1000;
|
|
11
|
+
const LIST_TIMEOUT_MS = 10_000;
|
|
12
|
+
const OAUTH_BETA = 'oauth-2025-04-20';
|
|
13
|
+
const BEARER = 'Bearer ';
|
|
14
|
+
|
|
15
|
+
export interface ProviderModel {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Dated extends ProviderModel {
|
|
21
|
+
created: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const KNOWN_CLAUDE: ProviderModel[] = [
|
|
25
|
+
{ id: 'claude-sonnet-5-5', name: 'Claude Sonnet 5.5' },
|
|
26
|
+
{ id: 'claude-opus-5-5', name: 'Claude Opus 5.5' },
|
|
27
|
+
{ id: 'claude-fable-5-1', name: 'Claude Fable 5.1' },
|
|
28
|
+
{ id: 'claude-opus-5', name: 'Claude Opus 5' },
|
|
29
|
+
{ id: 'claude-sonnet-5', name: 'Claude Sonnet 5' },
|
|
30
|
+
{ id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const claudeLists = listCache<ProviderModel>('anthropic');
|
|
34
|
+
const bedrockLists = listCache<ProviderModel>('bedrock');
|
|
35
|
+
|
|
36
|
+
const newestFirst = (list: Dated[]): ProviderModel[] =>
|
|
37
|
+
list.sort((a, b) => b.created.localeCompare(a.created) || a.id.localeCompare(b.id)).map(({ id, name }) => ({ id, name }));
|
|
38
|
+
|
|
39
|
+
function claudeEntry(entry: unknown): Dated[] {
|
|
40
|
+
if (!isRecord(entry) || stringOf(entry.id) === '') return [];
|
|
41
|
+
const type = stringOf(entry.type);
|
|
42
|
+
if (type !== '' && type !== 'model') return [];
|
|
43
|
+
const id = stringOf(entry.id);
|
|
44
|
+
return [{ id, name: stringOf(entry.display_name) || id, created: stringOf(entry.created_at) }];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function listClaude(auth: Record<string, string>, what: string, base: string, fetchImpl: typeof fetch): Promise<ProviderModel[]> {
|
|
48
|
+
const res = await fetchImpl(`${base}/v1/models?limit=${String(LIST_MAX)}`, {
|
|
49
|
+
headers: { ...auth, 'anthropic-version': ANTHROPIC_VERSION, accept: 'application/json' },
|
|
50
|
+
redirect: 'manual',
|
|
51
|
+
signal: AbortSignal.timeout(LIST_TIMEOUT_MS),
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) throw new GatewayError(res.status, 'api_error', `Anthropic would not list its models with ${what} (${String(res.status)})`);
|
|
54
|
+
const body: unknown = await res.json();
|
|
55
|
+
const data = isRecord(body) && Array.isArray(body.data) ? body.data : null;
|
|
56
|
+
if (data === null) throw new GatewayError(502, 'api_error', 'Anthropic answered with no model list');
|
|
57
|
+
return newestFirst(data.flatMap(claudeEntry));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const loginKey = (base: string): string => `login:${base}`;
|
|
61
|
+
|
|
62
|
+
const first = (value: string | string[] | undefined): string => (Array.isArray(value) ? value[0] : value)?.trim() ?? '';
|
|
63
|
+
|
|
64
|
+
function loginAuth(headers: IncomingHttpHeaders): Record<string, string> | null {
|
|
65
|
+
const bearer = first(headers.authorization);
|
|
66
|
+
if (bearer.startsWith(BEARER) && bearer.length > BEARER.length) return { authorization: bearer, 'anthropic-beta': OAUTH_BETA };
|
|
67
|
+
const key = first(headers['x-api-key']);
|
|
68
|
+
return key === '' ? null : { 'x-api-key': key };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function refreshLoginModels(headers: IncomingHttpHeaders, base = ANTHROPIC_API, fetchImpl: typeof fetch = fetch): Promise<void> {
|
|
72
|
+
const auth = loginAuth(headers);
|
|
73
|
+
if (auth === null) return Promise.resolve();
|
|
74
|
+
return claudeLists.refresh(loginKey(base), () => listClaude(auth, 'the Claude Code login', base, fetchImpl));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function anthropicModels(settings: Connection, base = ANTHROPIC_API, fetchImpl: typeof fetch = fetch): Promise<ProviderModel[]> {
|
|
78
|
+
if (settings.apiKey === '') {
|
|
79
|
+
const live = claudeLists.peek(loginKey(base)) ?? [];
|
|
80
|
+
return live.length > 0 ? live : KNOWN_CLAUDE;
|
|
81
|
+
}
|
|
82
|
+
return claudeLists.get(`key:${base}:${fingerprint(settings.apiKey)}`, () => listClaude({ 'x-api-key': settings.apiKey }, 'the stored key', base, fetchImpl));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const bedrockControlBase = (region: string): string => `https://bedrock.${region}.amazonaws.com`;
|
|
86
|
+
|
|
87
|
+
function bedrockEntry(entry: unknown): Dated[] {
|
|
88
|
+
if (!isRecord(entry)) return [];
|
|
89
|
+
const id = stringOf(entry.inferenceProfileId);
|
|
90
|
+
const status = stringOf(entry.status);
|
|
91
|
+
if (id === '' || !id.includes('anthropic') || (status !== '' && status !== 'ACTIVE')) return [];
|
|
92
|
+
return [{ id, name: stringOf(entry.inferenceProfileName) || id, created: stringOf(entry.createdAt) }];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function listBedrock(settings: Connection, base: string, fetchImpl: typeof fetch): Promise<ProviderModel[]> {
|
|
96
|
+
const res = await fetchImpl(`${base}/inference-profiles?maxResults=${String(LIST_MAX)}`, {
|
|
97
|
+
headers: { authorization: `Bearer ${settings.apiKey}`, accept: 'application/json' },
|
|
98
|
+
redirect: 'manual',
|
|
99
|
+
signal: AbortSignal.timeout(LIST_TIMEOUT_MS),
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw new GatewayError(
|
|
103
|
+
res.status,
|
|
104
|
+
'api_error',
|
|
105
|
+
`Bedrock would not list its inference profiles (${String(res.status)}); the stored key may cover the runtime only, so type the model id instead`,
|
|
106
|
+
);
|
|
107
|
+
const body: unknown = await res.json();
|
|
108
|
+
const list = isRecord(body) && Array.isArray(body.inferenceProfileSummaries) ? body.inferenceProfileSummaries : null;
|
|
109
|
+
if (list === null) throw new GatewayError(502, 'api_error', 'Bedrock answered with no inference profile list');
|
|
110
|
+
return newestFirst(list.flatMap(bedrockEntry));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function bedrockModels(settings: Connection, base?: string, fetchImpl: typeof fetch = fetch): Promise<ProviderModel[]> {
|
|
114
|
+
if (settings.region === '') throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs a region before its models can be listed');
|
|
115
|
+
if (settings.apiKey === '') throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs an API key before its models can be listed');
|
|
116
|
+
const from = base ?? bedrockControlBase(settings.region);
|
|
117
|
+
return bedrockLists.get(`${from}:${fingerprint(settings.apiKey)}`, () => listBedrock(settings, from, fetchImpl));
|
|
118
|
+
}
|
|
@@ -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,35 @@
|
|
|
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
|
+
export const TOOL_ERROR = '[tool error]';
|
|
6
|
+
|
|
7
|
+
export const nonEmpty = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
|
|
8
|
+
|
|
9
|
+
export interface InlineImage {
|
|
10
|
+
mediaType: string;
|
|
11
|
+
data: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type Part = Record<string, unknown>;
|
|
15
|
+
type Accepts = (mediaType: string) => boolean;
|
|
16
|
+
const anyImage: Accepts = () => true;
|
|
17
|
+
|
|
18
|
+
export function inlineImage(block: Part, accepts = anyImage): InlineImage | null {
|
|
19
|
+
if (block.type !== 'image' || !isRecord(block.source) || block.source.type !== 'base64') return null;
|
|
20
|
+
const image = { mediaType: stringOf(block.source.media_type), data: stringOf(block.source.data) };
|
|
21
|
+
return image.mediaType !== '' && image.data !== '' && accepts(image.mediaType) ? image : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const resultParts = (block: Part): Part[] => (Array.isArray(block.content) ? block.content.filter(isRecord) : []);
|
|
25
|
+
|
|
26
|
+
export const partText = (part: Part, accepts = anyImage): string =>
|
|
27
|
+
part.type === 'text' ? stringOf(part.text) : part.type === 'image' && inlineImage(part, accepts) === null ? IMAGE_NOTE : '';
|
|
28
|
+
|
|
29
|
+
export function resultText(block: Part, accepts = anyImage): string {
|
|
30
|
+
const content = block.content;
|
|
31
|
+
const text = typeof content === 'string' ? content : resultParts(block).map((part) => partText(part, accepts)).filter((t) => t !== '').join('\n');
|
|
32
|
+
return block.is_error === true ? `${TOOL_ERROR} ${text}` : text;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const resultImages = (block: Part, accepts = anyImage): InlineImage[] => resultParts(block).flatMap((part) => inlineImage(part, accepts) ?? []);
|