@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +9 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +182 -0
- package/dist/cli.js +86 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +77 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +43 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +50 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +155 -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 -28
- 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 +40 -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/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 +47 -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 +39 -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 +225 -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 +364 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -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 +200 -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 +30 -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 +380 -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 +354 -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 +263 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -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 +313 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -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 +304 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -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 +247 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -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 +148 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -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 +242 -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 +186 -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/bearer.ts +35 -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 +370 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -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 +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -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 +97 -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 +29 -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 +106 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -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/workos-token.ts +150 -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 +223 -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 +37 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -139
- package/dist/broker/claims.js +0 -144
- package/dist/broker/history-stream.js +0 -149
- package/dist/cli/config.js +0 -179
- package/dist/cli/index.js +0 -175
- package/dist/cli/messenger-api.js +0 -214
- package/dist/cli/messenger-transcribe.js +0 -43
- package/dist/cli/messenger-uploads.js +0 -116
- package/dist/cli/monitor-api.js +0 -205
- package/dist/cli/tail.js +0 -95
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -181
- package/dist/codex-rc/client.js +0 -225
- package/dist/codex-rc/protocol.js +0 -38
- package/dist/dispatcher/server.js +0 -122
- package/dist/dispatcher.js +0 -97
- package/dist/history.js +0 -143
- package/dist/ipc.js +0 -90
- package/dist/lines.js +0 -54
- package/dist/local-identity.js +0 -80
- package/dist/log.js +0 -15
- package/dist/paths.js +0 -114
- package/dist/trains/protocol.js +0 -99
- package/dist/trains/supervisor.js +0 -210
- package/dist/tunnel.js +0 -102
- package/docs/broker.md +0 -166
- package/docs/monitor.md +0 -241
- package/docs/uri-scheme.md +0 -127
- package/examples/README.md +0 -32
- package/examples/telegram.ts +0 -121
- package/skills/metro/SKILL.md +0 -110
|
@@ -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,242 @@
|
|
|
1
|
+
export type UsageProvider = 'anthropic' | 'codex' | 'openrouter' | 'bedrock' | 'gemini';
|
|
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 interface QuotaRow {
|
|
223
|
+
id: string;
|
|
224
|
+
remaining: number | null;
|
|
225
|
+
resetAt: string | null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function geminiUsage(rows: QuotaRow[], now = new Date()): Reported | null {
|
|
229
|
+
const windows: UsageWindow[] = rows
|
|
230
|
+
.filter((row) => row.remaining !== null)
|
|
231
|
+
.map((row) => ({ label: row.id, used: clamp(1 - (row.remaining ?? 0)), resetAt: row.resetAt, detail: null }));
|
|
232
|
+
return windows.length === 0 ? null : { windows, note: null, at: now.toISOString() };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function openrouterUsage(total: number, spent: number, now = new Date()): Reported {
|
|
236
|
+
const used = total > 0 ? clamp(spent / total) : null;
|
|
237
|
+
return {
|
|
238
|
+
windows: [{ label: 'Credits', used, resetAt: null, detail: `${dollars(spent)} of ${dollars(total)} used` }],
|
|
239
|
+
note: null,
|
|
240
|
+
at: now.toISOString(),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { forwardTrainCall } from '../stations/train-call.js';
|
|
2
|
+
import {
|
|
3
|
+
accountStationNames,
|
|
4
|
+
stationByName,
|
|
5
|
+
} from '../stations/registry.js';
|
|
6
|
+
import { listEndpoints } from '../net/tunnel.js';
|
|
7
|
+
import { hookUrl, threemaCallbackUrl } from '../stations/attach.js';
|
|
8
|
+
import { accountEnabled, agentIdForAccount, allowlistForAccount, knownAccounts, type KnownAccount } from '../agents/map.js';
|
|
9
|
+
|
|
10
|
+
const accountId = (acc: unknown): string | undefined => {
|
|
11
|
+
const id = (acc as { id?: unknown }).id;
|
|
12
|
+
return typeof id === 'string' ? id : undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const asRecord = (acc: unknown): Record<string, unknown> | undefined =>
|
|
16
|
+
typeof acc === 'object' && acc !== null && !Array.isArray(acc)
|
|
17
|
+
? (acc as Record<string, unknown>)
|
|
18
|
+
: undefined;
|
|
19
|
+
|
|
20
|
+
function withAgentId(station: string, acc: unknown): unknown {
|
|
21
|
+
const rec = asRecord(acc);
|
|
22
|
+
if (rec === undefined) return acc;
|
|
23
|
+
const id = typeof rec.id === 'string' ? rec.id : undefined;
|
|
24
|
+
if (id === undefined) return acc;
|
|
25
|
+
const agentId = agentIdForAccount(station, id);
|
|
26
|
+
if (agentId === undefined) return acc;
|
|
27
|
+
const allowlist = allowlistForAccount(station, id);
|
|
28
|
+
const tagged = allowlist === undefined ? { ...rec, agentId } : { ...rec, agentId, allowlist };
|
|
29
|
+
return accountEnabled(station, id) ? tagged : { ...tagged, enabled: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function attachAgentIds(
|
|
33
|
+
byStation: Record<string, unknown[]>,
|
|
34
|
+
): Record<string, unknown[]> {
|
|
35
|
+
const out: Record<string, unknown[]> = {};
|
|
36
|
+
for (const [station, list] of Object.entries(byStation))
|
|
37
|
+
out[station] = list.map((acc) => withAgentId(station, acc));
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function scopeAccountsByAgent(
|
|
42
|
+
byStation: Record<string, unknown[]>,
|
|
43
|
+
allowed: Set<string>,
|
|
44
|
+
): Record<string, unknown[]> {
|
|
45
|
+
const out: Record<string, unknown[]> = {};
|
|
46
|
+
for (const [station, list] of Object.entries(byStation)) {
|
|
47
|
+
out[station] = list.filter((acc) => {
|
|
48
|
+
const id = accountId(acc);
|
|
49
|
+
if (id === undefined) return false;
|
|
50
|
+
const agentId = agentIdForAccount(station, id);
|
|
51
|
+
return agentId !== undefined && allowed.has(agentId);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hasTrain = (station: string): boolean =>
|
|
58
|
+
stationByName(station)?.hasTrain !== false;
|
|
59
|
+
|
|
60
|
+
function inCoreAccounts(station: string): unknown[] {
|
|
61
|
+
if (station !== 'webhook') return [];
|
|
62
|
+
return listEndpoints().flatMap((e) =>
|
|
63
|
+
e.secret === undefined || e.webhookId === undefined
|
|
64
|
+
? []
|
|
65
|
+
: [
|
|
66
|
+
{
|
|
67
|
+
id: e.id,
|
|
68
|
+
handle: `/api/webhooks/${e.webhookId}`,
|
|
69
|
+
endpoint: hookUrl(e.webhookId, e.secret),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function withCallbackUrl(acc: unknown): unknown {
|
|
76
|
+
const rec = asRecord(acc);
|
|
77
|
+
if (rec === undefined) return acc;
|
|
78
|
+
const { callbackId, callbackToken, ...rest } = rec;
|
|
79
|
+
if (typeof callbackId !== 'string' || typeof callbackToken !== 'string') return rest;
|
|
80
|
+
return { ...rest, callback: threemaCallbackUrl(callbackId, callbackToken) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const decorate = (station: string, rows: unknown[]): unknown[] =>
|
|
84
|
+
station === 'threema' ? rows.map(withCallbackUrl) : rows;
|
|
85
|
+
|
|
86
|
+
export interface ScopedAccounts {
|
|
87
|
+
accounts: Record<string, unknown[]>;
|
|
88
|
+
unavailable: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function liveAccounts(
|
|
92
|
+
station: string,
|
|
93
|
+
): Promise<{ rows: unknown[]; reachable: boolean }> {
|
|
94
|
+
if (!hasTrain(station))
|
|
95
|
+
return { rows: inCoreAccounts(station), reachable: true };
|
|
96
|
+
try {
|
|
97
|
+
const resp = await forwardTrainCall(station, 'accounts', {});
|
|
98
|
+
const list = (resp.result as { accounts?: unknown[] } | undefined)?.accounts;
|
|
99
|
+
return { rows: decorate(station, Array.isArray(list) ? list : []), reachable: true };
|
|
100
|
+
} catch {
|
|
101
|
+
return { rows: [], reachable: false };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function mergeKnown(
|
|
106
|
+
station: string,
|
|
107
|
+
live: unknown[],
|
|
108
|
+
known: KnownAccount[],
|
|
109
|
+
): unknown[] {
|
|
110
|
+
const seen = new Set(
|
|
111
|
+
live.flatMap((row) => {
|
|
112
|
+
const id = accountId(row);
|
|
113
|
+
return id === undefined ? [] : [id];
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
const missing = known
|
|
117
|
+
.filter((r) => r.station === station && !seen.has(r.id))
|
|
118
|
+
.map((r) => ({ id: r.id }));
|
|
119
|
+
return [...live, ...missing];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function loadStations(): Promise<ScopedAccounts> {
|
|
123
|
+
const unavailable: string[] = [];
|
|
124
|
+
const accounts: Record<string, unknown[]> = {};
|
|
125
|
+
const known = knownAccounts();
|
|
126
|
+
await Promise.all(
|
|
127
|
+
accountStationNames().map(async (station) => {
|
|
128
|
+
const { rows, reachable } = await liveAccounts(station);
|
|
129
|
+
if (!reachable) unavailable.push(station);
|
|
130
|
+
accounts[station] = hasTrain(station) ? mergeKnown(station, rows, known) : rows;
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
return { accounts, unavailable };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function gatherAccounts(
|
|
137
|
+
allowedAgents?: Set<string>,
|
|
138
|
+
): Promise<Record<string, unknown[]>> {
|
|
139
|
+
const { accounts } = await loadStations();
|
|
140
|
+
return allowedAgents ? scopeAccountsByAgent(accounts, allowedAgents) : accounts;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function gatherAccountsForAgents(
|
|
144
|
+
allowed: Set<string>,
|
|
145
|
+
): Promise<ScopedAccounts> {
|
|
146
|
+
const { accounts, unavailable } = await loadStations();
|
|
147
|
+
return {
|
|
148
|
+
accounts: attachAgentIds(scopeAccountsByAgent(accounts, allowed)),
|
|
149
|
+
unavailable,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { stationForLine } from '../stations/registry.js';
|
|
2
|
+
import { kindOf, toCanonical } from '@metro-labs/core/stations/attachments';
|
|
3
|
+
import {
|
|
4
|
+
cleanupAttachments,
|
|
5
|
+
resolveAttachments,
|
|
6
|
+
type ResolvedAttachment,
|
|
7
|
+
} from '../stations/attach-resolve.js';
|
|
8
|
+
import type { CanonicalAttachment, ToolResult } from '@metro-labs/core/stations/types';
|
|
9
|
+
import { errResult, makeCtx, ok, okJson, toErr } from './ctx.js';
|
|
10
|
+
import { allowedAgents, currentIdentity } from './request-identity.js';
|
|
11
|
+
import { str } from '@metro-labs/core/str';
|
|
12
|
+
|
|
13
|
+
type Station = NonNullable<ReturnType<typeof stationForLine>>;
|
|
14
|
+
|
|
15
|
+
interface MessageArgs {
|
|
16
|
+
line: string;
|
|
17
|
+
a: Record<string, unknown>;
|
|
18
|
+
ctx: ReturnType<typeof makeCtx>;
|
|
19
|
+
onSent?: (messageId: string) => void;
|
|
20
|
+
station: Station;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const messageIdOf = (response: { result: unknown }): string | undefined => {
|
|
24
|
+
const id = (response.result as { messageId?: unknown } | null)?.messageId;
|
|
25
|
+
return typeof id === 'string' && id !== '' ? id : undefined;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const withId = (text: string, messageId: string | undefined): string =>
|
|
29
|
+
messageId === undefined ? text : `${text} — message_id: ${messageId}`;
|
|
30
|
+
|
|
31
|
+
interface Sent {
|
|
32
|
+
labels: string[];
|
|
33
|
+
messageId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const deliveredLabels = (response: { result: unknown }): string[] => {
|
|
37
|
+
const list = (response.result as { attachments?: unknown } | null)
|
|
38
|
+
?.attachments;
|
|
39
|
+
return Array.isArray(list)
|
|
40
|
+
? list.filter((x): x is string => typeof x === 'string')
|
|
41
|
+
: [];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function assertDelivered(
|
|
45
|
+
station: Station,
|
|
46
|
+
delivered: string[],
|
|
47
|
+
atts: ResolvedAttachment[],
|
|
48
|
+
): void {
|
|
49
|
+
if (delivered.length < atts.length)
|
|
50
|
+
throw new Error(
|
|
51
|
+
`${station.name} delivered ${delivered.length} of ${atts.length} attachment(s); ` +
|
|
52
|
+
'the message text may still have been sent',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function sendNative(
|
|
57
|
+
m: MessageArgs,
|
|
58
|
+
text: string | undefined,
|
|
59
|
+
replyTo: string | undefined,
|
|
60
|
+
atts: ResolvedAttachment[],
|
|
61
|
+
): Promise<Sent> {
|
|
62
|
+
const { line, ctx, station } = m;
|
|
63
|
+
const labels: string[] = [];
|
|
64
|
+
let messageId: string | undefined;
|
|
65
|
+
if (text) {
|
|
66
|
+
const response = await ctx.call(
|
|
67
|
+
'send',
|
|
68
|
+
replyTo ? { line, text, replyTo } : { line, text },
|
|
69
|
+
);
|
|
70
|
+
messageId = messageIdOf(response);
|
|
71
|
+
labels.push('text');
|
|
72
|
+
}
|
|
73
|
+
if (!atts.length) return { labels, messageId };
|
|
74
|
+
const delivered = station.sendAttachments
|
|
75
|
+
? await station.sendAttachments(line, atts, ctx)
|
|
76
|
+
: [];
|
|
77
|
+
assertDelivered(station, delivered, atts);
|
|
78
|
+
labels.push(...delivered);
|
|
79
|
+
return { labels, messageId };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function sendForwarded(
|
|
83
|
+
m: MessageArgs,
|
|
84
|
+
text: string | undefined,
|
|
85
|
+
replyTo: string | undefined,
|
|
86
|
+
atts: ResolvedAttachment[],
|
|
87
|
+
): Promise<Sent> {
|
|
88
|
+
const { line, ctx, station } = m;
|
|
89
|
+
const args: Record<string, unknown> = { line };
|
|
90
|
+
if (text) args.text = text;
|
|
91
|
+
if (replyTo) args.replyTo = replyTo;
|
|
92
|
+
if (atts.length) args.attachments = atts.map(toCanonical);
|
|
93
|
+
const response = await ctx.call('send', args);
|
|
94
|
+
const messageId = messageIdOf(response);
|
|
95
|
+
const labels: string[] = [];
|
|
96
|
+
if (text) labels.push('text');
|
|
97
|
+
if (!atts.length) return { labels, messageId };
|
|
98
|
+
const delivered = deliveredLabels(response);
|
|
99
|
+
assertDelivered(station, delivered, atts);
|
|
100
|
+
labels.push(...delivered);
|
|
101
|
+
return { labels, messageId };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const unsupported = (station: Station, atts: CanonicalAttachment[]): string =>
|
|
105
|
+
`${station.name} cannot send attachments (${atts
|
|
106
|
+
.map((a) => kindOf(a.mime ?? '', a.path ?? a.url ?? a.name ?? ''))
|
|
107
|
+
.join(', ')}); send a link in \`text\` instead`;
|
|
108
|
+
|
|
109
|
+
function noteSent(m: MessageArgs, messageId: string | undefined): void {
|
|
110
|
+
if (messageId !== undefined) m.onSent?.(messageId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function handleSend(m: MessageArgs): Promise<ToolResult> {
|
|
114
|
+
const text = m.a.text as string | undefined;
|
|
115
|
+
const replyTo = m.a.reply_to as string | undefined;
|
|
116
|
+
const requested = (m.a.attachments as CanonicalAttachment[] | undefined) ?? [];
|
|
117
|
+
if (!text && !requested.length)
|
|
118
|
+
return errResult('send requires `text` or `attachments`');
|
|
119
|
+
if (requested.length && m.station.attachmentMode === 'none')
|
|
120
|
+
return errResult(unsupported(m.station, requested));
|
|
121
|
+
const atts = await resolveAttachments(requested, {
|
|
122
|
+
allowed: allowedAgents(currentIdentity()),
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
const native =
|
|
126
|
+
m.station.attachmentMode === 'native' &&
|
|
127
|
+
typeof m.station.sendAttachments === 'function';
|
|
128
|
+
const sent = native
|
|
129
|
+
? await sendNative(m, text, replyTo, atts)
|
|
130
|
+
: await sendForwarded(m, text, replyTo, atts);
|
|
131
|
+
if (!sent.labels.length)
|
|
132
|
+
return errResult('send requires `text` or `attachments`');
|
|
133
|
+
noteSent(m, sent.messageId);
|
|
134
|
+
return ok(withId(`sent: ${sent.labels.join(', ')}`, sent.messageId));
|
|
135
|
+
} finally {
|
|
136
|
+
await cleanupAttachments(atts);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function handleRead({ line, a, ctx }: MessageArgs): Promise<ToolResult> {
|
|
141
|
+
const args: Record<string, unknown> = { line };
|
|
142
|
+
if (typeof a.limit === 'number') args.limit = a.limit;
|
|
143
|
+
if (a.before) args.before = str(a.before);
|
|
144
|
+
if (a.since) args.since = str(a.since);
|
|
145
|
+
return okJson(await ctx.call('read', args));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type MessageHandler = (m: MessageArgs) => Promise<ToolResult>;
|
|
149
|
+
|
|
150
|
+
interface VerbSpec {
|
|
151
|
+
args: readonly (readonly [string, string])[];
|
|
152
|
+
success: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const MESSAGE_VERBS: Record<string, VerbSpec> = {
|
|
156
|
+
reply: { args: [['message_id', 'replyTo'], ['text', 'text']], success: 'replied' },
|
|
157
|
+
react: { args: [['message_id', 'messageId'], ['emoji', 'emoji']], success: 'reacted' },
|
|
158
|
+
unreact: {
|
|
159
|
+
args: [['message_id', 'messageId'], ['emoji', 'emoji']],
|
|
160
|
+
success: 'reaction removed',
|
|
161
|
+
},
|
|
162
|
+
edit: { args: [['message_id', 'messageId'], ['text', 'text']], success: 'edited' },
|
|
163
|
+
delete: { args: [['message_id', 'messageId']], success: 'deleted' },
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
function makeVerbHandler(verb: string, spec: VerbSpec): MessageHandler {
|
|
167
|
+
return async ({ line, a, ctx, onSent }) => {
|
|
168
|
+
const payload: Record<string, unknown> = { line };
|
|
169
|
+
for (const [snake, camel] of spec.args) {
|
|
170
|
+
const value = str(a[snake]);
|
|
171
|
+
if (!value) {
|
|
172
|
+
const fields = spec.args.map(([snakeName]) => `\`${snakeName}\``).join(' and ');
|
|
173
|
+
return errResult(`${verb} requires ${fields}`);
|
|
174
|
+
}
|
|
175
|
+
payload[camel] = value;
|
|
176
|
+
}
|
|
177
|
+
const response = await ctx.call(verb, payload);
|
|
178
|
+
const messageId = messageIdOf(response);
|
|
179
|
+
if (verb === 'reply' && messageId !== undefined) onSent?.(messageId);
|
|
180
|
+
return ok(withId(spec.success, messageId));
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
|
|
185
|
+
send: handleSend,
|
|
186
|
+
read: handleRead,
|
|
187
|
+
...Object.fromEntries(
|
|
188
|
+
Object.entries(MESSAGE_VERBS).map(([verb, spec]) => [verb, makeVerbHandler(verb, spec)]),
|
|
189
|
+
),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export interface ToolHooks {
|
|
193
|
+
onSent?: (messageId: string) => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function dispatchMessageTool(
|
|
197
|
+
name: string,
|
|
198
|
+
a: Record<string, unknown>,
|
|
199
|
+
hooks: ToolHooks = {},
|
|
200
|
+
): Promise<ToolResult> {
|
|
201
|
+
const line = str(a.line);
|
|
202
|
+
if (!line) return errResult(`${name} requires \`line\``);
|
|
203
|
+
const station = stationForLine(line);
|
|
204
|
+
if (!station || station.messageVerbs.size === 0) {
|
|
205
|
+
return errResult(
|
|
206
|
+
`${station?.name ?? 'these'} lines do not support outbound messaging (send/reply/react/unreact/edit/delete/read).`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const handler = MESSAGE_HANDLERS[name];
|
|
210
|
+
if (!handler) return errResult(`unknown tool: ${name}`);
|
|
211
|
+
try {
|
|
212
|
+
return await handler({ line, a, ctx: makeCtx(station.name), station, onSent: hooks.onSent });
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return toErr(name, e);
|
|
215
|
+
}
|
|
216
|
+
}
|