{"version":3,"sources":["../../src/inference/avatar.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Room } from '@livekit/rtc-node';\nimport { TrackKind } from '@livekit/rtc-node';\nimport { RoomServiceClient } from 'livekit-server-sdk';\nimport { randomUUID } from 'node:crypto';\nimport { APIConnectionError, APIError, APIStatusError, APITimeoutError } from '../_exceptions.js';\nimport { getJobContext } from '../job.js';\nimport { log } from '../log.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS, intervalForRetry } from '../types.js';\nimport type { AgentSession } from '../voice/agent_session.js';\nimport {\n  AvatarSession as BaseAvatarSession,\n  DataStreamAudioOutput,\n} from '../voice/avatar/index.js';\nimport {\n  INFERENCE_PROVIDER_HEADER,\n  buildMetadataHeaders,\n  createAccessToken,\n  getDefaultInferenceUrl,\n} from './utils.js';\n\nconst DEFAULT_SAMPLE_RATE = 16000;\n\nexport interface LemonSliceOptions {\n  /** Appearance source; mutually exclusive with a model-string agent id. */\n  image_url?: string;\n  /** Speaking prompt, mapped to the provider's agent_prompt. */\n  prompt?: string;\n  /** Idle prompt, mapped to the provider's agent_idle_prompt. */\n  idle_prompt?: string;\n  /** Provider idle timeout in seconds; the gateway clamps it. */\n  idle_timeout?: number;\n  [key: string]: unknown;\n}\n\nconst OPTION_TO_PAYLOAD_FIELD: Record<string, string> = {\n  image_url: 'image_url',\n  prompt: 'prompt',\n  idle_prompt: 'idle_prompt',\n  idle_timeout: 'idle_timeout_s',\n};\n\nexport type AvatarModel = 'lemonslice' | `lemonslice/${string}` | (string & NonNullable<unknown>);\n\nexport interface AvatarSessionOptions {\n  /** `\"<provider>\"` or `\"<provider>/<avatar_id>\"`, e.g. `\"lemonslice/agent_abc\"`. */\n  model: AvatarModel;\n  /** Room identity for the avatar worker. Defaults to `\"<provider>-inference-avatar\"`. */\n  avatarParticipantIdentity?: string;\n  /** Display name for the avatar worker. */\n  avatarParticipantName?: string;\n  /** Provider-specific options. For LemonSlice, use {@link LemonSliceOptions}. */\n  extraKwargs?: LemonSliceOptions | Record<string, unknown>;\n  /** Inference gateway base URL. Defaults to the environment's gateway. */\n  baseURL?: string;\n  /** Gateway API key. Falls back to LIVEKIT_INFERENCE_API_KEY then LIVEKIT_API_KEY. */\n  apiKey?: string;\n  /** Gateway API secret. Falls back to LIVEKIT_INFERENCE_API_SECRET then LIVEKIT_API_SECRET. */\n  apiSecret?: string;\n  /** Optional fetch implementation, primarily for tests. */\n  fetch?: typeof fetch;\n  /** Retry and whole-request timeout options for gateway calls. */\n  connOptions?: APIConnectOptions;\n}\n\nexport interface AvatarSessionStartOptions {\n  /** LiveKit server URL. Falls back to LIVEKIT_URL. */\n  livekitUrl?: string;\n  /** LiveKit API key for resolving room SID when needed. Falls back to LIVEKIT_API_KEY. */\n  livekitApiKey?: string;\n  /** LiveKit API secret for resolving room SID when needed. Falls back to LIVEKIT_API_SECRET. */\n  livekitApiSecret?: string;\n}\n\ntype CreateSessionResponse = {\n  session_id?: string;\n  provider_session_id?: string;\n  terminate_token?: string;\n  /** The identity the gateway minted the worker token for; authoritative over the local value. */\n  avatar_identity?: string;\n  sample_rate?: number;\n};\n\nexport function parseAvatarModel(model: string): [provider: string, avatarId: string | undefined] {\n  const [rawProvider, ...rest] = model.split('/');\n  const provider = rawProvider?.trim() ?? '';\n  if (!provider) {\n    throw new Error(\n      `invalid avatar model string: ${JSON.stringify(model)} (expected 'provider' or 'provider/<id>')`,\n    );\n  }\n  const avatarId = rest.join('/').trim();\n  return [provider, avatarId || undefined];\n}\n\n/**\n * An avatar session provisioned through LiveKit Inference.\n *\n * Unlike BYOK avatar plugins, this calls the LiveKit Inference gateway with gateway\n * credentials and lets the gateway mint the avatar worker's room token. Media and RPC\n * still flow in-room over DataStream, exactly as the BYOK plugins do.\n */\nexport class AvatarSession extends BaseAvatarSession {\n  private readonly logger = log();\n  private readonly providerName: string;\n  private readonly avatarId?: string;\n  private readonly extraKwargs: Record<string, unknown>;\n  private readonly connOptions: APIConnectOptions;\n  private readonly fetchFn: typeof fetch;\n  private readonly baseURL: string;\n  private readonly apiKey: string;\n  private readonly apiSecret: string;\n  private readonly avatarParticipantIdentity: string;\n  private readonly avatarParticipantName: string;\n  private readonly createIdempotencyKey = randomUUID().replaceAll('-', '');\n\n  private _started = false;\n  private _sessionId: string | null = null;\n  private _providerSessionId: string | null = null;\n  private _terminateToken: string | null = null;\n\n  constructor(options: AvatarSessionOptions) {\n    super();\n\n    const [provider, avatarId] = parseAvatarModel(options.model);\n    this.providerName = provider;\n    this.avatarId = avatarId;\n    this.extraKwargs = { ...(options.extraKwargs ?? {}) };\n    if (this.avatarId && 'image_url' in this.extraKwargs) {\n      throw new Error(\n        `pass either a catalog id in the model string ('${this.providerName}/<avatar_id>') or image_url, not both`,\n      );\n    }\n\n    this.connOptions = options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS;\n    this.fetchFn = options.fetch ?? fetch;\n    this.baseURL = options.baseURL ?? getDefaultInferenceUrl();\n    this.apiKey =\n      options.apiKey ?? process.env.LIVEKIT_INFERENCE_API_KEY ?? process.env.LIVEKIT_API_KEY ?? '';\n    if (!this.apiKey) {\n      throw new Error(\n        'apiKey is required, either as argument or set LIVEKIT_API_KEY environment variable',\n      );\n    }\n    this.apiSecret =\n      options.apiSecret ??\n      process.env.LIVEKIT_INFERENCE_API_SECRET ??\n      process.env.LIVEKIT_API_SECRET ??\n      '';\n    if (!this.apiSecret) {\n      throw new Error(\n        'apiSecret is required, either as argument or set LIVEKIT_API_SECRET environment variable',\n      );\n    }\n\n    this.avatarParticipantIdentity =\n      options.avatarParticipantIdentity ?? `${this.providerName}-inference-avatar`;\n    this.avatarParticipantName = options.avatarParticipantName ?? this.avatarParticipantIdentity;\n  }\n\n  override get avatarIdentity(): string {\n    return this.avatarParticipantIdentity;\n  }\n\n  override get provider(): string {\n    return this.providerName;\n  }\n\n  /** The gateway-generated avatar session id, available after start(). */\n  get sessionId(): string | null {\n    return this._sessionId;\n  }\n\n  /** The provider's own session id, available after start(). */\n  get providerSessionId(): string | null {\n    return this._providerSessionId;\n  }\n\n  override async start(\n    agentSession: AgentSession,\n    room: Room,\n    options: AvatarSessionStartOptions = {},\n  ): Promise<void> {\n    // Set synchronously before any await: two overlapping start() calls must not both\n    // pass the guard and create two separately-billed provider sessions. The session ids\n    // used to arrive only after several awaits (token mint, sid resolution, gateway create).\n    if (this._started) {\n      throw new Error(\n        'AvatarSession.start() may only be called once per instance; create a new AvatarSession to start another avatar',\n      );\n    }\n    this._started = true;\n\n    let sessionCreated = false;\n    try {\n      await super.start(agentSession, room);\n\n      const livekitUrl = options.livekitUrl ?? process.env.LIVEKIT_URL;\n      if (!livekitUrl) {\n        throw new Error(\n          'livekitUrl must be set by argument or the LIVEKIT_URL environment variable',\n        );\n      }\n      const jobCtx = getJobContext(false);\n      // `@livekit/rtc-node` `Room.name` is an empty string (not undefined) before the room\n      // connects, so use `||` to fall back to the job's room name in that case.\n      const roomName = room.name || jobCtx?.job.room?.name;\n      if (!roomName) {\n        throw new Error('failed to get room name');\n      }\n\n      let localParticipantIdentity: string | undefined;\n      let roomSid: string | undefined;\n      if (jobCtx) {\n        localParticipantIdentity =\n          jobCtx.agent?.identity ??\n          room.localParticipant?.identity ??\n          jobCtx.info.acceptArguments.identity;\n        roomSid =\n          jobCtx.job.room?.sid || (await resolveRoomSid(room, roomName, livekitUrl, options));\n      } else if (room.isConnected) {\n        localParticipantIdentity = room.localParticipant?.identity;\n        roomSid = await resolveRoomSid(room, roomName, livekitUrl, options);\n      } else {\n        throw new Error(\n          'AvatarSession.start() needs a connected room or an agent job context; connect the room before calling start()',\n        );\n      }\n      if (!localParticipantIdentity) {\n        throw new Error('failed to get local participant identity');\n      }\n      if (!roomSid) {\n        throw new Error('failed to get room sid');\n      }\n\n      const createResp = await this._createSession({\n        roomName,\n        roomSid,\n        livekitUrl,\n        agentIdentity: localParticipantIdentity,\n      });\n      sessionCreated = true;\n\n      this._sessionId = createResp.session_id ?? null;\n      this._providerSessionId = createResp.provider_session_id ?? null;\n      this._terminateToken = createResp.terminate_token ?? null;\n      const sampleRate = createResp.sample_rate ?? DEFAULT_SAMPLE_RATE;\n\n      if (!this._providerSessionId) {\n        this.logger.warn(\n          { provider: this.providerName, sessionId: this._sessionId },\n          'avatar gateway create response had no provider_session_id; this session cannot be explicitly terminated and will bill until its provider idle timeout',\n        );\n      } else if (!this._terminateToken) {\n        this.logger.warn(\n          {\n            provider: this.providerName,\n            sessionId: this._sessionId,\n            providerSessionId: this._providerSessionId,\n          },\n          'avatar gateway create response had no terminate_token; this session cannot be explicitly terminated and will bill until its provider idle timeout',\n        );\n      }\n\n      // The worker joins under the identity in the minted token, so a mismatch means every\n      // identity-keyed operation here (RPC destination, join wait, removeParticipant cleanup)\n      // is addressing a participant that will never exist. Older gateways omit the field.\n      if (\n        createResp.avatar_identity &&\n        createResp.avatar_identity !== this.avatarParticipantIdentity\n      ) {\n        this.logger.warn(\n          {\n            provider: this.providerName,\n            sessionId: this._sessionId,\n            'lk.pii.requested_identity': this.avatarParticipantIdentity,\n            'lk.pii.minted_identity': createResp.avatar_identity,\n          },\n          'avatar gateway minted the worker token for a different identity than requested; the avatar participant will join as the minted identity and this session will not reach it',\n        );\n      }\n\n      agentSession.output.audio = new DataStreamAudioOutput({\n        room,\n        destinationIdentity: this.avatarParticipantIdentity,\n        sampleRate,\n        waitRemoteTrack: TrackKind.KIND_VIDEO,\n        waitPlaybackStart: true,\n      });\n\n      this.logger.debug(\n        {\n          provider: this.providerName,\n          sessionId: this._sessionId,\n          providerSessionId: this._providerSessionId,\n        },\n        'inference avatar session created',\n      );\n    } catch (error) {\n      if (!sessionCreated) {\n        try {\n          await this._rollbackStart();\n        } finally {\n          this._started = false;\n        }\n      }\n      throw error;\n    }\n  }\n\n  override async aclose(): Promise<void> {\n    const providerSessionId = this._providerSessionId;\n    const terminateToken = this._terminateToken;\n    let terminateError: unknown;\n    try {\n      if (providerSessionId && terminateToken) {\n        try {\n          await this._terminateSession(providerSessionId, terminateToken);\n          this._providerSessionId = null;\n          this._terminateToken = null;\n        } catch (error) {\n          terminateError = error;\n        }\n      } else if (providerSessionId) {\n        this.logger.debug(\n          { provider: this.providerName, providerSessionId },\n          'no terminate_token for this avatar session; skipping explicit terminate',\n        );\n      }\n    } finally {\n      await super.aclose();\n    }\n\n    if (terminateError !== undefined) {\n      // Rethrow after base cleanup so a failed terminate escalates through the\n      // job-shutdown handler (logger.error 'error while shutting down the job')\n      // rather than being a silent warn. _terminateSession has already retried; the\n      // provider session will keep billing until its idle timeout.\n      throw new Error(\n        `failed to terminate inference avatar session after retries ` +\n          `(provider=${this.providerName}, providerSessionId=${providerSessionId}); ` +\n          `it will keep billing until its provider idle timeout`,\n        { cause: terminateError },\n      );\n    }\n  }\n\n  /** @internal */\n  async _createSession({\n    roomName,\n    roomSid,\n    livekitUrl,\n    agentIdentity,\n  }: {\n    roomName: string;\n    roomSid: string;\n    livekitUrl: string;\n    agentIdentity: string;\n  }): Promise<CreateSessionResponse> {\n    // roomName / avatar identity / agent identity are the inputs the gateway mints the\n    // worker room token from: roomJoin is scoped to roomName, the worker joins as the\n    // avatar identity/name, and lk.publish_on_behalf is set to agentIdentity.\n    const payload: Record<string, unknown> = {\n      provider: this.providerName,\n      livekit_url: livekitUrl,\n      room_name: roomName,\n      room_sid: roomSid,\n      avatar_identity: this.avatarParticipantIdentity,\n      avatar_name: this.avatarParticipantName,\n      agent_identity: agentIdentity,\n    };\n    if (this.avatarId) {\n      payload.avatar_id = this.avatarId;\n    }\n\n    const extra: Record<string, unknown> = {};\n    for (const [key, value] of Object.entries(this.extraKwargs)) {\n      const payloadField = OPTION_TO_PAYLOAD_FIELD[key];\n      if (payloadField) {\n        payload[payloadField] = value;\n      } else {\n        extra[key] = value;\n      }\n    }\n    if (Object.keys(extra).length > 0) {\n      payload.extra_kwargs = extra;\n    }\n\n    // Idempotency-Key stays stable across both internal request retries and start() retries\n    // so an ambiguous failure cannot provision a second paid provider session.\n    const url = `${this.baseURL.replace(/\\/$/, '')}/avatar/sessions`;\n    const response = await this.postJsonRetrying(\n      url,\n      payload,\n      async () => ({\n        ...(await this.authHeaders()),\n        'Idempotency-Key': this.createIdempotencyKey,\n      }),\n      'avatar gateway create',\n    );\n    return (await response.json()) as CreateSessionResponse;\n  }\n\n  /** @internal */\n  async _terminateSession(providerSessionId: string, terminateToken: string): Promise<void> {\n    const url = `${this.baseURL.replace(/\\/$/, '')}/avatar/sessions/terminate`;\n    // Retry with the same policy as create: terminate is safe to replay (the gateway\n    // treats an already-torn-down session as success), so a transient failure at\n    // shutdown shouldn't silently leak an idle-timeout window of billing.\n    await this.postJsonRetrying(\n      url,\n      {\n        provider: this.providerName,\n        provider_session_id: providerSessionId,\n        terminate_token: terminateToken,\n      },\n      () => this.authHeaders(),\n      'avatar gateway terminate',\n    );\n  }\n\n  private async authHeaders(): Promise<Record<string, string>> {\n    return {\n      ...buildMetadataHeaders(),\n      Authorization: `Bearer ${await createAccessToken(this.apiKey, this.apiSecret)}`,\n      [INFERENCE_PROVIDER_HEADER]: this.providerName,\n      'Content-Type': 'application/json',\n    };\n  }\n\n  /**\n   * POST with the shared retry/backoff policy (up to `connOptions.maxRetry` retries).\n   * Rethrows immediately on a non-retryable error, and prefers a gateway `Retry-After`\n   * hint over the default backoff interval.\n   */\n  private async postJsonRetrying(\n    url: string,\n    payload: Record<string, unknown>,\n    getHeaders: () => Promise<Record<string, string>>,\n    label: string,\n  ): Promise<Response> {\n    let lastError: Error | undefined;\n    for (let i = 0; i <= this.connOptions.maxRetry; i++) {\n      let retryAfterMs: number | undefined;\n      try {\n        return await this.postJson(url, payload, await getHeaders());\n      } catch (error) {\n        const apiError = toAPIError(error, `${label} timed out after attempt ${i + 1}`);\n        lastError = apiError;\n        if (apiError instanceof APIError && !apiError.retryable) {\n          throw apiError;\n        }\n        retryAfterMs = (apiError as { retryAfterMs?: number }).retryAfterMs;\n        this.logger.warn(\n          { provider: this.providerName, error: String(apiError) },\n          apiError instanceof APITimeoutError\n            ? 'avatar gateway request timed out'\n            : 'failed to call avatar gateway',\n        );\n      }\n\n      if (i < this.connOptions.maxRetry) {\n        const backoffMs = retryAfterMs ?? intervalForRetry(this.connOptions, i);\n        await new Promise((resolve) => setTimeout(resolve, backoffMs));\n      }\n    }\n\n    throw lastError ?? new APIConnectionError({ message: `${label} failed after all retries` });\n  }\n\n  private async postJson(\n    url: string,\n    payload: Record<string, unknown>,\n    headers: Record<string, string>,\n  ): Promise<Response> {\n    const response = await this.fetchFn(url, {\n      method: 'POST',\n      headers,\n      body: JSON.stringify(payload),\n      signal: AbortSignal.timeout(this.connOptions.timeoutMs),\n    });\n    if (!response.ok) {\n      const text = await response.text();\n      const error = new APIStatusError({\n        message: `avatar gateway returned an error: ${text}`,\n        options: { statusCode: response.status, body: { error: text } },\n      });\n      // Surface the gateway's backoff hint (e.g. 60s on 429) so the retry loop can\n      // honor it instead of hammering with the default ~2s interval.\n      const retryAfterMs = parseRetryAfterMs(response.headers.get('Retry-After'));\n      if (retryAfterMs !== undefined) {\n        (error as APIStatusError & { retryAfterMs?: number }).retryAfterMs = retryAfterMs;\n      }\n      throw error;\n    }\n    return response;\n  }\n}\n\nfunction toAPIError(error: unknown, timeoutMessage: string): APIError {\n  if (error instanceof APIError) return error;\n  if (isTimeoutError(error)) return new APITimeoutError({ message: timeoutMessage });\n  return new APIConnectionError({ message: String(error) });\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n  return (\n    error instanceof DOMException && (error.name === 'TimeoutError' || error.name === 'AbortError')\n  );\n}\n\n/**\n * Parse an HTTP `Retry-After` header (RFC 7231: delta-seconds or an HTTP-date) into\n * milliseconds. Returns undefined when absent or unparseable.\n */\nfunction parseRetryAfterMs(headerValue: string | null): number | undefined {\n  if (!headerValue) return undefined;\n  const trimmed = headerValue.trim();\n  if (!trimmed) return undefined;\n\n  const seconds = Number(trimmed);\n  if (Number.isFinite(seconds)) {\n    return Math.max(0, Math.round(seconds * 1000));\n  }\n\n  const dateMs = Date.parse(trimmed);\n  if (!Number.isNaN(dateMs)) {\n    return Math.max(0, dateMs - Date.now());\n  }\n  return undefined;\n}\n\nasync function resolveRoomSid(\n  room: Room,\n  roomName: string,\n  livekitUrl: string,\n  options: AvatarSessionStartOptions,\n): Promise<string | undefined> {\n  // `@livekit/rtc-node` `Room` exposes `getSid()` (not a `sid` property); for a connected\n  // room it resolves to the server-assigned sid without any extra RPC. Fall back to a\n  // RoomService lookup only if it comes back empty (e.g. sid not yet assigned).\n  const sid = await room.getSid();\n  if (sid) return sid;\n\n  const livekitApiKey = options.livekitApiKey ?? process.env.LIVEKIT_API_KEY;\n  const livekitApiSecret = options.livekitApiSecret ?? process.env.LIVEKIT_API_SECRET;\n  if (!livekitApiKey || !livekitApiSecret) {\n    throw new Error(\n      'livekitApiKey and livekitApiSecret must be set by arguments or the LIVEKIT_API_KEY / LIVEKIT_API_SECRET environment variables to resolve room sid',\n    );\n  }\n\n  const client = new RoomServiceClient(livekitUrl, livekitApiKey, livekitApiSecret);\n  const rooms = await client.listRooms([roomName]);\n  return rooms[0]?.sid;\n}\n"],"mappings":"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","names":["BaseAvatarSession"]}