1import {
2 RelayManagedEndpointOrigin,
3 RelayManagedEndpointRuntimeConfig,
4} from "@t3tools/contracts/relay";
5import * as Effect from "effect/Effect";
6import * as Option from "effect/Option";
7import * as Schema from "effect/Schema";
8
9import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
10
11export const CLOUD_MINT_PUBLIC_KEY = "cloud-mint-ed25519-public-key";
12export const CLOUD_ENDPOINT_RUNTIME_CONFIG = "cloud-endpoint-runtime-config";
13export const CLOUD_ENDPOINT_CONFIRMED_ORIGIN = "cloud-endpoint-confirmed-origin";
14export const CLOUD_LINKED_USER_ID = "cloud-linked-user-id";
15export const RELAY_URL_SECRET = "cloud-relay-url";
16export const RELAY_ISSUER_SECRET = "cloud-relay-issuer";
17export const RELAY_ENVIRONMENT_CREDENTIAL_SECRET = "cloud-relay-environment-credential";
18export const PUBLISH_AGENT_ACTIVITY_SECRET = "cloud-publish-agent-activity";
19export const HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET = "cloud-hold-webhooks-while-offline";
20// Set when the relay rejects this environment's credential (401), as after the
21// environment is deregistered from T3 Connect. The link stays stored; the mark
22// tells `t3 connect status` and clients to offer a relink. Linking, unlinking,
23// or a later successful tunnel registration clears it.
24export const CLOUD_LINK_REVOKED = "cloud-link-revoked";
25
26export const encodeEndpointRuntimeConfigJson = Schema.encodeEffect(
27 Schema.fromJsonString(RelayManagedEndpointRuntimeConfig),
28);
29
30export const decodeRuntimeConfig = Schema.decodeUnknownOption(
31 Schema.fromJsonString(RelayManagedEndpointRuntimeConfig),
32);
33
34export const ManagedEndpointConfirmedOrigin = Schema.Struct({
35 config: RelayManagedEndpointRuntimeConfig,
36 origin: RelayManagedEndpointOrigin,
37});
38
39export const encodeConfirmedOriginJson = Schema.encodeEffect(
40 Schema.fromJsonString(ManagedEndpointConfirmedOrigin),
41);
42
43export const decodeConfirmedOrigin = Schema.decodeUnknownOption(
44 Schema.fromJsonString(ManagedEndpointConfirmedOrigin),
45);
46
47export function isAgentActivityPublishingEnabledValue(value: string | null): boolean {
48 return value === "true";
49}
50
51/** Whether agent-activity publishes currently leave this environment: the
52 publish opt-in secret is enabled and the relay link credentials exist.
53 Mirrors the per-publish gate in AgentAwarenessRelay, so the descriptor
54 capability never advertises publishing that the publisher would skip. */
55export const readAgentActivityPublishingActive = (
56 secrets: ServerSecretStore.ServerSecretStore["Service"],
57): Effect.Effect<boolean> =>
58 Effect.gen(function* () {
59 const readSecretString = (name: string) =>
60 secrets
61 .get(name)
62 .pipe(
63 Effect.map((bytes) =>
64 Option.isSome(bytes) ? new TextDecoder().decode(bytes.value) : null,
65 ),
66 );
67 const [enabled, url, environmentCredential] = yield* Effect.all([
68 readSecretString(PUBLISH_AGENT_ACTIVITY_SECRET),
69 readSecretString(RELAY_URL_SECRET),
70 readSecretString(RELAY_ENVIRONMENT_CREDENTIAL_SECRET),
71 ]);
72 // Empty strings are as unconfigured as missing files: the publisher's
73 // truthiness gate skips them, so the capability must too.
74 return (
75 isAgentActivityPublishingEnabledValue(enabled) &&
76 url !== null &&
77 url !== "" &&
78 environmentCredential !== null &&
79 environmentCredential !== ""
80 );
81 }).pipe(Effect.orElseSucceed(() => false));
82
83/** A non-empty secret as text, or null when it is missing, empty or unreadable. */
84const readSecretString = (name: string) =>
85 ServerSecretStore.ServerSecretStore.pipe(
86 Effect.flatMap((secrets) => secrets.get(name)),
87 Effect.map((bytes) =>
88 Option.isSome(bytes) && bytes.value.length > 0 ? new TextDecoder().decode(bytes.value) : null,
89 ),
90 Effect.orElseSucceed(() => null),
91 );
92
93/** The relay URL and environment credential, or null when not linked to T3 Connect. */
94export const readRelayConnection = Effect.all([
95 readSecretString(RELAY_URL_SECRET),
96 readSecretString(RELAY_ENVIRONMENT_CREDENTIAL_SECRET),
97]).pipe(
98 Effect.map(([url, environmentCredential]) =>
99 url && environmentCredential ? { url, environmentCredential } : null,
100 ),
101);
102
103/** Whether this environment opted in to T3 Connect holding webhooks while it is offline. */
104export const readHoldWebhooksWhileOffline = readSecretString(
105 HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET,
106).pipe(Effect.map((value) => value === "true"));