485 lines · 16.6 KB · TypeScript
RawDownload
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));}