From 6947230a1d2673872eee19d47314a978bcf8e7fb Mon Sep 17 00:00:00 2001 From: Wanjohi <71614375+wanjohiryan@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:04:29 +0300 Subject: [PATCH] feat: Add types and IDE comments to session --- packages/core/src/session/index.ts | 255 +++++------------ packages/core/src/session/session.sql.ts | 66 +++++ packages/core/src/session/types.ts | 286 ++++++++++++++++++++ packages/function/src/api/routes/session.ts | 13 +- packages/function/src/api/utils/auth.ts | 11 + packages/function/src/api/utils/error.ts | 138 ++++++++++ 6 files changed, 584 insertions(+), 185 deletions(-) create mode 100644 packages/core/src/session/session.sql.ts create mode 100644 packages/core/src/session/types.ts create mode 100644 packages/function/src/api/utils/error.ts diff --git a/packages/core/src/session/index.ts b/packages/core/src/session/index.ts index 80db8e79..f37262fe 100644 --- a/packages/core/src/session/index.ts +++ b/packages/core/src/session/index.ts @@ -1,189 +1,38 @@ import { z } from "zod/v4"; -import { Identifier } from "../id.js"; -import { Examples } from "../examples.js"; +import { fn } from "../utils/fn.js"; +import { sessionTable } from "./session.sql.js"; +import { + MachinePresetName, + ScreenResolutionPresetName, + VideoCodecName, + AudioCodecName, + RetryOptions, + SessionInfo, + type SessionInfo as SessionInfoType, + type Session as SessionInterface, + type Config as SessionConfig, +} from "./types.js"; -// Basic types we need -export const MachinePresetName = z.enum([ - "ns3-micro", - "ns3-mini", - "ns3-small", - "ns3-medium", - "ns3-large", -]); +// Re-export all types and schemas from types.ts +export { + MachinePresetName, + ScreenResolutionPresetName, + VideoCodecName, + AudioCodecName, + RetryOptions, + SessionInfo, +}; -export const ScreenResolutionPresetName = z.enum([ - "1280x720", - "1080x1920", - "1440x2560", - "2160x3840", -]); - -export const VideoCodecName = z.enum(["h264", "h265", "vp8", "vp9", "av1"]); - -export const AudioCodecName = z.enum(["opus", "aac", "mp3", "pcm"]); - -export const RetryOptions = z.object({ - maxAttempts: z.number().int().optional(), - factor: z.number().optional(), - minTimeoutInMs: z.number().int().optional(), - maxTimeoutInMs: z.number().int().optional(), - randomize: z.boolean().optional(), - outOfMemory: z - .object({ - machine: MachinePresetName.optional(), - }) - .optional(), -}); +export type { + SessionInfoType, + SessionInterface as Session, + SessionConfig as SessionConfigType, +}; +// Maintain backward compatibility with existing namespace structure export namespace Session { - export const Info = z - .object({ - id: Identifier.schema("session").meta({ - description: "The unique identifier of the session", - example: Examples.Session.id, - }), - description: z.string().optional().meta({ - description: "Description of what the session does", - example: Examples.Session.description, - }), - retry: RetryOptions.optional().meta({ - description: "Retry configuration for failed session attempts", - example: Examples.Session.retry, - }), - queue: z - .object({ - name: z.string().optional(), - concurrencyLimit: z.number().optional(), - }) - .optional() - .meta({ - description: "Queue configuration for concurrent session execution", - example: Examples.Session.queue, - }), - machine: MachinePresetName.optional().meta({ - description: "Machine preset for the session", - example: Examples.Session.machine, - }), - maxDuration: z.number().optional().meta({ - description: "Maximum duration in seconds the session can run", - example: Examples.Session.maxDuration, - }), - relayUrl: z.url().optional().meta({ - description: "Nestri relay URL for streaming and signaling", - example: Examples.Session.relayUrl, - }), - resolution: ScreenResolutionPresetName.optional().meta({ - description: "Display or stream resolution in 'WxH' format", - example: Examples.Session.resolution, - }), - framerate: z.number().optional().meta({ - description: "Display or stream framerate", - example: Examples.Session.framerate, - }), - room: z.string().optional().meta({ - description: "Room name or identifier for multiplayer sessions", - example: Examples.Session.room, - }), - gpuVendor: z.string().optional().meta({ - description: "Preferred GPU vendor", - example: Examples.Session.gpuVendor, - }), - gpuName: z.string().optional().meta({ - description: "Preferred GPU name", - example: Examples.Session.gpuName, - }), - gpuIndex: z.number().optional().meta({ - description: "Specific GPU index to use", - example: Examples.Session.gpuIndex, - }), - gpuCardPath: z.string().optional().meta({ - description: "Force specific GPU card by /dev/dri path", - example: Examples.Session.gpuCardPath, - }), - videoCodec: VideoCodecName.optional().meta({ - description: "Preferred video codec for streaming", - example: Examples.Session.videoCodec, - }), - videoEncoder: z.string().optional().meta({ - description: "Override video encoder implementation", - example: Examples.Session.videoEncoder, - }), - videoRateControl: z - .enum(["cbr", "vbr", "cq", "lossless"]) - .optional() - .meta({ - description: "Video rate control method", - example: Examples.Session.videoRateControl, - }), - videoCqp: z.number().min(1).max(51).optional().meta({ - description: "Constant Quantization Parameter for quality control", - example: Examples.Session.videoCqp, - }), - videoBitrate: z.number().min(1).optional().meta({ - description: "Target video bitrate in kbps", - example: Examples.Session.videoBitrate, - }), - videoBitrateMax: z.number().min(1).optional().meta({ - description: "Maximum video bitrate in kbps", - example: Examples.Session.videoBitrateMax, - }), - videoEncoderType: z.enum(["hardware", "software"]).optional().meta({ - description: "Encoder type: hardware or software", - example: Examples.Session.videoEncoderType, - }), - videoBitDepth: z - .union([z.literal(8), z.literal(10)]) - .optional() - .meta({ - description: "Video bit depth (8 or 10)", - example: Examples.Session.videoBitDepth, - }), - audioCaptureMethod: z - .enum(["pipewire", "pulse", "alsa"]) - .optional() - .meta({ - description: "Audio capture method", - example: Examples.Session.audioCaptureMethod, - }), - audioCodec: AudioCodecName.optional().meta({ - description: "Preferred audio codec for streaming", - example: Examples.Session.audioCodec, - }), - audioEncoder: z.string().optional().meta({ - description: "Override audio encoder binary", - example: Examples.Session.audioEncoder, - }), - audioRateControl: z.enum(["cbr", "vbr"]).optional().meta({ - description: "Audio rate control method", - example: Examples.Session.audioRateControl, - }), - audioBitrate: z.number().min(1).optional().meta({ - description: "Target audio bitrate in kbps", - example: Examples.Session.audioBitrate, - }), - audioBitrateMax: z.number().optional().meta({ - description: "Maximum audio bitrate in kbps", - example: Examples.Session.audioBitrateMax, - }), - zeroCopy: z.boolean().optional().meta({ - description: "Whether to use DMA-BUF for zero-copy video pipelines", - example: Examples.Session.zeroCopy, - }), - verbose: z.boolean().optional().meta({ - description: "Enable verbose output for debugging", - example: Examples.Session.verbose, - }), - }) - .meta({ - ref: "Session", - description: - "Represents a session configuration with streaming and execution options", - example: Examples.Session, - }); - - export type Info = z.infer; - - // Session interface that matches the SDK session object + export const Info = SessionInfo; + export type Info = SessionInfoType; export interface Session { /** Optional description of the session */ description?: string; @@ -195,9 +44,49 @@ export namespace Session { startAndWait: (options?: any) => any; } - // Session configuration type that includes the run function - export type Config = Omit & { + export type Config = Omit & { /** The function that executes when the session is started */ run: (ctx: any) => Promise; }; + + export const list = fn(z.void(), () => { + // Placeholder implementation + }); + + export function serialize( + input: typeof sessionTable.$inferSelect, + ): z.infer { + return { + id: input.id, + room: input.room || undefined, + retry: input.retry || undefined, + queue: input.queue || undefined, + machine: input.machine || undefined, + description: input.description || undefined, + maxDuration: input.maxDuration || undefined, + relayUrl: input.relayUrl || undefined, + resolution: input.resolution || undefined, + framerate: input.framerate || undefined, + gpuVendor: input.gpuVendor || undefined, + gpuName: input.gpuName || undefined, + gpuIndex: input.gpuIndex || undefined, + gpuCardPath: input.gpuCardPath || undefined, + videoCodec: input.videoCodec || undefined, + videoEncoder: input.videoEncoder || undefined, + videoRateControl: input.videoRateControl || undefined, + videoCqp: input.videoCqp || undefined, + videoBitrate: input.videoBitrate || undefined, + videoBitrateMax: input.videoBitrateMax || undefined, + videoEncoderType: input.videoEncoderType || undefined, + videoBitDepth: input.videoBitDepth || undefined, + audioCaptureMethod: input.audioCaptureMethod || undefined, + audioCodec: input.audioCodec || undefined, + audioEncoder: input.audioEncoder || undefined, + audioRateControl: input.audioRateControl || undefined, + audioBitrate: input.audioBitrate || undefined, + audioBitrateMax: input.audioBitrateMax || undefined, + zeroCopy: input.zeroCopy || undefined, + verbose: input.verbose || undefined, + }; + } } diff --git a/packages/core/src/session/session.sql.ts b/packages/core/src/session/session.sql.ts new file mode 100644 index 00000000..e3d0eedb --- /dev/null +++ b/packages/core/src/session/session.sql.ts @@ -0,0 +1,66 @@ +import { id, timestamps } from "../drizzle/types.js"; +import { + pgTable, + varchar, + text, + integer, + boolean, + jsonb, +} from "drizzle-orm/pg-core"; +import { + type Queue, + type RetryOptions, + type VideoCodecName, + type AudioCodecName, + type MachinePresetName, + type ScreenResolutionPresetName, + VideoEncoderType, + VideoRateControl, + AudioCaptureMethod, + AudioRateControl, + VideoBitDepth, +} from "./types.js"; + +export const sessionTable = pgTable("sessions", { + ...id, + ...timestamps, + description: text("description"), + retry: jsonb("retry").$type(), + queue: jsonb("queue").$type(), + machine: varchar("machine", { length: 200 }).$type(), + maxDuration: integer("max_duration"), + relayUrl: varchar("relay_url", { length: 500 }), + resolution: varchar("resolution", { + length: 20, + }).$type(), + framerate: integer("framerate"), + room: varchar("room", { length: 255 }), + gpuVendor: varchar("gpu_vendor", { length: 100 }), + gpuName: varchar("gpu_name", { length: 255 }), + gpuIndex: integer("gpu_index"), + gpuCardPath: varchar("gpu_card_path", { length: 500 }), + videoCodec: varchar("video_codec", { length: 20 }).$type(), + videoEncoder: varchar("video_encoder", { length: 100 }), + videoRateControl: varchar("video_rate_control", { + length: 20, + }).$type(), + videoCqp: integer("video_cqp"), + videoBitrate: integer("video_bitrate"), + videoBitrateMax: integer("video_bitrate_max"), + videoEncoderType: varchar("video_encoder_type", { + length: 20, + }).$type(), + videoBitDepth: integer("video_bit_depth").$type(), + audioCaptureMethod: varchar("audio_capture_method", { + length: 20, + }).$type(), + audioCodec: varchar("audio_codec", { length: 20 }).$type(), + audioEncoder: varchar("audio_encoder", { length: 100 }), + audioRateControl: varchar("audio_rate_control", { + length: 20, + }).$type(), + audioBitrate: integer("audio_bitrate"), + audioBitrateMax: integer("audio_bitrate_max"), + zeroCopy: boolean("zero_copy"), + verbose: boolean("verbose"), +}); diff --git a/packages/core/src/session/types.ts b/packages/core/src/session/types.ts new file mode 100644 index 00000000..46de8c46 --- /dev/null +++ b/packages/core/src/session/types.ts @@ -0,0 +1,286 @@ +import { z } from "zod/v4"; +import { Identifier } from "../id.js"; +import { Examples } from "../examples.js"; + +/** + * Available machine preset sizes for session execution + */ +export const MachinePresetName = z.enum([ + "ns3-micro", + "ns3-mini", + "ns3-small", + "ns3-medium", + "ns3-large", +]); + +export type MachinePresetName = z.infer; + +/** + * Available screen resolution presets in 'WxH' format + */ +export const ScreenResolutionPresetName = z.enum([ + "1280x720", + "1080x1920", + "1440x2560", + "2160x3840", +]); + +export type ScreenResolutionPresetName = z.infer< + typeof ScreenResolutionPresetName +>; + +/** + * Supported video codecs for streaming + */ +export const VideoCodecName = z.enum(["h264", "h265", "vp8", "vp9", "av1"]); + +export type VideoCodecName = z.infer; + +/** + * Supported audio codecs for streaming + */ +export const AudioCodecName = z.enum(["opus", "aac", "mp3", "pcm"]); + +export type AudioCodecName = z.infer; + +/** + * Retry configuration for failed operations + */ +export const RetryOptions = z.object({ + /** Maximum number of retry attempts */ + maxAttempts: z.number().int().optional(), + /** Exponential backoff factor for retry delays */ + factor: z.number().optional(), + /** Minimum timeout between retries in milliseconds */ + minTimeoutInMs: z.number().int().optional(), + /** Maximum timeout between retries in milliseconds */ + maxTimeoutInMs: z.number().int().optional(), + /** Whether to randomize retry delays to avoid thundering herd */ + randomize: z.boolean().optional(), + /** Configuration for handling out of memory errors */ + outOfMemory: z + .object({ + /** Machine preset to upgrade to on out of memory errors */ + machine: MachinePresetName.optional(), + }) + .optional(), +}); + +export type RetryOptions = z.infer; + +export const Queue = z.object({ + /** Queue name for organizing sessions */ + name: z.string().optional(), + /** Maximum number of concurrent sessions in queue */ + concurrencyLimit: z.number().optional(), +}); + +export type Queue = z.infer; + +export const VideoRateControl = z.enum(["cbr", "vbr", "cq", "lossless"]); + +export type VideoRateControl = z.infer; + +export const VideoEncoderType = z.enum(["hardware", "software"]); + +export type VideoEncoderType = z.infer; + +export const AudioCaptureMethod = z.enum(["pipewire", "pulse", "alsa"]); + +export type AudioCaptureMethod = z.infer; + +export const AudioRateControl = z.enum(["cbr", "vbr"]); + +export type AudioRateControl = z.infer; + +export const VideoBitDepth = z.union([z.literal(8), z.literal(10)]); + +export type VideoBitDepth = z.infer; + +/** + * Represents a session configuration with streaming and execution options + */ +export const SessionInfo = z + .object({ + /** The unique identifier of the session */ + id: Identifier.schema("session").meta({ + description: "The unique identifier of the session", + example: Examples.Session.id, + }), + /** Description of what the session does */ + description: z.string().optional().meta({ + description: "Description of what the session does", + example: Examples.Session.description, + }), + /** Retry configuration for failed session attempts */ + retry: RetryOptions.optional().meta({ + description: "Retry configuration for failed session attempts", + example: Examples.Session.retry, + }), + /** Queue configuration for concurrent session execution */ + queue: Queue.optional().meta({ + description: "Queue configuration for concurrent session execution", + example: Examples.Session.queue, + }), + /** Machine preset for the session */ + machine: MachinePresetName.optional().meta({ + description: "Machine preset for the session", + example: Examples.Session.machine, + }), + /** Maximum duration in seconds the session can run */ + maxDuration: z.number().optional().meta({ + description: "Maximum duration in seconds the session can run", + example: Examples.Session.maxDuration, + }), + /** Nestri relay URL for streaming and signaling */ + relayUrl: z.url().optional().meta({ + description: "Nestri relay URL for streaming and signaling", + example: Examples.Session.relayUrl, + }), + /** Display or stream resolution in 'WxH' format */ + resolution: ScreenResolutionPresetName.optional().meta({ + description: "Display or stream resolution in 'WxH' format", + example: Examples.Session.resolution, + }), + /** Display or stream framerate */ + framerate: z.number().optional().meta({ + description: "Display or stream framerate", + example: Examples.Session.framerate, + }), + /** Room name or identifier for multiplayer sessions */ + room: z.string().optional().meta({ + description: "Room name or identifier for multiplayer sessions", + example: Examples.Session.room, + }), + /** Preferred GPU vendor */ + gpuVendor: z.string().optional().meta({ + description: "Preferred GPU vendor", + example: Examples.Session.gpuVendor, + }), + /** Preferred GPU name */ + gpuName: z.string().optional().meta({ + description: "Preferred GPU name", + example: Examples.Session.gpuName, + }), + /** Specific GPU index to use */ + gpuIndex: z.number().optional().meta({ + description: "Specific GPU index to use", + example: Examples.Session.gpuIndex, + }), + /** Force specific GPU card by /dev/dri path */ + gpuCardPath: z.string().optional().meta({ + description: "Force specific GPU card by /dev/dri path", + example: Examples.Session.gpuCardPath, + }), + /** Preferred video codec for streaming */ + videoCodec: VideoCodecName.optional().meta({ + description: "Preferred video codec for streaming", + example: Examples.Session.videoCodec, + }), + /** Override video encoder implementation */ + videoEncoder: z.string().optional().meta({ + description: "Override video encoder implementation", + example: Examples.Session.videoEncoder, + }), + /** Video rate control method */ + videoRateControl: VideoRateControl.optional().meta({ + description: "Video rate control method", + example: Examples.Session.videoRateControl, + }), + /** Constant Quantization Parameter for quality control */ + videoCqp: z.number().min(1).max(51).optional().meta({ + description: "Constant Quantization Parameter for quality control", + example: Examples.Session.videoCqp, + }), + /** Target video bitrate in kbps */ + videoBitrate: z.number().min(1).optional().meta({ + description: "Target video bitrate in kbps", + example: Examples.Session.videoBitrate, + }), + /** Maximum video bitrate in kbps */ + videoBitrateMax: z.number().min(1).optional().meta({ + description: "Maximum video bitrate in kbps", + example: Examples.Session.videoBitrateMax, + }), + /** Encoder type: hardware or software */ + videoEncoderType: VideoEncoderType.optional().meta({ + description: "Encoder type: hardware or software", + example: Examples.Session.videoEncoderType, + }), + /** Video bit depth (8 or 10) */ + videoBitDepth: VideoBitDepth.optional().meta({ + description: "Video bit depth (8 or 10)", + example: Examples.Session.videoBitDepth, + }), + /** Audio capture method */ + audioCaptureMethod: AudioCaptureMethod.optional().meta({ + description: "Audio capture method", + example: Examples.Session.audioCaptureMethod, + }), + /** Preferred audio codec for streaming */ + audioCodec: AudioCodecName.optional().meta({ + description: "Preferred audio codec for streaming", + example: Examples.Session.audioCodec, + }), + /** Override audio encoder binary */ + audioEncoder: z.string().optional().meta({ + description: "Override audio encoder binary", + example: Examples.Session.audioEncoder, + }), + /** Audio rate control method */ + audioRateControl: AudioRateControl.optional().meta({ + description: "Audio rate control method", + example: Examples.Session.audioRateControl, + }), + /** Target audio bitrate in kbps */ + audioBitrate: z.number().min(1).optional().meta({ + description: "Target audio bitrate in kbps", + example: Examples.Session.audioBitrate, + }), + /** Maximum audio bitrate in kbps */ + audioBitrateMax: z.number().optional().meta({ + description: "Maximum audio bitrate in kbps", + example: Examples.Session.audioBitrateMax, + }), + /** Whether to use DMA-BUF for zero-copy video pipelines */ + zeroCopy: z.boolean().optional().meta({ + description: "Whether to use DMA-BUF for zero-copy video pipelines", + example: Examples.Session.zeroCopy, + }), + /** Enable verbose output for debugging */ + verbose: z.boolean().optional().meta({ + description: "Enable verbose output for debugging", + example: Examples.Session.verbose, + }), + }) + .meta({ + ref: "Session", + description: + "Represents a session configuration with streaming and execution options", + example: Examples.Session, + }); + +/** Inferred type from SessionInfo schema */ +export type SessionInfo = z.infer; + +/** + * Session interface that matches the SDK session object + */ +export interface Session { + /** Optional description of the session */ + description?: string; + + /** Start a session without waiting for the result */ + start: (options?: any, requestOptions?: any) => Promise; + + /** Start a session and wait for the result */ + startAndWait: (options?: any) => any; +} + +/** + * Session configuration type that includes the run function + */ +export type Config = Omit & { + /** The function that executes when the session is started */ + run: (ctx: any) => Promise; +}; diff --git a/packages/function/src/api/routes/session.ts b/packages/function/src/api/routes/session.ts index 4ab8fe26..4066993c 100644 --- a/packages/function/src/api/routes/session.ts +++ b/packages/function/src/api/routes/session.ts @@ -1,9 +1,11 @@ import { Hono } from "hono"; -import { describeRoute, resolver } from "hono-openapi"; +import { notPublic } from "../utils/auth.js"; +import { ErrorResponses } from "../utils/error.js"; import { Session } from "@nestri/core/session/index"; +import { describeRoute, resolver } from "hono-openapi"; export namespace SessionRoute { - export const route = new Hono().get( + export const route = new Hono().use(notPublic).get( "/", describeRoute({ tags: ["Session"], @@ -19,7 +21,14 @@ export namespace SessionRoute { }, }, }, + 401: ErrorResponses[401], + 403: ErrorResponses[403], + 429: ErrorResponses[429], }, }), + async (c) => { + const sessions = await Session.list(); + return c.json(sessions); + }, ); } diff --git a/packages/function/src/api/utils/auth.ts b/packages/function/src/api/utils/auth.ts index af7bf304..10cce52d 100644 --- a/packages/function/src/api/utils/auth.ts +++ b/packages/function/src/api/utils/auth.ts @@ -12,6 +12,17 @@ const client = createClient({ subjects, }); +export const notPublic: MiddlewareHandler = async (c, next) => { + const actor = Actor.use(); + if (actor.type === "public") + throw new VisibleError( + "authentication", + ErrorCodes.Authentication.UNAUTHORIZED, + "Missing authorization header", + ); + return next(); +}; + export const auth: MiddlewareHandler = async (c, next) => { const authHeader = c.req.query("authorization") ?? c.req.header("authorization"); diff --git a/packages/function/src/api/utils/error.ts b/packages/function/src/api/utils/error.ts new file mode 100644 index 00000000..4bd2f713 --- /dev/null +++ b/packages/function/src/api/utils/error.ts @@ -0,0 +1,138 @@ +import { resolver } from "hono-openapi"; +import { ErrorResponse } from "@nestri/core/error"; + +export const ErrorResponses = { + /**Validation error*/ + 400: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Validation error", + example: { + type: "validation", + code: "invalid_parameter", + message: "The request was invalid", + param: "email", + }, + }), + ), + }, + }, + description: + "Bad Request - The request could not be understood or was missing required parameters.", + }, + /**Authentication Error*/ + 401: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Authentication error", + example: { + type: "authentication", + code: "unauthorized", + message: "Authentication required", + }, + }), + ), + }, + }, + description: + "Unauthorized - Authentication is required and has failed or has not been provided.", + }, + /**Permission Error*/ + 403: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Permission error", + example: { + type: "forbidden", + code: "permission_denied", + message: "You do not have permission to access this resource", + }, + }), + ), + }, + }, + description: + "Forbidden - You do not have permission to access this resource.", + }, + /**Not Found Error*/ + 404: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Not found error", + example: { + type: "not_found", + code: "resource_not_found", + message: "The requested resource could not be found", + }, + }), + ), + }, + }, + description: "Not Found - The requested resource does not exist.", + }, + /**Conflict Error*/ + 409: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Conflict Error", + example: { + type: "already_exists", + code: "resource_already_exists", + message: + "The resource could not be created because it already exists", + }, + }), + ), + }, + }, + description: + "Conflict - The resource could not be created because it already exists.", + }, + /**Rate Limit Error*/ + 429: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Rate limit error", + example: { + type: "rate_limit", + code: "too_many_requests", + message: "Rate limit exceeded", + }, + }), + ), + }, + }, + description: + "Too Many Requests - You have made too many requests in a short period of time.", + }, + /**Internal Server Error*/ + 500: { + content: { + "application/json": { + schema: resolver( + ErrorResponse.meta({ + description: "Server error", + example: { + type: "internal", + code: "internal_error", + message: "Internal server error", + }, + }), + ), + }, + }, + description: "Internal Server Error - Something went wrong on our end.", + }, +};