@stage-labs/metro 0.1.0-beta.13 → 0.1.0-beta.130
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 +14 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +176 -0
- package/dist/cli.js +101 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +93 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +43 -0
- package/dist/plugin.js +120 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +38 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +150 -0
- package/dist/service.js +242 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -27
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -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 +16 -0
- package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
- package/runtime/node_modules/@metro-labs/core/package.json +43 -0
- package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -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/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -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 +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -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 +187 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -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 +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/http/package.json +33 -0
- package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -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 +201 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
- package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +39 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -252
- package/dist/broker.js +0 -202
- package/dist/cache.js +0 -69
- package/dist/cli/actions.js +0 -206
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -188
- package/dist/cli/skill.js +0 -62
- package/dist/cli/tail.js +0 -164
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -81
- package/dist/codex-rc.js +0 -245
- package/dist/dispatcher.js +0 -128
- package/dist/history.js +0 -121
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- package/dist/monitor.js +0 -194
- package/dist/paths.js +0 -68
- package/dist/registry.js +0 -48
- package/dist/stations/claude.js +0 -45
- package/dist/stations/codex.js +0 -68
- package/dist/stations/discord.js +0 -216
- package/dist/stations/index.js +0 -129
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -234
- package/dist/stations/webhook.js +0 -103
- package/dist/tunnel.js +0 -64
- package/dist/webhooks.js +0 -41
- package/docs/broker.md +0 -214
- package/docs/monitor.md +0 -163
- package/docs/uri-scheme.md +0 -124
- package/docs/users.md +0 -226
- package/skills/metro/SKILL.md +0 -256
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readJson, writeSecure } from '@metro-labs/core/secure-fs';
|
|
3
|
+
import { agentsDir } from '../agents/files.js';
|
|
4
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
5
|
+
import type { CodexTokens } from './codex-auth.js';
|
|
6
|
+
|
|
7
|
+
export const PROVIDERS = ['anthropic', 'bedrock', 'openrouter', 'codex'] as const;
|
|
8
|
+
export type Provider = (typeof PROVIDERS)[number];
|
|
9
|
+
|
|
10
|
+
export interface AnthropicSettings {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
model: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BedrockSettings {
|
|
16
|
+
region: string;
|
|
17
|
+
apiKey: string;
|
|
18
|
+
model: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OpenRouterSettings {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
model: string;
|
|
24
|
+
zdr: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CodexSettings {
|
|
28
|
+
model: string;
|
|
29
|
+
auth: CodexTokens | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ModelConfig {
|
|
33
|
+
version: 1;
|
|
34
|
+
provider: Provider;
|
|
35
|
+
anthropic: AnthropicSettings;
|
|
36
|
+
bedrock: BedrockSettings;
|
|
37
|
+
openrouter: OpenRouterSettings;
|
|
38
|
+
codex: CodexSettings;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Route {
|
|
42
|
+
provider: Provider;
|
|
43
|
+
model: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class ModelConfigError extends Error {}
|
|
47
|
+
|
|
48
|
+
export const MODEL_FILE = 'model.json';
|
|
49
|
+
const MAX_FIELD = 512;
|
|
50
|
+
const PREFIX_RE = /^(anthropic|bedrock|openrouter|codex):(.+)$/;
|
|
51
|
+
const SMALL_RE = /haiku/i;
|
|
52
|
+
|
|
53
|
+
const empty = (): ModelConfig => ({
|
|
54
|
+
version: 1,
|
|
55
|
+
provider: 'anthropic',
|
|
56
|
+
anthropic: { apiKey: '', model: '' },
|
|
57
|
+
bedrock: { region: '', apiKey: '', model: '' },
|
|
58
|
+
openrouter: { apiKey: '', model: '', zdr: false },
|
|
59
|
+
codex: { model: '', auth: null },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const isProvider = (value: unknown): value is Provider =>
|
|
63
|
+
typeof value === 'string' && (PROVIDERS as readonly string[]).includes(value);
|
|
64
|
+
|
|
65
|
+
const text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
|
|
66
|
+
const maybe = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
|
|
67
|
+
|
|
68
|
+
function tokensFromDisk(raw: unknown): CodexTokens | null {
|
|
69
|
+
if (!isRecord(raw)) return null;
|
|
70
|
+
const accessToken = text(raw.accessToken);
|
|
71
|
+
const refreshToken = text(raw.refreshToken);
|
|
72
|
+
const accountId = text(raw.accountId);
|
|
73
|
+
if (accessToken === '' || refreshToken === '' || accountId === '') return null;
|
|
74
|
+
return {
|
|
75
|
+
accessToken,
|
|
76
|
+
refreshToken,
|
|
77
|
+
idToken: text(raw.idToken),
|
|
78
|
+
accountId,
|
|
79
|
+
email: maybe(raw.email),
|
|
80
|
+
plan: maybe(raw.plan),
|
|
81
|
+
savedAt: text(raw.savedAt) === '' ? new Date(0).toISOString() : text(raw.savedAt),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parseModelConfig(raw: unknown): ModelConfig {
|
|
86
|
+
const base = empty();
|
|
87
|
+
if (!isRecord(raw)) return base;
|
|
88
|
+
const anthropic = isRecord(raw.anthropic) ? raw.anthropic : {};
|
|
89
|
+
const bedrock = isRecord(raw.bedrock) ? raw.bedrock : {};
|
|
90
|
+
const openrouter = isRecord(raw.openrouter) ? raw.openrouter : {};
|
|
91
|
+
const codex = isRecord(raw.codex) ? raw.codex : {};
|
|
92
|
+
return {
|
|
93
|
+
version: 1,
|
|
94
|
+
provider: isProvider(raw.provider) ? raw.provider : 'anthropic',
|
|
95
|
+
anthropic: { apiKey: text(anthropic.apiKey), model: text(anthropic.model) },
|
|
96
|
+
bedrock: { region: text(bedrock.region), apiKey: text(bedrock.apiKey), model: text(bedrock.model) },
|
|
97
|
+
openrouter: { apiKey: text(openrouter.apiKey), model: text(openrouter.model), zdr: openrouter.zdr === true },
|
|
98
|
+
codex: { model: text(codex.model), auth: tokensFromDisk(codex.auth) },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function readModelConfig(dir = agentsDir()): ModelConfig {
|
|
103
|
+
return parseModelConfig(readJson<unknown>(join(dir, MODEL_FILE), null, { warn: 'model-config: model.json is unreadable, so every request goes to Anthropic until it is fixed' }));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function writeModelConfig(cfg: ModelConfig, dir = agentsDir()): void {
|
|
107
|
+
writeSecure(join(dir, MODEL_FILE), JSON.stringify(cfg, null, 2));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function flag(patch: Record<string, unknown>, key: string, current: boolean, what: string): boolean {
|
|
111
|
+
if (!(key in patch)) return current;
|
|
112
|
+
const value = patch[key];
|
|
113
|
+
if (typeof value !== 'boolean') throw new ModelConfigError(`${what} must be true or false`);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function field(patch: Record<string, unknown>, key: string, current: string, what: string): string {
|
|
118
|
+
if (!(key in patch)) return current;
|
|
119
|
+
const value = patch[key];
|
|
120
|
+
if (typeof value !== 'string') throw new ModelConfigError(`${what} must be a string`);
|
|
121
|
+
if (value.length > MAX_FIELD) throw new ModelConfigError(`${what} is too long`);
|
|
122
|
+
return value.trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function applyModelUpdate(cfg: ModelConfig, patch: unknown): ModelConfig {
|
|
126
|
+
if (!isRecord(patch)) throw new ModelConfigError('body must be a JSON object');
|
|
127
|
+
const provider = 'provider' in patch ? patch.provider : cfg.provider;
|
|
128
|
+
if (!isProvider(provider)) throw new ModelConfigError(`provider must be one of ${PROVIDERS.join(', ')}`);
|
|
129
|
+
const anthropic = isRecord(patch.anthropic) ? patch.anthropic : {};
|
|
130
|
+
const bedrock = isRecord(patch.bedrock) ? patch.bedrock : {};
|
|
131
|
+
const openrouter = isRecord(patch.openrouter) ? patch.openrouter : {};
|
|
132
|
+
const codex = isRecord(patch.codex) ? patch.codex : {};
|
|
133
|
+
return {
|
|
134
|
+
version: 1,
|
|
135
|
+
provider,
|
|
136
|
+
anthropic: {
|
|
137
|
+
apiKey: field(anthropic, 'apiKey', cfg.anthropic.apiKey, 'Anthropic API key'),
|
|
138
|
+
model: field(anthropic, 'model', cfg.anthropic.model, 'Anthropic model'),
|
|
139
|
+
},
|
|
140
|
+
bedrock: {
|
|
141
|
+
region: field(bedrock, 'region', cfg.bedrock.region, 'Bedrock region'),
|
|
142
|
+
apiKey: field(bedrock, 'apiKey', cfg.bedrock.apiKey, 'Bedrock API key'),
|
|
143
|
+
model: field(bedrock, 'model', cfg.bedrock.model, 'Bedrock model'),
|
|
144
|
+
},
|
|
145
|
+
openrouter: {
|
|
146
|
+
apiKey: field(openrouter, 'apiKey', cfg.openrouter.apiKey, 'OpenRouter API key'),
|
|
147
|
+
model: field(openrouter, 'model', cfg.openrouter.model, 'OpenRouter model'),
|
|
148
|
+
zdr: flag(openrouter, 'zdr', cfg.openrouter.zdr, 'OpenRouter zero data retention'),
|
|
149
|
+
},
|
|
150
|
+
codex: { model: field(codex, 'model', cfg.codex.model, 'Codex model'), auth: cfg.codex.auth },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const setCodexAuth = (cfg: ModelConfig, auth: CodexTokens | null): ModelConfig => ({ ...cfg, codex: { ...cfg.codex, auth } });
|
|
155
|
+
|
|
156
|
+
const CHECKS: Record<Provider, [(cfg: ModelConfig) => boolean, string][]> = {
|
|
157
|
+
anthropic: [],
|
|
158
|
+
bedrock: [
|
|
159
|
+
[(cfg) => cfg.bedrock.apiKey === '', 'Bedrock needs an API key: add it on the Model page.'],
|
|
160
|
+
[(cfg) => cfg.bedrock.region === '', 'Bedrock needs a region: add it on the Model page.'],
|
|
161
|
+
],
|
|
162
|
+
openrouter: [
|
|
163
|
+
[(cfg) => cfg.openrouter.apiKey === '', 'OpenRouter needs an API key: add it on the Model page.'],
|
|
164
|
+
[(cfg) => cfg.openrouter.model === '', 'OpenRouter needs a model id: choose one on the Model page.'],
|
|
165
|
+
],
|
|
166
|
+
codex: [
|
|
167
|
+
[(cfg) => cfg.codex.auth === null, 'Codex is not connected: sign in with ChatGPT on the Model page.'],
|
|
168
|
+
[(cfg) => cfg.codex.model === '', 'Codex needs a model id: choose one on the Model page.'],
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export function notReady(cfg: ModelConfig, provider: Provider = cfg.provider): string | null {
|
|
173
|
+
return CHECKS[provider].find(([missing]) => missing(cfg))?.[1] ?? null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function publicModelConfig(cfg: ModelConfig): Record<string, unknown> {
|
|
177
|
+
const auth = cfg.codex.auth;
|
|
178
|
+
return {
|
|
179
|
+
provider: cfg.provider,
|
|
180
|
+
ready: notReady(cfg) === null,
|
|
181
|
+
reason: notReady(cfg),
|
|
182
|
+
anthropic: { model: cfg.anthropic.model, hasKey: cfg.anthropic.apiKey !== '' },
|
|
183
|
+
bedrock: { region: cfg.bedrock.region, model: cfg.bedrock.model, hasKey: cfg.bedrock.apiKey !== '' },
|
|
184
|
+
openrouter: { model: cfg.openrouter.model, hasKey: cfg.openrouter.apiKey !== '', zdr: cfg.openrouter.zdr },
|
|
185
|
+
codex: { model: cfg.codex.model, signedIn: auth !== null, account: auth?.email ?? null, plan: auth?.plan ?? null },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const isSmallModel = (requested: string): boolean => SMALL_RE.test(requested);
|
|
190
|
+
|
|
191
|
+
function defaultModelFor(provider: Provider, requested: string, cfg: ModelConfig): string {
|
|
192
|
+
if (provider === 'openrouter') return requested.includes('/') ? requested : cfg.openrouter.model;
|
|
193
|
+
if (provider === 'bedrock') return cfg.bedrock.model === '' ? requested : cfg.bedrock.model;
|
|
194
|
+
if (provider === 'codex') return requested.startsWith('gpt-') ? requested : cfg.codex.model;
|
|
195
|
+
return cfg.anthropic.model === '' || isSmallModel(requested) ? requested : cfg.anthropic.model;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function resolveRoute(requested: string, cfg: ModelConfig): Route {
|
|
199
|
+
const explicit = PREFIX_RE.exec(requested);
|
|
200
|
+
if (explicit !== null && isProvider(explicit[1]))
|
|
201
|
+
return { provider: explicit[1], model: (explicit[2] ?? '').trim() };
|
|
202
|
+
return { provider: cfg.provider, model: defaultModelFor(cfg.provider, requested, cfg) };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function routeLabel(route: Route): string {
|
|
206
|
+
return route.provider === 'anthropic' ? route.model : `${route.provider}:${route.model}`;
|
|
207
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
2
|
+
import { GatewayError } from './forward.js';
|
|
3
|
+
|
|
4
|
+
export const OPENROUTER_BASE = 'https://openrouter.ai/api';
|
|
5
|
+
const MODELS_MAX = 2000;
|
|
6
|
+
|
|
7
|
+
export interface OpenRouterModel {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
prompt: number | null;
|
|
11
|
+
completion: number | null;
|
|
12
|
+
created: number | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const str = (value: unknown): string => (typeof value === 'string' ? value : '');
|
|
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 = str(entry.id);
|
|
29
|
+
if (id === '') return null;
|
|
30
|
+
const pricing = isRecord(entry.pricing) ? entry.pricing : {};
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
name: str(entry.name) || id,
|
|
34
|
+
prompt: price(pricing.prompt),
|
|
35
|
+
completion: price(pricing.completion),
|
|
36
|
+
created: released(entry.created),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export 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) && str(entry.model_id) !== '') ids.add(str(entry.model_id));
|
|
89
|
+
return [...ids].sort();
|
|
90
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
2
|
+
import { GatewayError } from './forward.js';
|
|
3
|
+
import type { AnthropicSettings, BedrockSettings } from './model-config.js';
|
|
4
|
+
|
|
5
|
+
export const ANTHROPIC_API = 'https://api.anthropic.com';
|
|
6
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
7
|
+
const LIST_MAX = 1000;
|
|
8
|
+
|
|
9
|
+
export interface ProviderModel {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const KNOWN_CLAUDE: ProviderModel[] = [
|
|
15
|
+
{ id: 'claude-fable-5-1', name: 'Claude Fable 5.1' },
|
|
16
|
+
{ id: 'claude-opus-5', name: 'Claude Opus 5' },
|
|
17
|
+
{ id: 'claude-sonnet-5', name: 'Claude Sonnet 5' },
|
|
18
|
+
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const str = (value: unknown): string => (typeof value === 'string' ? value : '');
|
|
22
|
+
|
|
23
|
+
export async function anthropicModels(settings: AnthropicSettings, base = ANTHROPIC_API, fetchImpl: typeof fetch = fetch): Promise<ProviderModel[]> {
|
|
24
|
+
if (settings.apiKey === '') return KNOWN_CLAUDE;
|
|
25
|
+
const res = await fetchImpl(`${base}/v1/models?limit=${String(LIST_MAX)}`, {
|
|
26
|
+
headers: { 'x-api-key': settings.apiKey, 'anthropic-version': ANTHROPIC_VERSION, accept: 'application/json' },
|
|
27
|
+
redirect: 'manual',
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) throw new GatewayError(res.status, 'api_error', `Anthropic would not list its models with the stored key (${String(res.status)})`);
|
|
30
|
+
const body: unknown = await res.json();
|
|
31
|
+
const data = isRecord(body) && Array.isArray(body.data) ? body.data : null;
|
|
32
|
+
if (data === null) throw new GatewayError(502, 'api_error', 'Anthropic answered with no model list');
|
|
33
|
+
return data
|
|
34
|
+
.flatMap((entry) => (isRecord(entry) && str(entry.id) !== '' ? [{ id: str(entry.id), name: str(entry.display_name) || str(entry.id) }] : []))
|
|
35
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const bedrockControlBase = (region: string): string => `https://bedrock.${region}.amazonaws.com`;
|
|
39
|
+
|
|
40
|
+
export async function bedrockModels(settings: BedrockSettings, base?: string, fetchImpl: typeof fetch = fetch): Promise<ProviderModel[]> {
|
|
41
|
+
if (settings.region === '') throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs a region before its models can be listed');
|
|
42
|
+
if (settings.apiKey === '') throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs an API key before its models can be listed');
|
|
43
|
+
const res = await fetchImpl(`${base ?? bedrockControlBase(settings.region)}/inference-profiles?maxResults=${String(LIST_MAX)}`, {
|
|
44
|
+
headers: { authorization: `Bearer ${settings.apiKey}`, accept: 'application/json' },
|
|
45
|
+
redirect: 'manual',
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok)
|
|
48
|
+
throw new GatewayError(
|
|
49
|
+
res.status,
|
|
50
|
+
'api_error',
|
|
51
|
+
`Bedrock would not list its inference profiles (${String(res.status)}); the stored key may cover the runtime only, so type the model id instead`,
|
|
52
|
+
);
|
|
53
|
+
const body: unknown = await res.json();
|
|
54
|
+
const list = isRecord(body) && Array.isArray(body.inferenceProfileSummaries) ? body.inferenceProfileSummaries : null;
|
|
55
|
+
if (list === null) throw new GatewayError(502, 'api_error', 'Bedrock answered with no inference profile list');
|
|
56
|
+
return list
|
|
57
|
+
.flatMap((entry) => {
|
|
58
|
+
if (!isRecord(entry)) return [];
|
|
59
|
+
const id = str(entry.inferenceProfileId);
|
|
60
|
+
const status = str(entry.status);
|
|
61
|
+
if (id === '' || !id.includes('anthropic') || (status !== '' && status !== 'ACTIVE')) return [];
|
|
62
|
+
return [{ id, name: str(entry.inferenceProfileName) || id }];
|
|
63
|
+
})
|
|
64
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Served {
|
|
2
|
+
provider: string;
|
|
3
|
+
model: string;
|
|
4
|
+
at: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let last: Served | null = null;
|
|
8
|
+
|
|
9
|
+
export const noteServed = (served: Served): void => {
|
|
10
|
+
last = served;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const lastServed = (): Served | null => last;
|
|
14
|
+
|
|
15
|
+
export const forgetServed = (): void => {
|
|
16
|
+
last = null;
|
|
17
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
export type UsageProvider = 'anthropic' | 'codex' | 'openrouter' | 'bedrock';
|
|
2
|
+
|
|
3
|
+
export interface UsageWindow {
|
|
4
|
+
label: string;
|
|
5
|
+
used: number | null;
|
|
6
|
+
resetAt: string | null;
|
|
7
|
+
detail: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Tally {
|
|
11
|
+
requests: number;
|
|
12
|
+
input: number;
|
|
13
|
+
output: number;
|
|
14
|
+
cached: number;
|
|
15
|
+
since: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProviderUsage {
|
|
19
|
+
windows: UsageWindow[];
|
|
20
|
+
note: string | null;
|
|
21
|
+
at: string;
|
|
22
|
+
tally: Tally | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Reported = Omit<ProviderUsage, 'tally'>;
|
|
26
|
+
|
|
27
|
+
const latest = new Map<UsageProvider, Reported>();
|
|
28
|
+
const tallies = new Map<UsageProvider, Tally>();
|
|
29
|
+
|
|
30
|
+
export const noteUsage = (provider: UsageProvider, usage: Reported): void => {
|
|
31
|
+
latest.set(provider, usage);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function usageSeen(): Partial<Record<UsageProvider, ProviderUsage>> {
|
|
35
|
+
const out: Partial<Record<UsageProvider, ProviderUsage>> = {};
|
|
36
|
+
const providers = new Set<UsageProvider>([...latest.keys(), ...tallies.keys()]);
|
|
37
|
+
for (const provider of providers) {
|
|
38
|
+
const reported = latest.get(provider);
|
|
39
|
+
const tally = tallies.get(provider) ?? null;
|
|
40
|
+
out[provider] = reported === undefined
|
|
41
|
+
? { windows: [], note: null, at: tally?.since ?? new Date().toISOString(), tally }
|
|
42
|
+
: { ...reported, tally };
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const usageOf = (provider: UsageProvider): Reported | undefined => latest.get(provider);
|
|
48
|
+
|
|
49
|
+
export const forgetUsage = (): void => {
|
|
50
|
+
latest.clear();
|
|
51
|
+
tallies.clear();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface Counted {
|
|
55
|
+
input: number;
|
|
56
|
+
output: number;
|
|
57
|
+
cached: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function tallyTokens(provider: UsageProvider, counted: Counted, now = new Date()): void {
|
|
61
|
+
const so = tallies.get(provider) ?? { requests: 0, input: 0, output: 0, cached: 0, since: now.toISOString() };
|
|
62
|
+
tallies.set(provider, {
|
|
63
|
+
requests: so.requests + 1,
|
|
64
|
+
input: so.input + counted.input,
|
|
65
|
+
output: so.output + counted.output,
|
|
66
|
+
cached: so.cached + counted.cached,
|
|
67
|
+
since: so.since,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const TOKEN_FIELD = /"(input_tokens|output_tokens|cache_read_input_tokens|cache_creation_input_tokens)"\s*:\s*(\d+)/g;
|
|
72
|
+
|
|
73
|
+
export class UsageScanner {
|
|
74
|
+
private carry = '';
|
|
75
|
+
private readonly max = { input: 0, output: 0, read: 0, creation: 0 };
|
|
76
|
+
private touched = false;
|
|
77
|
+
|
|
78
|
+
constructor(private readonly provider: UsageProvider) {}
|
|
79
|
+
|
|
80
|
+
private scan(text: string): void {
|
|
81
|
+
for (const hit of text.matchAll(TOKEN_FIELD)) {
|
|
82
|
+
const value = Number(hit[2]);
|
|
83
|
+
if (!Number.isFinite(value)) continue;
|
|
84
|
+
this.touched = true;
|
|
85
|
+
if (hit[1] === 'input_tokens') this.max.input = Math.max(this.max.input, value);
|
|
86
|
+
else if (hit[1] === 'output_tokens') this.max.output = Math.max(this.max.output, value);
|
|
87
|
+
else if (hit[1] === 'cache_read_input_tokens') this.max.read = Math.max(this.max.read, value);
|
|
88
|
+
else this.max.creation = Math.max(this.max.creation, value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
feed(text: string): void {
|
|
93
|
+
const joined = this.carry + text;
|
|
94
|
+
const cut = joined.lastIndexOf('\n');
|
|
95
|
+
if (cut === -1) {
|
|
96
|
+
this.carry = joined;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.scan(joined.slice(0, cut));
|
|
100
|
+
this.carry = joined.slice(cut + 1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
done(now = new Date()): void {
|
|
104
|
+
if (this.carry !== '') this.scan(this.carry);
|
|
105
|
+
this.carry = '';
|
|
106
|
+
if (!this.touched) return;
|
|
107
|
+
tallyTokens(this.provider, { input: this.max.input, output: this.max.output, cached: this.max.read + this.max.creation }, now);
|
|
108
|
+
this.touched = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const clamp = (value: number): number => Math.min(1, Math.max(0, value));
|
|
113
|
+
|
|
114
|
+
function fraction(raw: string | null): number | null {
|
|
115
|
+
if (raw === null) return null;
|
|
116
|
+
const value = Number(raw);
|
|
117
|
+
return Number.isFinite(value) ? clamp(value) : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const MS_THRESHOLD = 1e12;
|
|
121
|
+
|
|
122
|
+
function whenFrom(raw: string | null, now: Date, relative = false): string | null {
|
|
123
|
+
if (raw === null || raw.trim() === '') return null;
|
|
124
|
+
const numeric = Number(raw);
|
|
125
|
+
if (Number.isFinite(numeric)) {
|
|
126
|
+
const absolute = numeric > MS_THRESHOLD ? numeric : numeric * 1000;
|
|
127
|
+
const ms = relative ? now.getTime() + numeric * 1000 : absolute;
|
|
128
|
+
return new Date(ms).toISOString();
|
|
129
|
+
}
|
|
130
|
+
const parsed = Date.parse(raw);
|
|
131
|
+
return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const ANTHROPIC_WINDOWS: [string, string][] = [
|
|
135
|
+
['5h', '5-hour window'],
|
|
136
|
+
['7d', 'Weekly'],
|
|
137
|
+
['7d_sonnet', 'Weekly, Sonnet'],
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
function anthropicUnified(headers: Headers, now: Date): { windows: UsageWindow[]; note: string | null } {
|
|
141
|
+
const windows: UsageWindow[] = [];
|
|
142
|
+
let note: string | null = null;
|
|
143
|
+
for (const [key, label] of ANTHROPIC_WINDOWS) {
|
|
144
|
+
const used = fraction(headers.get(`anthropic-ratelimit-unified-${key}-utilization`));
|
|
145
|
+
if (used === null) continue;
|
|
146
|
+
const status = headers.get(`anthropic-ratelimit-unified-${key}-status`);
|
|
147
|
+
const blocked = status !== null && status !== 'allowed' ? status.replaceAll('_', ' ') : null;
|
|
148
|
+
if (blocked !== null && note === null) note = `${label}: ${blocked}`;
|
|
149
|
+
windows.push({ label, used, resetAt: whenFrom(headers.get(`anthropic-ratelimit-unified-${key}-reset`), now), detail: blocked });
|
|
150
|
+
}
|
|
151
|
+
return { windows, note };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function anthropicKeyed(headers: Headers, now: Date): UsageWindow[] {
|
|
155
|
+
const limit = Number(headers.get('anthropic-ratelimit-tokens-limit'));
|
|
156
|
+
const remaining = Number(headers.get('anthropic-ratelimit-tokens-remaining'));
|
|
157
|
+
if (!Number.isFinite(limit) || !Number.isFinite(remaining) || limit <= 0) return [];
|
|
158
|
+
return [
|
|
159
|
+
{
|
|
160
|
+
label: 'Tokens per minute',
|
|
161
|
+
used: clamp(1 - remaining / limit),
|
|
162
|
+
resetAt: whenFrom(headers.get('anthropic-ratelimit-tokens-reset'), now),
|
|
163
|
+
detail: `${remaining.toLocaleString('en-US')} of ${limit.toLocaleString('en-US')} left`,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function anthropicUsage(headers: Headers, now = new Date()): Reported | null {
|
|
169
|
+
const unified = anthropicUnified(headers, now);
|
|
170
|
+
if (unified.windows.length > 0) return { ...unified, at: now.toISOString() };
|
|
171
|
+
const keyed = anthropicKeyed(headers, now);
|
|
172
|
+
return keyed.length === 0 ? null : { windows: keyed, note: null, at: now.toISOString() };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const MINUTES_PER_HOUR = 60;
|
|
176
|
+
const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
|
|
177
|
+
const MINUTES_PER_WEEK = 7 * MINUTES_PER_DAY;
|
|
178
|
+
|
|
179
|
+
export function windowLabel(minutes: number | null): string {
|
|
180
|
+
if (minutes === null || !Number.isFinite(minutes) || minutes <= 0) return 'Usage window';
|
|
181
|
+
if (minutes === MINUTES_PER_WEEK) return 'Weekly';
|
|
182
|
+
if (minutes % MINUTES_PER_DAY === 0) return `${String(minutes / MINUTES_PER_DAY)}-day window`;
|
|
183
|
+
if (minutes % MINUTES_PER_HOUR === 0) return `${String(minutes / MINUTES_PER_HOUR)}-hour window`;
|
|
184
|
+
return `${String(minutes)}-minute window`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function codexWindow(headers: Headers, kind: 'primary' | 'secondary', now: Date): UsageWindow | null {
|
|
188
|
+
const raw = headers.get(`x-codex-${kind}-used-percent`);
|
|
189
|
+
if (raw === null) return null;
|
|
190
|
+
const percent = Number(raw);
|
|
191
|
+
if (!Number.isFinite(percent)) return null;
|
|
192
|
+
const used = clamp(percent / 100);
|
|
193
|
+
const minutes = Number(headers.get(`x-codex-${kind}-window-minutes`) ?? 'none');
|
|
194
|
+
const absolute = whenFrom(headers.get(`x-codex-${kind}-reset-at`), now);
|
|
195
|
+
const resetAt = absolute ?? whenFrom(headers.get(`x-codex-${kind}-reset-after-seconds`), now, true);
|
|
196
|
+
return { label: windowLabel(Number.isFinite(minutes) ? minutes : null), used, resetAt, detail: null };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function codexCredits(headers: Headers): UsageWindow | null {
|
|
200
|
+
if (headers.get('x-codex-credits-has-credits') !== 'true') return null;
|
|
201
|
+
const balance = Number(headers.get('x-codex-credits-balance') ?? 'none');
|
|
202
|
+
if (!Number.isFinite(balance)) return null;
|
|
203
|
+
return { label: 'Credits', used: null, resetAt: null, detail: `${balance.toLocaleString('en-US')} left` };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function codexUsage(headers: Headers, now = new Date()): Reported | null {
|
|
207
|
+
const windows = [codexWindow(headers, 'primary', now), codexWindow(headers, 'secondary', now), codexCredits(headers)].filter(
|
|
208
|
+
(w): w is UsageWindow => w !== null,
|
|
209
|
+
);
|
|
210
|
+
if (windows.length === 0) return null;
|
|
211
|
+
const reached = headers.get('x-codex-rate-limit-reached-type');
|
|
212
|
+
return { windows, note: reached === null || reached === '' ? null : reached.replaceAll('_', ' '), at: now.toISOString() };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function noteUsageHeaders(provider: 'anthropic' | 'codex', headers: Headers, now = new Date()): void {
|
|
216
|
+
const usage = provider === 'anthropic' ? anthropicUsage(headers, now) : codexUsage(headers, now);
|
|
217
|
+
if (usage !== null) noteUsage(provider, usage);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const dollars = (value: number): string => `$${value.toFixed(2)}`;
|
|
221
|
+
|
|
222
|
+
export function openrouterUsage(total: number, spent: number, now = new Date()): Reported {
|
|
223
|
+
const used = total > 0 ? clamp(spent / total) : null;
|
|
224
|
+
return {
|
|
225
|
+
windows: [{ label: 'Credits', used, resetAt: null, detail: `${dollars(spent)} of ${dollars(total)} used` }],
|
|
226
|
+
note: null,
|
|
227
|
+
at: now.toISOString(),
|
|
228
|
+
};
|
|
229
|
+
}
|