import * as Data from "effect/Data";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import { HttpClient } from "effect/unstable/http";import { EnvironmentCloudEndpointUnavailableError, type EnvironmentCloudLinkStateResult, EnvironmentHttpBadRequestError, EnvironmentHttpConflictError, EnvironmentHttpForbiddenError, EnvironmentHttpInternalServerError, EnvironmentHttpUnauthorizedError, EnvironmentId, WS_METHODS,} from "@t3tools/contracts";import { type RelayClientDeviceRecord, type RelayClientEnvironmentRecord, type RelayEnvironmentLinkResponse, type RelayProtectedError as RelayProtectedErrorType, type RelayManagedEndpointProviderKind,} from "@t3tools/contracts/relay";import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";import { request, runStream } from "@t3tools/client-runtime/rpc";import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc";import { ManagedRelay } from "@t3tools/client-runtime/relay"; import { readPrimaryEnvironmentDescriptor, readPrimaryEnvironmentTarget,} from "../environments/primary";import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";import { resolveCloudPublicConfig } from "./publicConfig";import { finishRelayClientInstall, reportRelayClientInstallProgress, requestRelayClientInstallConfirmation,} from "./relayClientInstallDialog"; export function normalizeRelayBaseUrl(value: string | null | undefined): string | null { const trimmed = value?.trim(); if (!trimmed) { return null; } return trimmed.replace(/\/+$/g, "");} function relayUrl(): string | null { return resolveCloudPublicConfig().relayUrl;} export class CloudEnvironmentLinkError extends Data.TaggedError("CloudEnvironmentLinkError")<{ readonly message: string; readonly cause?: unknown; readonly traceId?: string;}> {} const relayClientRpcError = (message: string) => (cause: unknown) => new CloudEnvironmentLinkError({ message, cause, }); function ensureRelayClientAvailable( environmentId: EnvironmentId,): Effect.Effect<void, CloudEnvironmentLinkError, EnvironmentRegistry> { return Effect.gen(function* () { const registry = yield* EnvironmentRegistry; const status = yield* registry .run(environmentId, request(WS_METHODS.cloudGetRelayClientStatus, {})) .pipe(Effect.mapError(relayClientRpcError("Could not check relay client availability."))); if (status.status === "available") return; if (status.status === "unsupported") { return yield* new CloudEnvironmentLinkError({ message: `T3 Code cannot install the relay client automatically on ${status.platform}-${status.arch}.`, }); } const confirmed = yield* Effect.tryPromise({ try: () => requestRelayClientInstallConfirmation(status.version), catch: relayClientRpcError("Could not confirm relay client installation."), }); if (!confirmed) { return yield* new CloudEnvironmentLinkError({ message: "Relay client installation was cancelled.", }); } const installed = yield* registry .runStream( environmentId, runStream(WS_METHODS.cloudInstallRelayClient, {}).pipe( Stream.tap((event) => Effect.sync(() => reportRelayClientInstallProgress(event))), ), ) .pipe( Stream.runLast, Effect.mapError(relayClientRpcError("Could not install the relay client.")), Effect.ensuring(Effect.sync(finishRelayClientInstall)), ); if (Option.isNone(installed) || installed.value.type !== "complete") { return yield* new CloudEnvironmentLinkError({ message: "The relay client install completed without a final status.", }); } const installedStatus = installed.value.status; if (installedStatus.status !== "available") { return yield* new CloudEnvironmentLinkError({ message: installedStatus.status === "unsupported" ? `T3 Code cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.` : "The relay client is still unavailable after installation.", }); } });} const isEnvironmentCloudApiError = Schema.is( Schema.Union([ EnvironmentHttpBadRequestError, EnvironmentHttpUnauthorizedError, EnvironmentHttpForbiddenError, EnvironmentHttpConflictError, EnvironmentHttpInternalServerError, EnvironmentCloudEndpointUnavailableError, ]),); function relayProtectedErrorMessage(error: RelayProtectedErrorType): string { switch (error._tag) { case "RelayAuthInvalidError": switch (error.reason) { case "missing_bearer": case "invalid_bearer": return "Relay rejected the cloud session token."; case "invalid_dpop": return "Relay rejected the DPoP proof."; case "not_authorized": return "Relay rejected the authenticated request."; } case "RelayEnvironmentLinkProofExpiredError": return "Relay rejected an expired environment link proof."; case "RelayEnvironmentLinkProofInvalidError": return `Relay rejected the environment link proof (${error.reason}).`; case "RelayEnvironmentConnectNotAuthorizedError": return "Relay rejected the environment connection request."; case "RelayEnvironmentEndpointUnavailableError": return `Relay could not reach the environment endpoint (${error.reason}).`; case "RelayEnvironmentEndpointTimedOutError": return "Relay timed out while contacting the environment endpoint."; case "RelayEnvironmentLinkFailedError": return `Relay could not link the environment (${error.reason}).`; case "RelayEnvironmentLinkUnavailableError": return `Relay cannot provision the managed endpoint (${error.reason}).`; case "RelayAgentActivityPublishProofExpiredError": return "Relay rejected an expired agent activity publish proof."; case "RelayAgentActivityPublishProofInvalidError": return `Relay rejected the agent activity publish proof (${error.reason}).`; case "RelayInternalError": return `Relay encountered an internal error (${error.reason}).`; }} function decodedRelayClientError(message: string) { return (cause: ManagedRelay.ManagedRelayClientError) => { const relayError = cause._tag === "ManagedRelayRequestFailedError" ? cause.relayError : undefined; const traceId = cause._tag === "ManagedRelayRequestFailedError" ? cause.traceId : undefined; const detail = relayError ? relayProtectedErrorMessage(relayError) : null; return new CloudEnvironmentLinkError({ message: detail ? `${message}: ${detail}` : message, cause, ...(traceId ? { traceId } : {}), }); };} function findEnvironmentCloudApiError(cause: unknown): { readonly message: string } | null { if (isEnvironmentCloudApiError(cause)) { return cause; } if (typeof cause !== "object" || cause === null) { return null; } return "cause" in cause ? findEnvironmentCloudApiError(cause.cause) : null;} const environmentApiError = (message: string) => (cause: unknown) => { const environmentError = findEnvironmentCloudApiError(cause); return new CloudEnvironmentLinkError({ message: environmentError ? `${message.replace(/[.:]$/, "")}: ${environmentError.message}` : message, cause, });}; function endpointOrigin(httpBaseUrl: string) { const url = new URL(httpBaseUrl); return { localHttpHost: "127.0.0.1", localHttpPort: Number(url.port || (url.protocol === "https:" ? 443 : 80)), };} const MANAGED_ENDPOINT_PROVIDER_KIND = "cloudflare_tunnel" satisfies RelayManagedEndpointProviderKind; function ensureLinkedEnvironmentMatches(input: { readonly expectedEnvironmentId: string; readonly expectedProviderKind: RelayManagedEndpointProviderKind; readonly link: RelayEnvironmentLinkResponse;}): Effect.Effect<void, CloudEnvironmentLinkError> { if (input.link.environmentId !== input.expectedEnvironmentId) { return new CloudEnvironmentLinkError({ message: "Relay returned credentials for a different environment.", }); } if (input.link.endpoint.providerKind !== input.expectedProviderKind) { return new CloudEnvironmentLinkError({ message: "Relay returned credentials for a different endpoint provider.", }); } return Effect.void;} export interface CloudLinkTarget { readonly environmentId: string; readonly label: string; readonly httpBaseUrl: string; readonly wsBaseUrl: string;} export type CloudLinkState = EnvironmentCloudLinkStateResult; export function collectCloudLinkTargets(input: { readonly primary: CloudLinkTarget | null; readonly saved: ReadonlyArray<CloudLinkTarget>;}): ReadonlyArray<CloudLinkTarget> { const byId = new Map<string, CloudLinkTarget>(); if (input.primary) { byId.set(input.primary.environmentId, input.primary); } for (const environment of input.saved) { if (!byId.has(environment.environmentId)) { byId.set(environment.environmentId, environment); } } return [...byId.values()];} export function readPrimaryCloudLinkTarget(): CloudLinkTarget | null { const descriptor = readPrimaryEnvironmentDescriptor(); const target = readPrimaryEnvironmentTarget(); if (!descriptor || !target) { return null; } return { environmentId: descriptor.environmentId, label: descriptor.label, httpBaseUrl: target.target.httpBaseUrl, wsBaseUrl: target.target.wsBaseUrl, };} export function listManagedCloudEnvironments(input: { readonly clerkToken: string;}): Effect.Effect< ReadonlyArray<RelayClientEnvironmentRecord>, CloudEnvironmentLinkError, ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { const configuredRelayUrl = relayUrl(); if (!configuredRelayUrl) { return yield* new CloudEnvironmentLinkError({ message: "T3CODE_RELAY_URL is not configured.", }); } const relayClient = yield* ManagedRelay.ManagedRelayClient; return yield* relayClient .listEnvironments({ clerkToken: input.clerkToken, }) .pipe( Effect.mapError( (cause) => new CloudEnvironmentLinkError({ message: "Could not list relay-managed environments.", cause, }), ), ); });} export function listCloudDevices(input: { readonly clerkToken: string;}): Effect.Effect< ReadonlyArray<RelayClientDeviceRecord>, CloudEnvironmentLinkError, ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { if (!relayUrl()) { return yield* new CloudEnvironmentLinkError({ message: "T3CODE_RELAY_URL is not configured.", }); } const relayClient = yield* ManagedRelay.ManagedRelayClient; return yield* relayClient.listDevices({ clerkToken: input.clerkToken }).pipe( Effect.mapError( (cause) => new CloudEnvironmentLinkError({ message: "Could not list cloud devices.", cause, }), ), ); });} export function readPrimaryCloudLinkState(input: { readonly target: CloudLinkTarget;}): Effect.Effect<CloudLinkState | null, CloudEnvironmentLinkError, HttpClient.HttpClient> { return Effect.gen(function* () { const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); return yield* client.connect .linkState({ headers: {} }) .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state."))); }).pipe(Effect.provide(primaryEnvironmentHttpLayer));} export function updatePrimaryCloudPreferences(input: { readonly target: CloudLinkTarget; readonly publishAgentActivity: boolean;}): Effect.Effect<CloudLinkState, CloudEnvironmentLinkError, HttpClient.HttpClient> { return Effect.gen(function* () { const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); return yield* client.connect .preferences({ headers: {}, payload: input, }) .pipe( Effect.mapError(environmentApiError("Could not update environment cloud preferences.")), ); }).pipe(Effect.provide(primaryEnvironmentHttpLayer));} export function unlinkPrimaryEnvironmentFromCloud(input: { readonly target: CloudLinkTarget; readonly clerkToken: string | null;}): Effect.Effect< void, CloudEnvironmentLinkError, HttpClient.HttpClient | ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); yield* client.connect .unlink({ headers: {} }) .pipe(Effect.mapError(environmentApiError("Could not unlink the environment from cloud."))); const configuredRelayUrl = relayUrl(); if (configuredRelayUrl && input.clerkToken) { const relayClient = yield* ManagedRelay.ManagedRelayClient; yield* relayClient .unlinkEnvironment({ clerkToken: input.clerkToken, environmentId: EnvironmentId.make(input.target.environmentId), }) .pipe( Effect.catch((cause) => Effect.logWarning("Could not revoke cloud environment link after local unlink.", { cause, }), ), ); } }).pipe(Effect.provide(primaryEnvironmentHttpLayer));} // "publish_only" links the environment to the relay for agent-activity// publishing alone: no managed tunnel is provisioned, so it can be toggled// independently of T3 Connect while clients reach the environment out of band.export type CloudLinkMode = "managed" | "publish_only"; const PUBLISH_ONLY_PROVIDER_KIND = "manual" satisfies RelayManagedEndpointProviderKind; export function linkPrimaryEnvironmentToCloud(input: { readonly target: CloudLinkTarget; readonly clerkToken: string; readonly mode?: CloudLinkMode;}): Effect.Effect< void, CloudEnvironmentLinkError, EnvironmentRegistry | HttpClient.HttpClient | ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { const configuredRelayUrl = relayUrl(); if (!configuredRelayUrl) { return yield* new CloudEnvironmentLinkError({ message: "T3CODE_RELAY_URL is not configured.", }); } const managedTunnelsEnabled = (input.mode ?? "managed") === "managed"; const providerKind = managedTunnelsEnabled ? MANAGED_ENDPOINT_PROVIDER_KIND : PUBLISH_ONLY_PROVIDER_KIND; const relayClient = yield* ManagedRelay.ManagedRelayClient; const environmentClient = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); if (managedTunnelsEnabled) { yield* ensureRelayClientAvailable(EnvironmentId.make(input.target.environmentId)); } const challenge = yield* relayClient .createEnvironmentLinkChallenge({ clerkToken: input.clerkToken, payload: { notificationsEnabled: true, liveActivitiesEnabled: true, managedTunnelsEnabled, }, }) .pipe( Effect.mapError( decodedRelayClientError( `${configuredRelayUrl}/v1/client/environment-link-challenges failed`, ), ), ); const proof = yield* environmentClient.connect .linkProof({ headers: {}, payload: { challenge: challenge.challenge, relayIssuer: configuredRelayUrl, endpoint: { httpBaseUrl: input.target.httpBaseUrl, wsBaseUrl: input.target.wsBaseUrl, providerKind, }, origin: endpointOrigin(input.target.httpBaseUrl), }, }) .pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof."))); const link = yield* relayClient .linkEnvironment({ clerkToken: input.clerkToken, payload: { proof, notificationsEnabled: true, liveActivitiesEnabled: true, managedTunnelsEnabled, }, }) .pipe( Effect.mapError( decodedRelayClientError(`${configuredRelayUrl}/v1/client/environment-links failed`), ), ); yield* ensureLinkedEnvironmentMatches({ expectedEnvironmentId: input.target.environmentId, expectedProviderKind: providerKind, link, }); yield* environmentClient.connect .relayConfig({ headers: {}, payload: { relayUrl: configuredRelayUrl, relayIssuer: link.relayIssuer, cloudUserId: link.cloudUserId, environmentCredential: link.environmentCredential, cloudMintPublicKey: link.cloudMintPublicKey, endpointRuntime: link.endpointRuntime, }, }) .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access."))); }).pipe(Effect.provide(primaryEnvironmentHttpLayer));}
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
import { HttpClient } from "effect/unstable/http";
import {
EnvironmentCloudEndpointUnavailableError,
type EnvironmentCloudLinkStateResult,
EnvironmentHttpBadRequestError,
EnvironmentHttpConflictError,
EnvironmentHttpForbiddenError,
EnvironmentHttpInternalServerError,
EnvironmentHttpUnauthorizedError,
EnvironmentId,
WS_METHODS,
} from "@t3tools/contracts";
import {
type RelayClientDeviceRecord,
type RelayClientEnvironmentRecord,
type RelayEnvironmentLinkResponse,
type RelayProtectedError as RelayProtectedErrorType,
type RelayManagedEndpointProviderKind,
} from "@t3tools/contracts/relay";
import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";
import { request, runStream } from "@t3tools/client-runtime/rpc";
import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc";
import { ManagedRelay } from "@t3tools/client-runtime/relay";
import {
readPrimaryEnvironmentDescriptor,
readPrimaryEnvironmentTarget,
} from "../environments/primary";
import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
import { resolveCloudPublicConfig } from "./publicConfig";
import {
finishRelayClientInstall,
reportRelayClientInstallProgress,
requestRelayClientInstallConfirmation,
} from "./relayClientInstallDialog";
export function normalizeRelayBaseUrl(value: string | null | undefined): string | null {
const trimmed = value?.trim();
if (!trimmed) {
return null;
}
return trimmed.replace(/\/+$/g, "");
}
function relayUrl(): string | null {
return resolveCloudPublicConfig().relayUrl;
}
export class CloudEnvironmentLinkError extends Data.TaggedError("CloudEnvironmentLinkError")<{
readonly message: string;
readonly cause?: unknown;
readonly traceId?: string;
}> {}
const relayClientRpcError = (message: string) => (cause: unknown) =>
new CloudEnvironmentLinkError({
message,
cause,
});
function ensureRelayClientAvailable(
environmentId: EnvironmentId,
): Effect.Effect<void, CloudEnvironmentLinkError, EnvironmentRegistry> {
return Effect.gen(function* () {
const registry = yield* EnvironmentRegistry;
const status = yield* registry
.run(environmentId, request(WS_METHODS.cloudGetRelayClientStatus, {}))
.pipe(Effect.mapError(relayClientRpcError("Could not check relay client availability.")));
if (status.status === "available") return;
if (status.status === "unsupported") {
return yield* new CloudEnvironmentLinkError({
message: `T3 Code cannot install the relay client automatically on ${status.platform}-${status.arch}.`,
});
}
const confirmed = yield* Effect.tryPromise({
try: () => requestRelayClientInstallConfirmation(status.version),
catch: relayClientRpcError("Could not confirm relay client installation."),
});
if (!confirmed) {
return yield* new CloudEnvironmentLinkError({
message: "Relay client installation was cancelled.",
});
}
const installed = yield* registry
.runStream(
environmentId,
runStream(WS_METHODS.cloudInstallRelayClient, {}).pipe(
Stream.tap((event) => Effect.sync(() => reportRelayClientInstallProgress(event))),
),
)
.pipe(
Stream.runLast,
Effect.mapError(relayClientRpcError("Could not install the relay client.")),
Effect.ensuring(Effect.sync(finishRelayClientInstall)),
);
if (Option.isNone(installed) || installed.value.type !== "complete") {
return yield* new CloudEnvironmentLinkError({
message: "The relay client install completed without a final status.",
});
}
const installedStatus = installed.value.status;
if (installedStatus.status !== "available") {
return yield* new CloudEnvironmentLinkError({
message:
installedStatus.status === "unsupported"
? `T3 Code cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.`
: "The relay client is still unavailable after installation.",
});
}
});
}
const isEnvironmentCloudApiError = Schema.is(
Schema.Union([
EnvironmentHttpBadRequestError,
EnvironmentHttpUnauthorizedError,
EnvironmentHttpForbiddenError,
EnvironmentHttpConflictError,
EnvironmentHttpInternalServerError,
EnvironmentCloudEndpointUnavailableError,
]),
);
function relayProtectedErrorMessage(error: RelayProtectedErrorType): string {
switch (error._tag) {
case "RelayAuthInvalidError":
switch (error.reason) {
case "missing_bearer":
case "invalid_bearer":
return "Relay rejected the cloud session token.";
case "invalid_dpop":
return "Relay rejected the DPoP proof.";
case "not_authorized":
return "Relay rejected the authenticated request.";
}
case "RelayEnvironmentLinkProofExpiredError":
return "Relay rejected an expired environment link proof.";
case "RelayEnvironmentLinkProofInvalidError":
return `Relay rejected the environment link proof (${error.reason}).`;
case "RelayEnvironmentConnectNotAuthorizedError":
return "Relay rejected the environment connection request.";
case "RelayEnvironmentEndpointUnavailableError":
return `Relay could not reach the environment endpoint (${error.reason}).`;
case "RelayEnvironmentEndpointTimedOutError":
return "Relay timed out while contacting the environment endpoint.";
case "RelayEnvironmentLinkFailedError":
return `Relay could not link the environment (${error.reason}).`;
case "RelayEnvironmentLinkUnavailableError":
return `Relay cannot provision the managed endpoint (${error.reason}).`;
case "RelayAgentActivityPublishProofExpiredError":
return "Relay rejected an expired agent activity publish proof.";
case "RelayAgentActivityPublishProofInvalidError":
return `Relay rejected the agent activity publish proof (${error.reason}).`;
case "RelayInternalError":
return `Relay encountered an internal error (${error.reason}).`;
}
}
function decodedRelayClientError(message: string) {
return (cause: ManagedRelay.ManagedRelayClientError) => {
const relayError =
cause._tag === "ManagedRelayRequestFailedError" ? cause.relayError : undefined;
const traceId = cause._tag === "ManagedRelayRequestFailedError" ? cause.traceId : undefined;
const detail = relayError ? relayProtectedErrorMessage(relayError) : null;
return new CloudEnvironmentLinkError({
message: detail ? `${message}: ${detail}` : message,
cause,
...(traceId ? { traceId } : {}),
});
};
}
function findEnvironmentCloudApiError(cause: unknown): { readonly message: string } | null {
if (isEnvironmentCloudApiError(cause)) {
return cause;
}
if (typeof cause !== "object" || cause === null) {
return null;
}
return "cause" in cause ? findEnvironmentCloudApiError(cause.cause) : null;
}
const environmentApiError = (message: string) => (cause: unknown) => {
const environmentError = findEnvironmentCloudApiError(cause);
return new CloudEnvironmentLinkError({
message: environmentError
? `${message.replace(/[.:]$/, "")}: ${environmentError.message}`
: message,
cause,
});
};
function endpointOrigin(httpBaseUrl: string) {
const url = new URL(httpBaseUrl);
return {
localHttpHost: "127.0.0.1",
localHttpPort: Number(url.port || (url.protocol === "https:" ? 443 : 80)),
};
}
const MANAGED_ENDPOINT_PROVIDER_KIND =
"cloudflare_tunnel" satisfies RelayManagedEndpointProviderKind;
function ensureLinkedEnvironmentMatches(input: {
readonly expectedEnvironmentId: string;
readonly expectedProviderKind: RelayManagedEndpointProviderKind;
readonly link: RelayEnvironmentLinkResponse;
}): Effect.Effect<void, CloudEnvironmentLinkError> {
if (input.link.environmentId !== input.expectedEnvironmentId) {
return new CloudEnvironmentLinkError({
message: "Relay returned credentials for a different environment.",
});
}
if (input.link.endpoint.providerKind !== input.expectedProviderKind) {
return new CloudEnvironmentLinkError({
message: "Relay returned credentials for a different endpoint provider.",
});
}
return Effect.void;
}
export interface CloudLinkTarget {
readonly environmentId: string;
readonly label: string;
readonly httpBaseUrl: string;
readonly wsBaseUrl: string;
}
export type CloudLinkState = EnvironmentCloudLinkStateResult;
export function collectCloudLinkTargets(input: {
readonly primary: CloudLinkTarget | null;
readonly saved: ReadonlyArray<CloudLinkTarget>;
}): ReadonlyArray<CloudLinkTarget> {
const byId = new Map<string, CloudLinkTarget>();
if (input.primary) {
byId.set(input.primary.environmentId, input.primary);
}
for (const environment of input.saved) {
if (!byId.has(environment.environmentId)) {
byId.set(environment.environmentId, environment);
}
}
return [...byId.values()];
}
export function readPrimaryCloudLinkTarget(): CloudLinkTarget | null {
const descriptor = readPrimaryEnvironmentDescriptor();
const target = readPrimaryEnvironmentTarget();
if (!descriptor || !target) {
return null;
}
return {
environmentId: descriptor.environmentId,
label: descriptor.label,
httpBaseUrl: target.target.httpBaseUrl,
wsBaseUrl: target.target.wsBaseUrl,
};
}
export function listManagedCloudEnvironments(input: {
readonly clerkToken: string;
}): Effect.Effect<
ReadonlyArray<RelayClientEnvironmentRecord>,
CloudEnvironmentLinkError,
ManagedRelay.ManagedRelayClient
> {
return Effect.gen(function* () {
const configuredRelayUrl = relayUrl();
if (!configuredRelayUrl) {
return yield* new CloudEnvironmentLinkError({
message: "T3CODE_RELAY_URL is not configured.",
});
}
const relayClient = yield* ManagedRelay.ManagedRelayClient;
return yield* relayClient
.listEnvironments({
clerkToken: input.clerkToken,
})
.pipe(
Effect.mapError(
(cause) =>
new CloudEnvironmentLinkError({
message: "Could not list relay-managed environments.",
cause,
}),
),
);
});
}
export function listCloudDevices(input: {
readonly clerkToken: string;
}): Effect.Effect<
ReadonlyArray<RelayClientDeviceRecord>,
CloudEnvironmentLinkError,
ManagedRelay.ManagedRelayClient
> {
return Effect.gen(function* () {
if (!relayUrl()) {
return yield* new CloudEnvironmentLinkError({
message: "T3CODE_RELAY_URL is not configured.",
});
}
const relayClient = yield* ManagedRelay.ManagedRelayClient;
return yield* relayClient.listDevices({ clerkToken: input.clerkToken }).pipe(
Effect.mapError(
(cause) =>
new CloudEnvironmentLinkError({
message: "Could not list cloud devices.",
cause,
}),
),
);
});
}
export function readPrimaryCloudLinkState(input: {
readonly target: CloudLinkTarget;
}): Effect.Effect<CloudLinkState | null, CloudEnvironmentLinkError, HttpClient.HttpClient> {
return Effect.gen(function* () {
const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
return yield* client.connect
.linkState({ headers: {} })
.pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state.")));
}).pipe(Effect.provide(primaryEnvironmentHttpLayer));
}
export function updatePrimaryCloudPreferences(input: {
readonly target: CloudLinkTarget;
readonly publishAgentActivity: boolean;
}): Effect.Effect<CloudLinkState, CloudEnvironmentLinkError, HttpClient.HttpClient> {
return Effect.gen(function* () {
const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
return yield* client.connect
.preferences({
headers: {},
payload: input,
})
.pipe(
Effect.mapError(environmentApiError("Could not update environment cloud preferences.")),
);
}).pipe(Effect.provide(primaryEnvironmentHttpLayer));
}
export function unlinkPrimaryEnvironmentFromCloud(input: {
readonly target: CloudLinkTarget;
readonly clerkToken: string | null;
}): Effect.Effect<
void,
CloudEnvironmentLinkError,
HttpClient.HttpClient | ManagedRelay.ManagedRelayClient
> {
return Effect.gen(function* () {
const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
yield* client.connect
.unlink({ headers: {} })
.pipe(Effect.mapError(environmentApiError("Could not unlink the environment from cloud.")));
const configuredRelayUrl = relayUrl();
if (configuredRelayUrl && input.clerkToken) {
const relayClient = yield* ManagedRelay.ManagedRelayClient;
yield* relayClient
.unlinkEnvironment({
clerkToken: input.clerkToken,
environmentId: EnvironmentId.make(input.target.environmentId),
})
.pipe(
Effect.catch((cause) =>
Effect.logWarning("Could not revoke cloud environment link after local unlink.", {
cause,
}),
),
);
}
}).pipe(Effect.provide(primaryEnvironmentHttpLayer));
}
// "publish_only" links the environment to the relay for agent-activity
// publishing alone: no managed tunnel is provisioned, so it can be toggled
// independently of T3 Connect while clients reach the environment out of band.
export type CloudLinkMode = "managed" | "publish_only";
const PUBLISH_ONLY_PROVIDER_KIND = "manual" satisfies RelayManagedEndpointProviderKind;
export function linkPrimaryEnvironmentToCloud(input: {
readonly target: CloudLinkTarget;
readonly clerkToken: string;
readonly mode?: CloudLinkMode;
}): Effect.Effect<
void,
CloudEnvironmentLinkError,
EnvironmentRegistry | HttpClient.HttpClient | ManagedRelay.ManagedRelayClient
> {
return Effect.gen(function* () {
const configuredRelayUrl = relayUrl();
if (!configuredRelayUrl) {
return yield* new CloudEnvironmentLinkError({
message: "T3CODE_RELAY_URL is not configured.",
});
}
const managedTunnelsEnabled = (input.mode ?? "managed") === "managed";
const providerKind = managedTunnelsEnabled
? MANAGED_ENDPOINT_PROVIDER_KIND
: PUBLISH_ONLY_PROVIDER_KIND;
const relayClient = yield* ManagedRelay.ManagedRelayClient;
const environmentClient = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
if (managedTunnelsEnabled) {
yield* ensureRelayClientAvailable(EnvironmentId.make(input.target.environmentId));
}
const challenge = yield* relayClient
.createEnvironmentLinkChallenge({
clerkToken: input.clerkToken,
payload: {
notificationsEnabled: true,
liveActivitiesEnabled: true,
managedTunnelsEnabled,
},
})
.pipe(
Effect.mapError(
decodedRelayClientError(
`${configuredRelayUrl}/v1/client/environment-link-challenges failed`,
),
),
);
const proof = yield* environmentClient.connect
.linkProof({
headers: {},
payload: {
challenge: challenge.challenge,
relayIssuer: configuredRelayUrl,
endpoint: {
httpBaseUrl: input.target.httpBaseUrl,
wsBaseUrl: input.target.wsBaseUrl,
providerKind,
},
origin: endpointOrigin(input.target.httpBaseUrl),
},
})
.pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof.")));
const link = yield* relayClient
.linkEnvironment({
clerkToken: input.clerkToken,
payload: {
proof,
notificationsEnabled: true,
liveActivitiesEnabled: true,
managedTunnelsEnabled,
},
})
.pipe(
Effect.mapError(
decodedRelayClientError(`${configuredRelayUrl}/v1/client/environment-links failed`),
),
);
yield* ensureLinkedEnvironmentMatches({
expectedEnvironmentId: input.target.environmentId,
expectedProviderKind: providerKind,
link,
});
yield* environmentClient.connect
.relayConfig({
headers: {},
payload: {
relayUrl: configuredRelayUrl,
relayIssuer: link.relayIssuer,
cloudUserId: link.cloudUserId,
environmentCredential: link.environmentCredential,
cloudMintPublicKey: link.cloudMintPublicKey,
endpointRuntime: link.endpointRuntime,
},
})
.pipe(Effect.mapError(environmentApiError("Could not configure environment relay access.")));
}).pipe(Effect.provide(primaryEnvironmentHttpLayer));
}