@stage-labs/metro 0.1.0-beta.7 → 0.1.0-beta.71
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/bedrock-eventstream.js +120 -0
- package/dist/bedrock-proxy.js +260 -0
- package/dist/bedrock-upstream.js +72 -0
- package/dist/bedrock.js +37 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +107 -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/node-name.js +51 -0
- package/dist/plugin.js +80 -0
- package/dist/provider-flags.js +9 -0
- package/dist/runtime-install.js +84 -0
- package/dist/runtime.js +70 -0
- package/dist/serve.js +147 -0
- package/dist/service.js +224 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -25
- 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/mcp/package.json +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +390 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/model-api.ts +169 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/bedrock.ts +252 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-auth.ts +231 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-stream.ts +288 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-translate.ts +203 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex.ts +211 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/forward.ts +169 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/gateway.ts +193 -0
- package/runtime/node_modules/@metro-labs/mcp/src/gateway/model-config.ts +180 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
- package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -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/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 +178 -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 +327 -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/station.ts +17 -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/stations.json +36 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -193
- package/dist/cache.js +0 -75
- package/dist/cli/actions.js +0 -139
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -159
- package/dist/cli/skill.js +0 -62
- package/dist/cli/util.js +0 -72
- package/dist/codex-rc.js +0 -236
- package/dist/dispatcher.js +0 -95
- package/dist/history.js +0 -84
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- package/dist/paths.js +0 -68
- package/dist/stations/discord.js +0 -200
- package/dist/stations/index.js +0 -73
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -213
- package/docs/agents.md +0 -185
- package/docs/uri-scheme.md +0 -71
- package/skills/metro/SKILL.md +0 -241
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readJson, writeSecure } from '../daemon/secure-fs.js';
|
|
3
|
+
import { agentsDir } from '../db/file-source.js';
|
|
4
|
+
import { isRecord } from '../daemon/is-record.js';
|
|
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 BedrockSettings {
|
|
11
|
+
region: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
model: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OpenRouterSettings {
|
|
17
|
+
apiKey: string;
|
|
18
|
+
model: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CodexSettings {
|
|
22
|
+
model: string;
|
|
23
|
+
auth: CodexTokens | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ModelConfig {
|
|
27
|
+
version: 1;
|
|
28
|
+
provider: Provider;
|
|
29
|
+
bedrock: BedrockSettings;
|
|
30
|
+
openrouter: OpenRouterSettings;
|
|
31
|
+
codex: CodexSettings;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Route {
|
|
35
|
+
provider: Provider;
|
|
36
|
+
model: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ModelConfigError extends Error {}
|
|
40
|
+
|
|
41
|
+
export const MODEL_FILE = 'model.json';
|
|
42
|
+
const MAX_FIELD = 512;
|
|
43
|
+
const PREFIX_RE = /^(anthropic|bedrock|openrouter|codex):(.+)$/;
|
|
44
|
+
|
|
45
|
+
const empty = (): ModelConfig => ({
|
|
46
|
+
version: 1,
|
|
47
|
+
provider: 'anthropic',
|
|
48
|
+
bedrock: { region: '', apiKey: '', model: '' },
|
|
49
|
+
openrouter: { apiKey: '', model: '' },
|
|
50
|
+
codex: { model: '', auth: null },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const isProvider = (value: unknown): value is Provider =>
|
|
54
|
+
typeof value === 'string' && (PROVIDERS as readonly string[]).includes(value);
|
|
55
|
+
|
|
56
|
+
const text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
|
|
57
|
+
const maybe = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
|
|
58
|
+
|
|
59
|
+
function tokensFromDisk(raw: unknown): CodexTokens | null {
|
|
60
|
+
if (!isRecord(raw)) return null;
|
|
61
|
+
const accessToken = text(raw.accessToken);
|
|
62
|
+
const refreshToken = text(raw.refreshToken);
|
|
63
|
+
const accountId = text(raw.accountId);
|
|
64
|
+
if (accessToken === '' || refreshToken === '' || accountId === '') return null;
|
|
65
|
+
return {
|
|
66
|
+
accessToken,
|
|
67
|
+
refreshToken,
|
|
68
|
+
idToken: text(raw.idToken),
|
|
69
|
+
accountId,
|
|
70
|
+
email: maybe(raw.email),
|
|
71
|
+
plan: maybe(raw.plan),
|
|
72
|
+
savedAt: text(raw.savedAt) === '' ? new Date(0).toISOString() : text(raw.savedAt),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fromDisk(raw: unknown): ModelConfig {
|
|
77
|
+
const base = empty();
|
|
78
|
+
if (!isRecord(raw)) return base;
|
|
79
|
+
const bedrock = isRecord(raw.bedrock) ? raw.bedrock : {};
|
|
80
|
+
const openrouter = isRecord(raw.openrouter) ? raw.openrouter : {};
|
|
81
|
+
const codex = isRecord(raw.codex) ? raw.codex : {};
|
|
82
|
+
return {
|
|
83
|
+
version: 1,
|
|
84
|
+
provider: isProvider(raw.provider) ? raw.provider : 'anthropic',
|
|
85
|
+
bedrock: { region: text(bedrock.region), apiKey: text(bedrock.apiKey), model: text(bedrock.model) },
|
|
86
|
+
openrouter: { apiKey: text(openrouter.apiKey), model: text(openrouter.model) },
|
|
87
|
+
codex: { model: text(codex.model), auth: tokensFromDisk(codex.auth) },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function readModelConfig(dir = agentsDir()): ModelConfig {
|
|
92
|
+
return fromDisk(readJson<unknown>(join(dir, MODEL_FILE), null, { warn: 'model-config: model.json is unreadable, so every request goes to Anthropic until it is fixed' }));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function writeModelConfig(cfg: ModelConfig, dir = agentsDir()): void {
|
|
96
|
+
writeSecure(join(dir, MODEL_FILE), JSON.stringify(cfg, null, 2));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function field(patch: Record<string, unknown>, key: string, current: string, what: string): string {
|
|
100
|
+
if (!(key in patch)) return current;
|
|
101
|
+
const value = patch[key];
|
|
102
|
+
if (typeof value !== 'string') throw new ModelConfigError(`${what} must be a string`);
|
|
103
|
+
if (value.length > MAX_FIELD) throw new ModelConfigError(`${what} is too long`);
|
|
104
|
+
return value.trim();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function applyModelUpdate(cfg: ModelConfig, patch: unknown): ModelConfig {
|
|
108
|
+
if (!isRecord(patch)) throw new ModelConfigError('body must be a JSON object');
|
|
109
|
+
const provider = 'provider' in patch ? patch.provider : cfg.provider;
|
|
110
|
+
if (!isProvider(provider)) throw new ModelConfigError(`provider must be one of ${PROVIDERS.join(', ')}`);
|
|
111
|
+
const bedrock = isRecord(patch.bedrock) ? patch.bedrock : {};
|
|
112
|
+
const openrouter = isRecord(patch.openrouter) ? patch.openrouter : {};
|
|
113
|
+
const codex = isRecord(patch.codex) ? patch.codex : {};
|
|
114
|
+
return {
|
|
115
|
+
version: 1,
|
|
116
|
+
provider,
|
|
117
|
+
bedrock: {
|
|
118
|
+
region: field(bedrock, 'region', cfg.bedrock.region, 'Bedrock region'),
|
|
119
|
+
apiKey: field(bedrock, 'apiKey', cfg.bedrock.apiKey, 'Bedrock API key'),
|
|
120
|
+
model: field(bedrock, 'model', cfg.bedrock.model, 'Bedrock model'),
|
|
121
|
+
},
|
|
122
|
+
openrouter: {
|
|
123
|
+
apiKey: field(openrouter, 'apiKey', cfg.openrouter.apiKey, 'OpenRouter API key'),
|
|
124
|
+
model: field(openrouter, 'model', cfg.openrouter.model, 'OpenRouter model'),
|
|
125
|
+
},
|
|
126
|
+
codex: { model: field(codex, 'model', cfg.codex.model, 'Codex model'), auth: cfg.codex.auth },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const setCodexAuth = (cfg: ModelConfig, auth: CodexTokens | null): ModelConfig => ({ ...cfg, codex: { ...cfg.codex, auth } });
|
|
131
|
+
|
|
132
|
+
const CHECKS: Record<Provider, [(cfg: ModelConfig) => boolean, string][]> = {
|
|
133
|
+
anthropic: [],
|
|
134
|
+
bedrock: [
|
|
135
|
+
[(cfg) => cfg.bedrock.apiKey === '', 'Bedrock needs an API key: add it on the Model page.'],
|
|
136
|
+
[(cfg) => cfg.bedrock.region === '', 'Bedrock needs a region: add it on the Model page.'],
|
|
137
|
+
],
|
|
138
|
+
openrouter: [
|
|
139
|
+
[(cfg) => cfg.openrouter.apiKey === '', 'OpenRouter needs an API key: add it on the Model page.'],
|
|
140
|
+
[(cfg) => cfg.openrouter.model === '', 'OpenRouter needs a model id: choose one on the Model page.'],
|
|
141
|
+
],
|
|
142
|
+
codex: [
|
|
143
|
+
[(cfg) => cfg.codex.auth === null, 'Codex is not connected: sign in with ChatGPT on the Model page.'],
|
|
144
|
+
[(cfg) => cfg.codex.model === '', 'Codex needs a model id: choose one on the Model page.'],
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export function notReady(cfg: ModelConfig, provider: Provider = cfg.provider): string | null {
|
|
149
|
+
return CHECKS[provider].find(([missing]) => missing(cfg))?.[1] ?? null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function publicModelConfig(cfg: ModelConfig): Record<string, unknown> {
|
|
153
|
+
const auth = cfg.codex.auth;
|
|
154
|
+
return {
|
|
155
|
+
provider: cfg.provider,
|
|
156
|
+
ready: notReady(cfg) === null,
|
|
157
|
+
reason: notReady(cfg),
|
|
158
|
+
bedrock: { region: cfg.bedrock.region, model: cfg.bedrock.model, hasKey: cfg.bedrock.apiKey !== '' },
|
|
159
|
+
openrouter: { model: cfg.openrouter.model, hasKey: cfg.openrouter.apiKey !== '' },
|
|
160
|
+
codex: { model: cfg.codex.model, signedIn: auth !== null, account: auth?.email ?? null, plan: auth?.plan ?? null },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function defaultModelFor(provider: Provider, requested: string, cfg: ModelConfig): string {
|
|
165
|
+
if (provider === 'openrouter') return requested.includes('/') ? requested : cfg.openrouter.model;
|
|
166
|
+
if (provider === 'bedrock') return cfg.bedrock.model === '' ? requested : cfg.bedrock.model;
|
|
167
|
+
if (provider === 'codex') return requested.startsWith('gpt-') ? requested : cfg.codex.model;
|
|
168
|
+
return requested;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function resolveRoute(requested: string, cfg: ModelConfig): Route {
|
|
172
|
+
const explicit = PREFIX_RE.exec(requested);
|
|
173
|
+
if (explicit !== null && isProvider(explicit[1]))
|
|
174
|
+
return { provider: explicit[1], model: (explicit[2] ?? '').trim() };
|
|
175
|
+
return { provider: cfg.provider, model: defaultModelFor(cfg.provider, requested, cfg) };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function routeLabel(route: Route): string {
|
|
179
|
+
return route.provider === 'anthropic' ? route.model : `${route.provider}:${route.model}`;
|
|
180
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { forwardTrainCall } from '../daemon/train-call.js';
|
|
2
|
+
import {
|
|
3
|
+
accountStationNames,
|
|
4
|
+
stationByName,
|
|
5
|
+
} from '../stations/registry.js';
|
|
6
|
+
import { listEndpoints } from '../daemon/tunnel.js';
|
|
7
|
+
import { hookUrl } from '../stations/attach.js';
|
|
8
|
+
import { agentIdForAccount, knownAccounts, type KnownAccount } from '../db/agent-map.js';
|
|
9
|
+
import { stationRunsHere } from '../db/materialize.js';
|
|
10
|
+
import { type StationName } from '../db/stations.js';
|
|
11
|
+
|
|
12
|
+
const accountId = (acc: unknown): string | undefined => {
|
|
13
|
+
const id = (acc as { id?: unknown }).id;
|
|
14
|
+
return typeof id === 'string' ? id : undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const asRecord = (acc: unknown): Record<string, unknown> | undefined =>
|
|
18
|
+
typeof acc === 'object' && acc !== null && !Array.isArray(acc)
|
|
19
|
+
? (acc as Record<string, unknown>)
|
|
20
|
+
: undefined;
|
|
21
|
+
|
|
22
|
+
function withAgentId(station: string, acc: unknown): unknown {
|
|
23
|
+
const rec = asRecord(acc);
|
|
24
|
+
if (rec === undefined) return acc;
|
|
25
|
+
const id = typeof rec.id === 'string' ? rec.id : undefined;
|
|
26
|
+
if (id === undefined) return acc;
|
|
27
|
+
const agentId = agentIdForAccount(station, id);
|
|
28
|
+
return agentId === undefined ? acc : { ...rec, agentId };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function attachAgentIds(
|
|
32
|
+
byStation: Record<string, unknown[]>,
|
|
33
|
+
): Record<string, unknown[]> {
|
|
34
|
+
const out: Record<string, unknown[]> = {};
|
|
35
|
+
for (const [station, list] of Object.entries(byStation))
|
|
36
|
+
out[station] = list.map((acc) => withAgentId(station, acc));
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function scopeAccountsByAgent(
|
|
41
|
+
byStation: Record<string, unknown[]>,
|
|
42
|
+
allowed: Set<string>,
|
|
43
|
+
): Record<string, unknown[]> {
|
|
44
|
+
const out: Record<string, unknown[]> = {};
|
|
45
|
+
for (const [station, list] of Object.entries(byStation)) {
|
|
46
|
+
out[station] = list.filter((acc) => {
|
|
47
|
+
const id = accountId(acc);
|
|
48
|
+
if (id === undefined) return false;
|
|
49
|
+
const agentId = agentIdForAccount(station, id);
|
|
50
|
+
return agentId !== undefined && allowed.has(agentId);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const hasTrain = (station: string): boolean =>
|
|
57
|
+
stationByName(station)?.hasTrain !== false;
|
|
58
|
+
|
|
59
|
+
function inCoreAccounts(station: string): unknown[] {
|
|
60
|
+
if (station !== 'webhook') return [];
|
|
61
|
+
return listEndpoints().flatMap((e) =>
|
|
62
|
+
e.secret === undefined || e.webhookId === undefined
|
|
63
|
+
? []
|
|
64
|
+
: [
|
|
65
|
+
{
|
|
66
|
+
id: e.id,
|
|
67
|
+
handle: `/api/webhooks/${e.webhookId}`,
|
|
68
|
+
endpoint: hookUrl(e.webhookId, e.secret),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ScopedAccounts {
|
|
75
|
+
accounts: Record<string, unknown[]>;
|
|
76
|
+
unavailable: string[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function liveAccounts(
|
|
80
|
+
station: string,
|
|
81
|
+
): Promise<{ rows: unknown[]; reachable: boolean }> {
|
|
82
|
+
if (!hasTrain(station))
|
|
83
|
+
return { rows: inCoreAccounts(station), reachable: true };
|
|
84
|
+
if (!stationRunsHere(station as StationName))
|
|
85
|
+
return { rows: [], reachable: true };
|
|
86
|
+
try {
|
|
87
|
+
const resp = await forwardTrainCall(station, 'accounts', {});
|
|
88
|
+
const list = (resp.result as { accounts?: unknown[] } | undefined)?.accounts;
|
|
89
|
+
return { rows: Array.isArray(list) ? list : [], reachable: true };
|
|
90
|
+
} catch {
|
|
91
|
+
return { rows: [], reachable: false };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mergeKnown(
|
|
96
|
+
station: string,
|
|
97
|
+
live: unknown[],
|
|
98
|
+
known: KnownAccount[],
|
|
99
|
+
): unknown[] {
|
|
100
|
+
const seen = new Set(
|
|
101
|
+
live.flatMap((row) => {
|
|
102
|
+
const id = accountId(row);
|
|
103
|
+
return id === undefined ? [] : [id];
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
const missing = known
|
|
107
|
+
.filter((r) => r.station === station && !seen.has(r.id))
|
|
108
|
+
.map((r) => ({ id: r.id }));
|
|
109
|
+
return [...live, ...missing];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function loadStations(): Promise<ScopedAccounts> {
|
|
113
|
+
const unavailable: string[] = [];
|
|
114
|
+
const accounts: Record<string, unknown[]> = {};
|
|
115
|
+
const known = knownAccounts();
|
|
116
|
+
await Promise.all(
|
|
117
|
+
accountStationNames().map(async (station) => {
|
|
118
|
+
const { rows, reachable } = await liveAccounts(station);
|
|
119
|
+
if (!reachable) unavailable.push(station);
|
|
120
|
+
accounts[station] = hasTrain(station) ? mergeKnown(station, rows, known) : rows;
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
return { accounts, unavailable };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function gatherAccounts(
|
|
127
|
+
allowedAgents?: Set<string>,
|
|
128
|
+
): Promise<Record<string, unknown[]>> {
|
|
129
|
+
const { accounts } = await loadStations();
|
|
130
|
+
return allowedAgents ? scopeAccountsByAgent(accounts, allowedAgents) : accounts;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function gatherAccountsForAgents(
|
|
134
|
+
allowed: Set<string>,
|
|
135
|
+
): Promise<ScopedAccounts> {
|
|
136
|
+
const { accounts, unavailable } = await loadStations();
|
|
137
|
+
return {
|
|
138
|
+
accounts: attachAgentIds(scopeAccountsByAgent(accounts, allowed)),
|
|
139
|
+
unavailable,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { stationForLine } from '../stations/registry.js';
|
|
2
|
+
import { kindOf, toCanonical } from '../stations/attachments.js';
|
|
3
|
+
import {
|
|
4
|
+
cleanupAttachments,
|
|
5
|
+
resolveAttachments,
|
|
6
|
+
type ResolvedAttachment,
|
|
7
|
+
} from '../stations/attach-resolve.js';
|
|
8
|
+
import type { CanonicalAttachment, ToolResult } from '../stations/types.js';
|
|
9
|
+
import { errResult, makeCtx, ok, okJson, toErr } from './ctx.js';
|
|
10
|
+
import { allowedAgents, currentIdentity } from './request-identity.js';
|
|
11
|
+
import { str } from './str.js';
|
|
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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { eventInScope } from '../db/agent-scope.js';
|
|
2
|
+
import { allowedAgents, type RequestIdentity } from './request-identity.js';
|
|
3
|
+
import { sessionScopeKey } from './session-route.js';
|
|
4
|
+
|
|
5
|
+
export class ChannelOwner {
|
|
6
|
+
private stream: RequestIdentity | undefined;
|
|
7
|
+
|
|
8
|
+
bindStream(identity: RequestIdentity): void {
|
|
9
|
+
this.stream = identity;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
releaseStream(): void {
|
|
13
|
+
this.stream = undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
scope(): Set<string> {
|
|
17
|
+
return allowedAgents(this.stream);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
streamBelongsTo(identity: RequestIdentity): boolean {
|
|
21
|
+
if (this.stream === undefined) return false;
|
|
22
|
+
return sessionScopeKey(this.stream) === sessionScopeKey(identity);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
inScope(line: string): boolean {
|
|
26
|
+
if (this.stream === undefined) return false;
|
|
27
|
+
return eventInScope(this.scope(), line);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { forwardTrainCall } from '../daemon/train-call.js';
|
|
3
|
+
import { errMsg, log } from '../daemon/log.js';
|
|
4
|
+
import { TrainError } from '../daemon/train-error.js';
|
|
5
|
+
import { type ToolContext, type ToolResult } from '../stations/types.js';
|
|
6
|
+
|
|
7
|
+
export async function metroCall(
|
|
8
|
+
train: string,
|
|
9
|
+
action: string,
|
|
10
|
+
args: Record<string, unknown>,
|
|
11
|
+
): Promise<{ result: unknown }> {
|
|
12
|
+
const line = typeof args.line === 'string' ? args.line : undefined;
|
|
13
|
+
log.info({ train, action, line }, 'metro: dispatching train call');
|
|
14
|
+
let response;
|
|
15
|
+
try {
|
|
16
|
+
response = await forwardTrainCall(train, action, args);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
log.warn(
|
|
19
|
+
{ train, action, line, err: errMsg(err) },
|
|
20
|
+
'metro: train call failed to dispatch',
|
|
21
|
+
);
|
|
22
|
+
throw new TrainError(
|
|
23
|
+
'metro_call_failed',
|
|
24
|
+
`metro ${action} ${train}: ${errMsg(err)}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
if (response.error) {
|
|
28
|
+
log.warn(
|
|
29
|
+
{ train, action, line, err: response.error },
|
|
30
|
+
'metro: train call returned error',
|
|
31
|
+
);
|
|
32
|
+
throw new TrainError(
|
|
33
|
+
'metro_call_error',
|
|
34
|
+
`metro ${action} ${train}: ${response.error}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
log.info({ train, action, line }, 'metro: train call ok');
|
|
38
|
+
return { result: response.result ?? null };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ok = (text: string): ToolResult => ({
|
|
42
|
+
content: [{ type: 'text', text }],
|
|
43
|
+
});
|
|
44
|
+
export const okJson = (v: unknown): ToolResult => ({
|
|
45
|
+
content: [{ type: 'text', text: JSON.stringify(v, null, 2) }],
|
|
46
|
+
});
|
|
47
|
+
export const errResult = (text: string): ToolResult => ({
|
|
48
|
+
content: [{ type: 'text', text }],
|
|
49
|
+
isError: true,
|
|
50
|
+
});
|
|
51
|
+
export const toErr = (name: string, e: unknown): ToolResult =>
|
|
52
|
+
e instanceof TrainError
|
|
53
|
+
? errResult(e.message)
|
|
54
|
+
: errResult(`metro ${name} failed: ${String(e)}`);
|
|
55
|
+
|
|
56
|
+
export const makeCtx = (station: string): ToolContext => ({
|
|
57
|
+
call: (action, args) => metroCall(station, action, args),
|
|
58
|
+
ok,
|
|
59
|
+
okJson,
|
|
60
|
+
err: errResult,
|
|
61
|
+
readFile: (path) => readFile(path),
|
|
62
|
+
});
|