import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as SchemaTransformation from "effect/SchemaTransformation"; export const TrimmedString = Schema.String.pipe( Schema.decodeTo( Schema.String, SchemaTransformation.transformOrFail({ decode: (value) => Effect.succeed(value.trim()), encode: (value) => Effect.succeed(value.trim()), }), ), ); export const TrimmedNonEmptyString = TrimmedString.check(Schema.isNonEmpty()); export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)); export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)); export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })); /** * Safe categories for a failed DPoP proof. These describe the class of failure * without exposing proof contents or server-side authentication details. */ export const DpopFailureReason = Schema.Literals([ "time_window", "key_mismatch", "request_mismatch", "token_mismatch", "replay", "invalid_proof", ]); export type DpopFailureReason = typeof DpopFailureReason.Type; export const IsoDateTime = Schema.String; export type IsoDateTime = typeof IsoDateTime.Type; /** * Wire codec for server→client arrays whose element unions grow over time * (new literal members, new struct variants). Decoding drops elements the * current build cannot decode instead of failing the whole payload — a client * has to keep decoding configs sent by servers newer than itself, and * rejecting the payload would take down the connection over data the client * couldn't act on anyway. Encoding is the plain array encoding. */ /** * Same idea for one optional value whose literal set grows over time: a * member this build does not know decodes as absent rather than failing the * enclosing struct. Encoding is the plain encoding. */ export const ForwardCompatibleOptional = (value: Value) => { const decodeValue = Schema.decodeUnknownOption(value as never); return Schema.optionalKey( Schema.Unknown.pipe( Schema.decodeTo( Schema.UndefinedOr(value), SchemaTransformation.transform({ decode: (raw) => Option.isSome(decodeValue(raw)) ? (raw as Value["Encoded"]) : undefined, encode: (raw) => raw, }), ), ), ); }; /** * The nullable form, for a persisted setting whose literal set grows over * time: a member this build does not know (or a missing key) decodes as null * rather than failing the enclosing struct. Encoding is the plain encoding. */ export const ForwardCompatibleNullable = (value: Value) => { const decodeValue = Schema.decodeUnknownOption(value as never); return Schema.Unknown.pipe( Schema.decodeTo( Schema.NullOr(value), SchemaTransformation.transform({ decode: (raw) => (Option.isSome(decodeValue(raw)) ? (raw as Value["Encoded"]) : null), encode: (raw) => raw, }), ), ); }; export const ForwardCompatibleArray = (element: Element) => { const decodeElement = Schema.decodeUnknownOption(element as never); return Schema.Array(Schema.Unknown).pipe( Schema.decodeTo( Schema.Array(element), SchemaTransformation.transform, ReadonlyArray>({ decode: (values) => values.filter((value) => Option.isSome(decodeElement(value))) as ReadonlyArray< Element["Encoded"] >, encode: (values) => values, }), ), ); }; /** * Construct a branded identifier. Enforces non-empty trimmed strings */ const makeEntityId = (brand: Brand) => { return TrimmedNonEmptyString.pipe(Schema.brand(brand)); }; export const ThreadId = makeEntityId("ThreadId"); export type ThreadId = typeof ThreadId.Type; export const ProjectId = makeEntityId("ProjectId"); export type ProjectId = typeof ProjectId.Type; export const EnvironmentId = makeEntityId("EnvironmentId"); export type EnvironmentId = typeof EnvironmentId.Type; export const CommandId = makeEntityId("CommandId"); export type CommandId = typeof CommandId.Type; export const EventId = makeEntityId("EventId"); export type EventId = typeof EventId.Type; export const MessageId = makeEntityId("MessageId"); export type MessageId = typeof MessageId.Type; export const TurnId = makeEntityId("TurnId"); export type TurnId = typeof TurnId.Type; export const AuthSessionId = makeEntityId("AuthSessionId"); export type AuthSessionId = typeof AuthSessionId.Type; export const RpcClientId = NonNegativeInt.pipe(Schema.brand("RpcClientId")); export type RpcClientId = typeof RpcClientId.Type; /** * Which client surface a connection or command comes from. Unlike * `AuthClientMetadataDeviceType` (a UA-style device class where web and * desktop are both "desktop"), this names the actual product surface. * Optional everywhere it appears: old clients never send it. */ export const ClientSurface = Schema.Literals(["web", "desktop", "mobile", "cli"]); export type ClientSurface = typeof ClientSurface.Type; export const ClientOs = Schema.Literals([ "macOS", "Windows", "Linux", "iOS", "Android", "ChromeOS", "other", "unknown", ]); export type ClientOs = typeof ClientOs.Type; export const ClientDeviceType = Schema.Literals(["desktop", "phone", "tablet", "unknown"]); export type ClientDeviceType = typeof ClientDeviceType.Type; export const ClientWebDeployment = Schema.Literals(["hosted", "server"]); export type ClientWebDeployment = typeof ClientWebDeployment.Type; export const ClientConnectionMethod = Schema.Literals(["direct", "ssh", "relay", "unknown"]); export type ClientConnectionMethod = typeof ClientConnectionMethod.Type; export const ProviderItemId = makeEntityId("ProviderItemId"); export type ProviderItemId = typeof ProviderItemId.Type; export const RuntimeSessionId = makeEntityId("RuntimeSessionId"); export type RuntimeSessionId = typeof RuntimeSessionId.Type; export const RuntimeItemId = makeEntityId("RuntimeItemId"); export type RuntimeItemId = typeof RuntimeItemId.Type; export const RuntimeRequestId = makeEntityId("RuntimeRequestId"); export type RuntimeRequestId = typeof RuntimeRequestId.Type; export const RuntimeTaskId = makeEntityId("RuntimeTaskId"); export type RuntimeTaskId = typeof RuntimeTaskId.Type; export const ApprovalRequestId = makeEntityId("ApprovalRequestId"); export type ApprovalRequestId = typeof ApprovalRequestId.Type; export const CheckpointRef = makeEntityId("CheckpointRef"); export type CheckpointRef = typeof CheckpointRef.Type;