code:
import { Request, Response } from "express";
import * as http from "http";
import { buildFakeSseMessage } from "../common";
import { RawResponseBodyHandler, decodeResponseBody } from ".";

type OpenAiChatCompletionResponse = {
  id: string;
  object: string;
  created: number;
  model: string;
  choices: {
    message: { role: string; content: string };
    finish_reason: string | null;
    index: number;
  }[];
};

type AnthropicCompletionResponse = {
  completion: string;
  stop_reason: string;
  truncated: boolean;
  stop: any;
  model: string;
  log_id: string;
  exception: null;
};

/**
 * Consume the SSE stream and forward events to the client. Once the stream is
 * stream is closed, resolve with the full response body so that subsequent
 * middleware can work with it.
 *
 * Typically we would only need of the raw response handlers to execute, but
 * in the event a streamed request results in a non-200 response, we need to
 * fall back to the non-streaming response handler so that the error handler
 * can inspect the error response.
 *
 * Currently most frontends don't support Anthropic streaming, so users can opt
 * to send requests for Claude models via an endpoint that accepts OpenAI-
 * compatible requests and translates the received Anthropic SSE events into
 * OpenAI ones, essentially pretending to be an OpenAI streaming API.
 */
export const handleStreamedResponse: RawResponseBodyHandler = async (
  proxyRes,
  req,
  res
) => {
  // If these differ, the user is using the OpenAI-compatibile endpoint, so
  // we need to translate the SSE events into OpenAI completion events for their
  // frontend.
  if (!req.isStreaming) {
    const err = new Error(
      "handleStreamedResponse called for non-streaming request."
    );
    req.log.error({ stack: err.stack, api: req.inboundApi }, err.message);
    throw err;
  }

  const key = req.key!;
  if (proxyRes.statusCode !== 200) {
    // Ensure we use the non-streaming middleware stack since we won't be
    // getting any events.
    req.isStreaming = false;
    req.log.warn(
      { statusCode: proxyRes.statusCode, key: key.hash },
      `Streaming request returned error status code. Falling back to non-streaming response handler.`
    );
    return decodeResponseBody(proxyRes, req, res);
  }

  return new Promise((resolve, reject) => {
    req.log.info({ key: key.hash }, `Starting to proxy SSE stream.`);

    // Queued streaming requests will already have a connection open and headers
    // sent due to the heartbeat handler.  In that case we can just start
    // streaming the response without sending headers.
    if (!res.headersSent) {
      res.setHeader("Content-Type", "text/event-stream");
      res.setHeader("Cache-Control", "no-cache");
      res.setHeader("Connection", "keep-alive");
      res.setHeader("X-Accel-Buffering", "no");
      copyHeaders(proxyRes, res);
      res.flushHeaders();
    }

    const originalEvents: string[] = [];
    let partialMessage = "";
    let lastPosition = 0;

    type ProxyResHandler<T extends unknown> = (...args: T[]) => void;
    function withErrorHandling<T extends unknown>(fn: ProxyResHandler<T>) {
      return (...args: T[]) => {
        try {
          fn(...args);
        } catch (error) {
          proxyRes.emit("error", error);
        }
      };
    }

    proxyRes.on(
      "data",
      withErrorHandling((chunk: Buffer) => {
        // We may receive multiple (or partial) SSE messages in a single chunk,
        // so we need to buffer and emit seperate stream events for full
        // messages so we can parse/transform them properly.
        const str = chunk.toString();

        // Anthropic uses CRLF line endings (out-of-spec btw)
        const fullMessages = (partialMessage + str).split(/\r?\n\r?\n/);
        partialMessage = fullMessages.pop() || "";

        for (const message of fullMessages) {
          proxyRes.emit("full-sse-event", message);
        }
      })
    );

    proxyRes.on(
      "full-sse-event",
      withErrorHandling((data) => {
        originalEvents.push(data);
        const { event, position } = transformEvent({
          data,
          requestApi: req.inboundApi,
          responseApi: req.outboundApi,
          lastPosition,
        });
        lastPosition = position;
        res.write(event + "\n\n");
      })
    );

    proxyRes.on(
      "end",
      withErrorHandling(() => {
        let finalBody = convertEventsToFinalResponse(originalEvents, req);
        req.log.info({ key: key.hash }, `Finished proxying SSE stream.`);
        res.end();
        resolve(finalBody);
      })
    );

    proxyRes.on("error", (err) => {
      req.log.error({ error: err, key: key.hash }, `Mid-stream error.`);
      const fakeErrorEvent = buildFakeSseMessage(
        "mid-stream-error",
        err.message,
        req
      );
      res.write(`data: ${JSON.stringify(fakeErrorEvent)}\n\n`);
      res.write("data: [DONE]\n\n");
      res.end();
      reject(err);
    });
  });
};

/**
 * Transforms SSE events from the given response API into events compatible with
 * the API requested by the client.
 */
function transformEvent({
  data,
  requestApi,
  responseApi,
  lastPosition,
}: {
  data: string;
  requestApi: string;
  responseApi: string;
  lastPosition: number;
}) {
  if (requestApi === responseApi) {
    return { position: -1, event: data };
  }

  if (requestApi === "anthropic" && responseApi === "openai") {
    throw new Error(`Anthropic -> OpenAI streaming not implemented.`);
  }

  // Anthropic sends the full completion so far with each event whereas OpenAI
  // only sends the delta. To make the SSE events compatible, we remove
  // everything before `lastPosition` from the completion.
  if (!data.startsWith("data:")) {
    return { position: lastPosition, event: data };
  }

  if (data.startsWith("data: [DONE]")) {
    return { position: lastPosition, event: data };
  }

  const event = JSON.parse(data.slice("data: ".length));
  const newEvent = {
    id: "ant-" + event.log_id,
    object: "chat.completion.chunk",
    created: Date.now(),
    model: event.model,
    choices: [
      {
        index: 0,
        delta: { content: event.completion?.slice(lastPosition) },
        finish_reason: event.stop_reason,
      },
    ],
  };
  return {
    position: event.completion.length,
    event: `data: ${JSON.stringify(newEvent)}`,
  };
}

/** Copy headers, excluding ones we're already setting for the SSE response. */
function copyHeaders(proxyRes: http.IncomingMessage, res: Response) {
  const toOmit = [
    "content-length",
    "content-encoding",
    "transfer-encoding",
    "content-type",
    "connection",
    "cache-control",
  ];
  for (const [key, value] of Object.entries(proxyRes.headers)) {
    if (!toOmit.includes(key) && value) {
      res.setHeader(key, value);
    }
  }
}

/**
 * Converts the list of incremental SSE events into an object that resembles a
 * full, non-streamed response from the API so that subsequent middleware can
 * operate on it as if it were a normal response.
 * Events are expected to be in the format they were received from the API.
 */
function convertEventsToFinalResponse(events: string[], req: Request) {
  if (req.outboundApi === "openai") {
    let response: OpenAiChatCompletionResponse = {
      id: "",
      object: "",
      created: 0,
      model: "",
      choices: [],
    };
    response = events.reduce((acc, event, i) => {
      if (!event.startsWith("data: ")) {
        return acc;
      }

      if (event === "data: [DONE]") {
        return acc;
      }

      const data = JSON.parse(event.slice("data: ".length));
      if (i === 0) {
        return {
          id: data.id,
          object: data.object,
          created: data.created,
          model: data.model,
          choices: [
            {
              message: { role: data.choices[0].delta.role, content: "" },
              index: 0,
              finish_reason: null,
            },
          ],
        };
      }

      if (data.choices[0].delta.content) {
        acc.choices[0].message.content += data.choices[0].delta.content;
      }
      acc.choices[0].finish_reason = data.choices[0].finish_reason;
      return acc;
    }, response);
    return response;
  }
  if (req.outboundApi === "anthropic") {
    /*
     * Full complete responses from Anthropic are conveniently just the same as
     * the final SSE event before the "DONE" event, so we can reuse that
     */
    const lastEvent = events[events.length - 2].toString();
    const data = JSON.parse(lastEvent.slice("data: ".length));
    const response: AnthropicCompletionResponse = {
      ...data,
      log_id: req.id,
    };
    return response;
  }
  throw new Error("If you get this, something is fucked");
}


code:
/* This file is fucking horrendous, sorry */
import { Request, Response } from "express";
import * as http from "http";
import util from "util";
import zlib from "zlib";
import { config } from "../../../config";
import { logger } from "../../../logger";
import { keyPool } from "../../../key-management";
import { enqueue, trackWaitTime } from "../../queue";
import { incrementPromptCount } from "../../auth/user-store";
import { isCompletionRequest, writeErrorResponse } from "../common";
import { handleStreamedResponse } from "./handle-streamed-response";
import { logPrompt } from "./log-prompt";

const DECODER_MAP = {
  gzip: util.promisify(zlib.gunzip),
  deflate: util.promisify(zlib.inflate),
  br: util.promisify(zlib.brotliDecompress),
};

const isSupportedContentEncoding = (
  contentEncoding: string
): contentEncoding is keyof typeof DECODER_MAP => {
  return contentEncoding in DECODER_MAP;
};

class RetryableError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "RetryableError";
  }
}

/**
 * Either decodes or streams the entire response body and then passes it as the
 * last argument to the rest of the middleware stack.
 */
export type RawResponseBodyHandler = (
  proxyRes: http.IncomingMessage,
  req: Request,
  res: Response
) => Promise<string | Record<string, any>>;
export type ProxyResHandlerWithBody = (
  proxyRes: http.IncomingMessage,
  req: Request,
  res: Response,
  /**
   * This will be an object if the response content-type is application/json,
   * or if the response is a streaming response. Otherwise it will be a string.
   */
  body: string | Record<string, any>
) => Promise<void>;
export type ProxyResMiddleware = ProxyResHandlerWithBody[];

/**
 * Returns a on.proxyRes handler that executes the given middleware stack after
 * the common proxy response handlers have processed the response and decoded
 * the body.  Custom middleware won't execute if the response is determined to
 * be an error from the upstream service as the response will be taken over by
 * the common error handler.
 *
 * For streaming responses, the handleStream middleware will block remaining
 * middleware from executing as it consumes the stream and forwards events to
 * the client. Once the stream is closed, the finalized body will be attached
 * to res.body and the remaining middleware will execute.
 */
export const createOnProxyResHandler = (apiMiddleware: ProxyResMiddleware) => {
  return async (
    proxyRes: http.IncomingMessage,
    req: Request,
    res: Response
  ) => {
    const initialHandler = req.isStreaming
      ? handleStreamedResponse
      : decodeResponseBody;

    let lastMiddlewareName = initialHandler.name;

    try {
      const body = await initialHandler(proxyRes, req, res);

      const middlewareStack: ProxyResMiddleware = [];

      if (req.isStreaming) {
        // `handleStreamedResponse` writes to the response and ends it, so
        // we can only execute middleware that doesn't write to the response.
        middlewareStack.push(trackRateLimit, incrementKeyUsage, logPrompt);
      } else {
        middlewareStack.push(
          trackRateLimit,
          handleUpstreamErrors,
          incrementKeyUsage,
          copyHttpHeaders,
          logPrompt,
          ...apiMiddleware
        );
      }

      for (const middleware of middlewareStack) {
        lastMiddlewareName = middleware.name;
        await middleware(proxyRes, req, res, body);
      }

      trackWaitTime(req);
    } catch (error: any) {
      // Hack: if the error is a retryable rate-limit error, the request has
      // been re-enqueued and we can just return without doing anything else.
      if (error instanceof RetryableError) {
        return;
      }

      const errorData = {
        error: error.stack,
        thrownBy: lastMiddlewareName,
        key: req.key?.hash,
      };
      const message = `Error while executing proxy response middleware: ${lastMiddlewareName} (${error.message})`;
      if (res.headersSent) {
        req.log.error(errorData, message);
        // This should have already been handled by the error handler, but
        // just in case...
        if (!res.writableEnded) {
          res.end();
        }
        return;
      }
      logger.error(errorData, message);
      res
        .status(500)
        .json({ error: "Internal server error", proxy_note: message });
    }
  };
};

function reenqueueRequest(req: Request) {
  req.log.info(
    { key: req.key?.hash, retryCount: req.retryCount },
    `Re-enqueueing request due to retryable error`
  );
  req.retryCount++;
  enqueue(req);
}

/**
 * Handles the response from the upstream service and decodes the body if
 * necessary.  If the response is JSON, it will be parsed and returned as an
 * object.  Otherwise, it will be returned as a string.
 * @throws {Error} Unsupported content-encoding or invalid application/json body
 */
export const decodeResponseBody: RawResponseBodyHandler = async (
  proxyRes,
  req,
  res
) => {
  if (req.isStreaming) {
    const err = new Error("decodeResponseBody called for a streaming request.");
    req.log.error({ stack: err.stack, api: req.inboundApi }, err.message);
    throw err;
  }

  const promise = new Promise<string>((resolve, reject) => {
    let chunks: Buffer[] = [];
    proxyRes.on("data", (chunk) => chunks.push(chunk));
    proxyRes.on("end", async () => {
      let body = Buffer.concat(chunks);

      const contentEncoding = proxyRes.headers["content-encoding"];
      if (contentEncoding) {
        if (isSupportedContentEncoding(contentEncoding)) {
          const decoder = DECODER_MAP[contentEncoding];
          body = await decoder(body);
        } else {
          const errorMessage = `Proxy received response with unsupported content-encoding: ${contentEncoding}`;
          logger.warn({ contentEncoding, key: req.key?.hash }, errorMessage);
          writeErrorResponse(req, res, 500, {
            error: errorMessage,
            contentEncoding,
          });
          return reject(errorMessage);
        }
      }

      try {
        if (proxyRes.headers["content-type"]?.includes("application/json")) {
          const json = JSON.parse(body.toString());
          return resolve(json);
        }
        return resolve(body.toString());
      } catch (error: any) {
        const errorMessage = `Proxy received response with invalid JSON: ${error.message}`;
        logger.warn({ error, key: req.key?.hash }, errorMessage);
        writeErrorResponse(req, res, 500, { error: errorMessage });
        return reject(errorMessage);
      }
    });
  });
  return promise;
};

// TODO: This is too specific to OpenAI's error responses.
/**
 * Handles non-2xx responses from the upstream service.  If the proxied response
 * is an error, this will respond to the client with an error payload and throw
 * an error to stop the middleware stack.
 * On 429 errors, if request queueing is enabled, the request will be silently
 * re-enqueued.  Otherwise, the request will be rejected with an error payload.
 * @throws {Error} On HTTP error status code from upstream service
 */
const handleUpstreamErrors: ProxyResHandlerWithBody = async (
  proxyRes,
  req,
  res,
  body
) => {
  const statusCode = proxyRes.statusCode || 500;

  if (statusCode < 400) {
    return;
  }

  let errorPayload: Record<string, any>;
  // Subtract 1 from available keys because if this message is being shown,
  // it's because the key is about to be disabled.
  const availableKeys = keyPool.available(req.outboundApi) - 1;
  const tryAgainMessage = Boolean(availableKeys)
    ? `There are ${availableKeys} more keys available; try your request again.`
    : "There are no more keys available.";

  try {
    if (typeof body === "object") {
      errorPayload = body;
    } else {
      throw new Error("Received unparsable error response from upstream.");
    }
  } catch (parseError: any) {
    const statusMessage = proxyRes.statusMessage || "Unknown error";
    // Likely Bad Gateway or Gateway Timeout from reverse proxy/load balancer
    logger.warn(
      { statusCode, statusMessage, key: req.key?.hash },
      parseError.message
    );

    const errorObject = {
      statusCode,
      statusMessage: proxyRes.statusMessage,
      error: parseError.message,
      proxy_note: `This is likely a temporary error with the upstream service.`,
    };
    writeErrorResponse(req, res, statusCode, errorObject);
    throw new Error(parseError.message);
  }

  logger.warn(
    {
      statusCode,
      type: errorPayload.error?.code,
      errorPayload,
      key: req.key?.hash,
    },
    `Received error response from upstream. (${proxyRes.statusMessage})`
  );

  if (statusCode === 400) {
    // Bad request (likely prompt is too long)
    if (req.outboundApi === "openai") {
      errorPayload.proxy_note = `Upstream service rejected the request as invalid. Your prompt may be too long for ${req.body?.model}.`;
    } else if (req.outboundApi === "anthropic") {
      maybeHandleMissingPreambleError(req, errorPayload);
    }
  } else if (statusCode === 401) {
    // Key is invalid or was revoked
    keyPool.disable(req.key!);
    errorPayload.proxy_note = `API key is invalid or revoked. ${tryAgainMessage}`;
  } else if (statusCode === 429) {
    // OpenAI uses this for a bunch of different rate-limiting scenarios.
    if (req.outboundApi === "openai") {
      handleOpenAIRateLimitError(req, tryAgainMessage, errorPayload);
    } else if (req.outboundApi === "anthropic") {
      handleAnthropicRateLimitError(req, errorPayload);
    }
  } else if (statusCode === 404) {
    // Most likely model not found
    if (req.outboundApi === "openai") {
      // TODO: this probably doesn't handle GPT-4-32k variants properly if the
      // proxy has keys for both the 8k and 32k context models at the same time.
      if (errorPayload.error?.code === "model_not_found") {
        if (req.key!.isGpt4) {
          errorPayload.proxy_note = `Assigned key isn't provisioned for the GPT-4 snapshot you requested. Try again to get a different key, or use Turbo.`;
        } else {
          errorPayload.proxy_note = `No model was found for this key.`;
        }
      }
    } else if (req.outboundApi === "anthropic") {
      errorPayload.proxy_note = `The requested Claude model might not exist, or the key might not be provisioned for it.`;
    }
  } else {
    errorPayload.proxy_note = `Unrecognized error from upstream service.`;
  }

  // Some OAI errors contain the organization ID, which we don't want to reveal.
  if (errorPayload.error?.message) {
    errorPayload.error.message = errorPayload.error.message.replace(
      /org-.{24}/gm,
      "org-xxxxxxxxxxxxxxxxxxx"
    );
  }

  writeErrorResponse(req, res, statusCode, errorPayload);
  throw new Error(errorPayload.error?.message);
};

/**
 * This is a workaround for a very strange issue where certain API keys seem to
 * enforce more strict input validation than others -- specifically, they will
 * require a `\n\nHuman:` prefix on the prompt, perhaps to prevent the key from
 * being used as a generic text completion service and to enforce the use of
 * the chat RLHF.  This is not documented anywhere, and it's not clear why some
 * keys enforce this and others don't.
 * This middleware checks for that specific error and marks the key as being
 * one that requires the prefix, and then re-enqueues the request.
 * The exact error is:
 * ```
 * {
 *   "error": {
 *     "type": "invalid_request_error",
 *     "message": "prompt must start with \"\n\nHuman:\" turn"
 *   }
 * }
 * ```
 */
function maybeHandleMissingPreambleError(
  req: Request,
  errorPayload: Record<string, any>
) {
  if (
    errorPayload.error?.type === "invalid_request_error" &&
    errorPayload.error?.message === 'prompt must start with "\n\nHuman:" turn'
  ) {
    req.log.warn(
      { key: req.key?.hash },
      "Request failed due to missing preamble. Key will be marked as such for subsequent requests."
    );
    keyPool.update(req.key!, { requiresPreamble: true });
    if (config.queueMode !== "none") {
      reenqueueRequest(req);
      throw new RetryableError("Claude request re-enqueued to add preamble.");
    }
    errorPayload.proxy_note = `This Claude key requires special prompt formatting. Try again; the proxy will reformat your prompt next time.`;
  } else {
    errorPayload.proxy_note = `Proxy received unrecognized error from Anthropic. Check the specific error for more information.`;
  }
}

function handleAnthropicRateLimitError(
  req: Request,
  errorPayload: Record<string, any>
) {
  if (errorPayload.error?.type === "rate_limit_error") {
    keyPool.markRateLimited(req.key!);
    if (config.queueMode !== "none") {
      reenqueueRequest(req);
      throw new RetryableError("Claude rate-limited request re-enqueued.");
    }
    errorPayload.proxy_note = `There are too many in-flight requests for this key. Try again later.`;
  } else {
    errorPayload.proxy_note = `Unrecognized rate limit error from Anthropic. Key may be over quota.`;
  }
}

function handleOpenAIRateLimitError(
  req: Request,
  tryAgainMessage: string,
  errorPayload: Record<string, any>
): Record<string, any> {
  const type = errorPayload.error?.type;
  if (type === "insufficient_quota") {
    // Billing quota exceeded (key is dead, disable it)
    keyPool.disable(req.key!);
    errorPayload.proxy_note = `Assigned key's quota has been exceeded. ${tryAgainMessage}`;
  } else if (type === "billing_not_active") {
    // Billing is not active (key is dead, disable it)
    keyPool.disable(req.key!);
    errorPayload.proxy_note = `Assigned key was deactivated by OpenAI. ${tryAgainMessage}`;
  } else if (type === "requests" || type === "tokens") {
    // Per-minute request or token rate limit is exceeded, which we can retry
    keyPool.markRateLimited(req.key!);
    if (config.queueMode !== "none") {
      reenqueueRequest(req);
      // This is confusing, but it will bubble up to the top-level response
      // handler and cause the request to go back into the request queue.
      throw new RetryableError("Rate-limited request re-enqueued.");
    }
    errorPayload.proxy_note = `Assigned key's '${type}' rate limit has been exceeded. Try again later.`;
  } else {
    // OpenAI probably overloaded
    errorPayload.proxy_note = `This is likely a temporary error with OpenAI. Try again in a few seconds.`;
  }
  return errorPayload;
}

const incrementKeyUsage: ProxyResHandlerWithBody = async (_proxyRes, req) => {
  if (isCompletionRequest(req)) {
    keyPool.incrementPrompt(req.key!);
    if (req.user) {
      incrementPromptCount(req.user.token);
    }
  }
};

const trackRateLimit: ProxyResHandlerWithBody = async (proxyRes, req) => {
  keyPool.updateRateLimits(req.key!, proxyRes.headers);
};

const copyHttpHeaders: ProxyResHandlerWithBody = async (
  proxyRes,
  _req,
  res
) => {
  Object.keys(proxyRes.headers).forEach((key) => {
    // Omit content-encoding because we will always decode the response body
    if (key === "content-encoding") {
      return;
    }
    // We're usually using res.json() to send the response, which causes express
    // to set content-length. That's not valid for chunked responses and some
    // clients will reject it so we need to omit it.
    if (key === "transfer-encoding") {
      return;
    }
    res.setHeader(key, proxyRes.headers[key] as string);
  });
};

repo_name: JJNeverkry/jj

license: unknown


code:
import { Request } from "express";
import { config } from "../../../config";
import { AIService } from "../../../key-management";
import { logQueue } from "../../../prompt-logging";
import { isCompletionRequest } from "../common";
import { ProxyResHandlerWithBody } from ".";
import { logger } from "../../../logger";

/** If prompt logging is enabled, enqueues the prompt for logging. */
export const logPrompt: ProxyResHandlerWithBody = async (
  _proxyRes,
  req,
  _res,
  responseBody
) => {
  if (!config.promptLogging) {
    return;
  }
  if (typeof responseBody !== "object") {
    throw new Error("Expected body to be an object");
  }

  if (!isCompletionRequest(req)) {
    return;
  }

  const promptPayload = getPromptForRequest(req);
  const promptFlattened = flattenMessages(promptPayload);
  const response = getResponseForService({
    service: req.outboundApi,
    body: responseBody,
  });

  logQueue.enqueue({
    endpoint: req.inboundApi,
    promptRaw: JSON.stringify(promptPayload),
    promptFlattened,
    model: response.model, // may differ from the requested model
    response: response.completion,
  });
};

type OaiMessage = {
  role: "user" | "assistant" | "system";
  content: string;
};

const getPromptForRequest = (req: Request): string | OaiMessage[] => {
  // Since the prompt logger only runs after the request has been proxied, we
  // can assume the body has already been transformed to the target API's
  // format.
  if (req.outboundApi === "anthropic") {
    return req.body.prompt;
  } else {
    return req.body.messages;
  }
};

const flattenMessages = (messages: string | OaiMessage[]): string => {
  if (typeof messages === "string") {
    return messages.trim();
  }
  return messages.map((m) => `${m.role}: ${m.content}`).join("\n");
};

const getResponseForService = ({
  service,
  body,
}: {
  service: AIService;
  body: Record<string, any>;
}): { completion: string; model: string } => {
  if (service === "anthropic") {
    return { completion: body.completion.trim(), model: body.model };
  } else {
    return { completion: body.choices[0].message.content, model: body.model };
  }
};


code:
import { RequestHandler, Request, Router } from "express";
import * as http from "http";
import { createProxyMiddleware } from "http-proxy-middleware";
import { config } from "../config";
import { keyPool } from "../key-management";
import { logger } from "../logger";
import { createQueueMiddleware } from "./queue";
import { ipLimiter } from "./rate-limit";
import { handleProxyError } from "./middleware/common";
import {
  addKey,
  createPreprocessorMiddleware,
  finalizeBody,
  languageFilter,
  limitCompletions,
  limitOutputTokens,
} from "./middleware/request";
import {
  createOnProxyResHandler,
  ProxyResHandlerWithBody,
} from "./middleware/response";

let modelsCache: any = null;
let modelsCacheTime = 0;

function getModelsResponse() {
  if (new Date().getTime() - modelsCacheTime < 1000 * 60) {
    return modelsCache;
  }

  const gptVariants = [
    "gpt-4",
    "gpt-4-0314",
    "gpt-4-32k",
    "gpt-4-32k-0314",
    "gpt-3.5-turbo",
    "gpt-3.5-turbo-0301",
  ];

  const gpt4Available = keyPool.list().filter((key) => {
    return key.service === "openai" && !key.isDisabled && key.isGpt4;
  }).length;

  const models = gptVariants
    .map((id) => ({
      id,
      object: "model",
      created: new Date().getTime(),
      owned_by: "openai",
      permission: [
        {
          id: "modelperm-" + id,
          object: "model_permission",
          created: new Date().getTime(),
          organization: "*",
          group: null,
          is_blocking: false,
        },
      ],
      root: id,
      parent: null,
    }))
    .filter((model) => {
      if (model.id.startsWith("gpt-4")) {
        return gpt4Available > 0;
      }
      return true;
    });

  modelsCache = { object: "list", data: models };
  modelsCacheTime = new Date().getTime();

  return modelsCache;
}

const handleModelRequest: RequestHandler = (_req, res) => {
  res.status(200).json(getModelsResponse());
};

const rewriteRequest = (
  proxyReq: http.ClientRequest,
  req: Request,
  res: http.ServerResponse
) => {
  const rewriterPipeline = [
    addKey,
    languageFilter,
    limitOutputTokens,
    limitCompletions,
    finalizeBody,
  ];

  try {
    for (const rewriter of rewriterPipeline) {
      rewriter(proxyReq, req, res, {});
    }
  } catch (error) {
    req.log.error(error, "Error while executing proxy rewriter");
    proxyReq.destroy(error as Error);
  }
};

const openaiResponseHandler: ProxyResHandlerWithBody = async (
  _proxyRes,
  req,
  res,
  body
) => {
  if (typeof body !== "object") {
    throw new Error("Expected body to be an object");
  }

  if (config.promptLogging) {
    const host = req.get("host");
    body.proxy_note = `Prompts are logged on this proxy instance. See ${host} for more information.`;
  }

  res.status(200).json(body);
};

const openaiProxy = createQueueMiddleware(
  createProxyMiddleware({
    target: "https://api.openai.com",
    changeOrigin: true,
    on: {
      proxyReq: rewriteRequest,
      proxyRes: createOnProxyResHandler([openaiResponseHandler]),
      error: handleProxyError,
    },
    selfHandleResponse: true,
    logger,
  })
);

const openaiRouter = Router();
// Fix paths because clients don't consistently use the /v1 prefix.
openaiRouter.use((req, _res, next) => {
  if (!req.path.startsWith("/v1/")) {
    req.url = `/v1${req.url}`;
  }
  next();
});
openaiRouter.get("/v1/models", handleModelRequest);
openaiRouter.post(
  "/v1/chat/completions",
  ipLimiter,
  createPreprocessorMiddleware({ inApi: "openai", outApi: "openai" }),
  openaiProxy
);
// Redirect browser requests to the homepage.
openaiRouter.get("*", (req, res, next) => {
  const isBrowser = req.headers["user-agent"]?.includes("Mozilla");
  if (isBrowser) {
    res.redirect("/");
  } else {
    next();
  }
});
openaiRouter.use((req, res) => {
  req.log.warn(`Blocked openai proxy request: ${req.method} ${req.path}`);
  res.status(404).json({ error: "Not found" });
});

export const openai = openaiRouter;


code:
/**
 * Very scuffed request queue. OpenAI's GPT-4 keys have a very strict rate limit
 * of 40000 generated tokens per minute. We don't actually know how many tokens
 * a given key has generated, so our queue will simply retry requests that fail
 * with a non-billing related 429 over and over again until they succeed.
 *
 * Dequeueing can operate in one of two modes:
 * - 'fair': requests are dequeued in the order they were enqueued.
 * - 'random': requests are dequeued randomly, not really a queue at all.
 *
 * When a request to a proxied endpoint is received, we create a closure around
 * the call to http-proxy-middleware and attach it to the request. This allows
 * us to pause the request until we have a key available. Further, if the
 * proxied request encounters a retryable error, we can simply put the request
 * back in the queue and it will be retried later using the same closure.
 */

import type { Handler, Request } from "express";
import { config, DequeueMode } from "../config";
import { keyPool, SupportedModel } from "../key-management";
import { logger } from "../logger";
import { AGNAI_DOT_CHAT_IP } from "./rate-limit";
import { buildFakeSseMessage } from "./middleware/common";

export type QueuePartition = "claude" | "turbo" | "gpt-4";

const queue: Request[] = [];
const log = logger.child({ module: "request-queue" });

let dequeueMode: DequeueMode = "fair";

/** Maximum number of queue slots for Agnai.chat requests. */
const AGNAI_CONCURRENCY_LIMIT = 15;
/** Maximum number of queue slots for individual users. */
const USER_CONCURRENCY_LIMIT = 1;

const sameIpPredicate = (incoming: Request) => (queued: Request) =>
  queued.ip === incoming.ip;
const sameUserPredicate = (incoming: Request) => (queued: Request) => {
  const incomingUser = incoming.user ?? { token: incoming.ip };
  const queuedUser = queued.user ?? { token: queued.ip };
  return queuedUser.token === incomingUser.token;
};

export function enqueue(req: Request) {
  let enqueuedRequestCount = 0;
  let isGuest = req.user?.token === undefined;

  if (isGuest) {
    enqueuedRequestCount = queue.filter(sameIpPredicate(req)).length;
  } else {
    enqueuedRequestCount = queue.filter(sameUserPredicate(req)).length;
  }

  // All Agnai.chat requests come from the same IP, so we allow them to have
  // more spots in the queue. Can't make it unlimited because people will
  // intentionally abuse it.
  // Authenticated users always get a single spot in the queue.
  const maxConcurrentQueuedRequests =
    isGuest && req.ip === AGNAI_DOT_CHAT_IP
      ? AGNAI_CONCURRENCY_LIMIT
      : USER_CONCURRENCY_LIMIT;
  if (enqueuedRequestCount >= maxConcurrentQueuedRequests) {
    if (req.ip === AGNAI_DOT_CHAT_IP) {
      // Re-enqueued requests are not counted towards the limit since they
      // already made it through the queue once.
      if (req.retryCount === 0) {
        throw new Error("Too many agnai.chat requests are already queued");
      }
    } else {
      throw new Error("Your IP or token already has a request in the queue");
    }
  }

  queue.push(req);
  req.queueOutTime = 0;

  // shitty hack to remove hpm's event listeners on retried requests
  removeProxyMiddlewareEventListeners(req);

  // If the request opted into streaming, we need to register a heartbeat
  // handler to keep the connection alive while it waits in the queue. We
  // deregister the handler when the request is dequeued.
  if (req.body.stream === "true" || req.body.stream === true) {
    const res = req.res!;
    if (!res.headersSent) {
      initStreaming(req);
    }
    req.heartbeatInterval = setInterval(() => {
      if (process.env.NODE_ENV === "production") {
        req.res!.write(": queue heartbeat\n\n");
      } else {
        req.log.info(`Sending heartbeat to request in queue.`);
        const partition = getPartitionForRequest(req);
        const avgWait = Math.round(getEstimatedWaitTime(partition) / 1000);
        const currentDuration = Math.round((Date.now() - req.startTime) / 1000);
        const debugMsg = `queue length: ${queue.length}; elapsed time: ${currentDuration}s; avg wait: ${avgWait}s`;
        req.res!.write(buildFakeSseMessage("heartbeat", debugMsg, req));
      }
    }, 10000);
  }

  // Register a handler to remove the request from the queue if the connection
  // is aborted or closed before it is dequeued.
  const removeFromQueue = () => {
    req.log.info(`Removing aborted request from queue.`);
    const index = queue.indexOf(req);
    if (index !== -1) {
      queue.splice(index, 1);
    }
    if (req.heartbeatInterval) {
      clearInterval(req.heartbeatInterval);
    }
  };
  req.onAborted = removeFromQueue;
  req.res!.once("close", removeFromQueue);

  if (req.retryCount ?? 0 > 0) {
    req.log.info({ retries: req.retryCount }, `Enqueued request for retry.`);
  } else {
    req.log.info(`Enqueued new request.`);
  }
}

function getPartitionForRequest(req: Request): QueuePartition {
  // There is a single request queue, but it is partitioned by model and API
  // provider.
  // - claude: requests for the Anthropic API, regardless of model
  // - gpt-4: requests for the OpenAI API, specifically for GPT-4 models
  // - turbo: effectively, all other requests
  const provider = req.outboundApi;
  const model = (req.body.model as SupportedModel) ?? "gpt-3.5-turbo";
  if (provider === "anthropic") {
    return "claude";
  }
  if (provider === "openai" && model.startsWith("gpt-4")) {
    return "gpt-4";
  }
  return "turbo";
}

function getQueueForPartition(partition: QueuePartition): Request[] {
  return queue.filter((req) => getPartitionForRequest(req) === partition);
}

export function dequeue(partition: QueuePartition): Request | undefined {
  const modelQueue = getQueueForPartition(partition);

  if (modelQueue.length === 0) {
    return undefined;
  }

  let req: Request;

  if (dequeueMode === "fair") {
    // Dequeue the request that has been waiting the longest
    req = modelQueue.reduce((prev, curr) =>
      prev.startTime < curr.startTime ? prev : curr
    );
  } else {
    // Dequeue a random request
    const index = Math.floor(Math.random() * modelQueue.length);
    req = modelQueue[index];
  }
  queue.splice(queue.indexOf(req), 1);

  if (req.onAborted) {
    req.res!.off("close", req.onAborted);
    req.onAborted = undefined;
  }

  if (req.heartbeatInterval) {
    clearInterval(req.heartbeatInterval);
  }

  // Track the time leaving the queue now, but don't add it to the wait times
  // yet because we don't know if the request will succeed or fail. We track
  // the time now and not after the request succeeds because we don't want to
  // include the model processing time.
  req.queueOutTime = Date.now();
  return req;
}

/**
 * Naive way to keep the queue moving by continuously dequeuing requests. Not
 * ideal because it limits throughput but we probably won't have enough traffic
 * or keys for this to be a problem.  If it does we can dequeue multiple
 * per tick.
 **/
function processQueue() {
  // This isn't completely correct, because a key can service multiple models.
  // Currently if a key is locked out on one model it will also stop servicing
  // the others, because we only track one rate limit per key.
  const gpt4Lockout = keyPool.getLockoutPeriod("gpt-4");
  const turboLockout = keyPool.getLockoutPeriod("gpt-3.5-turbo");
  const claudeLockout = keyPool.getLockoutPeriod("claude-v1");

  const reqs: (Request | undefined)[] = [];
  if (gpt4Lockout === 0) {
    reqs.push(dequeue("gpt-4"));
  }
  if (turboLockout === 0) {
    reqs.push(dequeue("turbo"));
  }
  if (claudeLockout === 0) {
    reqs.push(dequeue("claude"));
  }

  reqs.filter(Boolean).forEach((req) => {
    if (req?.proceed) {
      req.log.info({ retries: req.retryCount }, `Dequeuing request.`);
      req.proceed();
    }
  });
  setTimeout(processQueue, 50);
}

/**
 * Kill stalled requests after 5 minutes, and remove tracked wait times after 2
 * minutes.
 **/
function cleanQueue() {
  const now = Date.now();
  const oldRequests = queue.filter(
    (req) => now - (req.startTime ?? now) > 5 * 60 * 1000
  );
  oldRequests.forEach((req) => {
    req.log.info(`Removing request from queue after 5 minutes.`);
    killQueuedRequest(req);
  });

  const index = waitTimes.findIndex(
    (waitTime) => now - waitTime.end > 300 * 1000
  );
  const removed = waitTimes.splice(0, index + 1);
  log.trace(
    { stalledRequests: oldRequests.length, prunedWaitTimes: removed.length },
    `Cleaning up request queue.`
  );
  setTimeout(cleanQueue, 20 * 1000);
}

export function start() {
  processQueue();
  cleanQueue();
  log.info(`Started request queue.`);
}

let waitTimes: { partition: QueuePartition; start: number; end: number }[] = [];

/** Adds a successful request to the list of wait times. */
export function trackWaitTime(req: Request) {
  waitTimes.push({
    partition: getPartitionForRequest(req),
    start: req.startTime!,
    end: req.queueOutTime ?? Date.now(),
  });
}

/** Returns average wait time in milliseconds. */
export function getEstimatedWaitTime(partition: QueuePartition) {
  const now = Date.now();
  const recentWaits = waitTimes.filter(
    (wt) => wt.partition === partition && now - wt.end < 300 * 1000
  );
  if (recentWaits.length === 0) {
    return 0;
  }

  return (
    recentWaits.reduce((sum, wt) => sum + wt.end - wt.start, 0) /
    recentWaits.length
  );
}

export function getQueueLength(partition: QueuePartition | "all" = "all") {
  if (partition === "all") {
    return queue.length;
  }
  const modelQueue = getQueueForPartition(partition);
  return modelQueue.length;
}

export function createQueueMiddleware(proxyMiddleware: Handler): Handler {
  return (req, res, next) => {
    if (config.queueMode === "none") {
      return proxyMiddleware(req, res, next);
    }

    req.proceed = () => {
      proxyMiddleware(req, res, next);
    };

    try {
      enqueue(req);
    } catch (err: any) {
      req.res!.status(429).json({
        type: "proxy_error",
        message: err.message,
        stack: err.stack,
        proxy_note: `Only one request can be queued at a time. If you don't have another request queued, your IP or user token might be in use by another request.`,
      });
    }
  };
}

function killQueuedRequest(req: Request) {
  if (!req.res || req.res.writableEnded) {
    req.log.warn(`Attempted to terminate request that has already ended.`);
    return;
  }
  const res = req.res;
  try {
    const message = `Your request has been terminated by the proxy because it has been in the queue for more than 5 minutes. The queue is currently ${queue.length} requests long.`;
    if (res.headersSent) {
      const fakeErrorEvent = buildFakeSseMessage(
        "proxy queue error",
        message,
        req
      );
      res.write(fakeErrorEvent);
      res.end();
    } else {
      res.status(500).json({ error: message });
    }
  } catch (e) {
    req.log.error(e, `Error killing stalled request.`);
  }
}

function initStreaming(req: Request) {
  req.log.info(`Initiating streaming for new queued request.`);
  const res = req.res!;
  res.statusCode = 200;
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");
  res.setHeader("X-Accel-Buffering", "no"); // nginx-specific fix
  res.flushHeaders();
  res.write("\n");
  res.write(": joining queue\n\n");
}

/**
 * http-proxy-middleware attaches a bunch of event listeners to the req and
 * res objects which causes problems with our approach to re-enqueuing failed
 * proxied requests. This function removes those event listeners.
 * We don't have references to the original event listeners, so we have to
 * look through the list and remove HPM's listeners by looking for particular
 * strings in the listener functions. This is an astoundingly shitty way to do
 * this, but it's the best I can come up with.
 */
function removeProxyMiddlewareEventListeners(req: Request) {
  // node_modules/http-proxy-middleware/dist/plugins/default/debug-proxy-errors-plugin.js:29
  // res.listeners('close')
  const RES_ONCLOSE = `Destroying proxyRes in proxyRes close event`;
  // node_modules/http-proxy-middleware/dist/plugins/default/debug-proxy-errors-plugin.js:19
  // res.listeners('error')
  const RES_ONERROR = `Socket error in proxyReq event`;
  // node_modules/http-proxy/lib/http-proxy/passes/web-incoming.js:146
  // req.listeners('aborted')
  const REQ_ONABORTED = `proxyReq.abort()`;
  // node_modules/http-proxy/lib/http-proxy/passes/web-incoming.js:156
  // req.listeners('error')
  const REQ_ONERROR = `if (req.socket.destroyed`;

  const res = req.res!;

  const resOnClose = res
    .listeners("close")
    .find((listener) => listener.toString().includes(RES_ONCLOSE));
  if (resOnClose) {
    res.removeListener("close", resOnClose as any);
  }

  const resOnError = res
    .listeners("error")
    .find((listener) => listener.toString().includes(RES_ONERROR));
  if (resOnError) {
    res.removeListener("error", resOnError as any);
  }

  const reqOnAborted = req
    .listeners("aborted")
    .find((listener) => listener.toString().includes(REQ_ONABORTED));
  if (reqOnAborted) {
    req.removeListener("aborted", reqOnAborted as any);
  }

  const reqOnError = req
    .listeners("error")
    .find((listener) => listener.toString().includes(REQ_ONERROR));
  if (reqOnError) {
    req.removeListener("error", reqOnError as any);
  }
}


code:
import { Request, Response, NextFunction } from "express";
import { config } from "../config";

export const AGNAI_DOT_CHAT_IP = "157.230.249.32";
const RATE_LIMIT_ENABLED = Boolean(config.modelRateLimit);
const RATE_LIMIT = Math.max(1, config.modelRateLimit);
const ONE_MINUTE_MS = 60 * 1000;

const lastAttempts = new Map<string, number[]>();

const expireOldAttempts = (now: number) => (attempt: number) =>
  attempt > now - ONE_MINUTE_MS;

const getTryAgainInMs = (ip: string) => {
  const now = Date.now();
  const attempts = lastAttempts.get(ip) || [];
  const validAttempts = attempts.filter(expireOldAttempts(now));

  if (validAttempts.length >= RATE_LIMIT) {
    return validAttempts[0] - now + ONE_MINUTE_MS;
  } else {
    lastAttempts.set(ip, [...validAttempts, now]);
    return 0;
  }
};

const getStatus = (ip: string) => {
  const now = Date.now();
  const attempts = lastAttempts.get(ip) || [];
  const validAttempts = attempts.filter(expireOldAttempts(now));
  return {
    remaining: Math.max(0, RATE_LIMIT - validAttempts.length),
    reset: validAttempts.length > 0 ? validAttempts[0] + ONE_MINUTE_MS : now,
  };
};

/** Prunes attempts and IPs that are no longer relevant after one minutes. */
const clearOldAttempts = () => {
  const now = Date.now();
  for (const [ip, attempts] of lastAttempts.entries()) {
    const validAttempts = attempts.filter(expireOldAttempts(now));
    if (validAttempts.length === 0) {
      lastAttempts.delete(ip);
    } else {
      lastAttempts.set(ip, validAttempts);
    }
  }
};
setInterval(clearOldAttempts, 10 * 1000);

export const getUniqueIps = () => {
  return lastAttempts.size;
};

export const ipLimiter = (req: Request, res: Response, next: NextFunction) => {
  if (!RATE_LIMIT_ENABLED) {
    next();
    return;
  }

  // Exempt Agnai.chat from rate limiting since it's shared between a lot of
  // users. Dunno how to prevent this from being abused without some sort of
  // identifier sent from Agnaistic to identify specific users.
  if (req.ip === AGNAI_DOT_CHAT_IP) {
    next();
    return;
  }

  // If user is authenticated, key rate limiting by their token. Otherwise, key
  // rate limiting by their IP address. Mitigates key sharing.
  const rateLimitKey = req.user?.token || req.ip;

  const { remaining, reset } = getStatus(rateLimitKey);
  res.set("X-RateLimit-Limit", config.modelRateLimit.toString());
  res.set("X-RateLimit-Remaining", remaining.toString());
  res.set("X-RateLimit-Reset", reset.toString());

  const tryAgainInMs = getTryAgainInMs(rateLimitKey);
  if (tryAgainInMs > 0) {
    res.set("Retry-After", tryAgainInMs.toString());
    res.status(429).json({
      error: {
        type: "proxy_rate_limited",
        message: `This proxy is rate limited to ${
          config.modelRateLimit
        } model requests per minute. Please try again in ${Math.ceil(
          tryAgainInMs / 1000
        )} seconds.`,
      },
    });
  } else {
    next();
  }
};


code:
/* Accepts incoming requests at either the /kobold or /openai routes and then
routes them to the appropriate handler to be forwarded to the OpenAI API.
Incoming OpenAI requests are more or less 1:1 with the OpenAI API, but only a
subset of the API is supported. Kobold requests must be transformed into
equivalent OpenAI requests. */

import * as express from "express";
import { gatekeeper } from "./auth/gatekeeper";
import { kobold } from "./kobold";
import { openai } from "./openai";
import { anthropic } from "./anthropic";

const router = express.Router();

router.use(gatekeeper);
router.use("/kobold", kobold);
router.use("/openai", openai);
router.use("/anthropic", anthropic);
export { router as proxyRouter };


code:
import { assertConfigIsValid, config } from "./config";
import "source-map-support/register";
import express from "express";
import cors from "cors";
import pinoHttp from "pino-http";
import childProcess from "child_process";
import { logger } from "./logger";
import { keyPool } from "./key-management";
import { adminRouter } from "./admin/routes";
import { proxyRouter } from "./proxy/routes";
import { handleInfoPage } from "./info-page";
import { logQueue } from "./prompt-logging";
import { start as startRequestQueue } from "./proxy/queue";
import { init as initUserStore } from "./proxy/auth/user-store";
import { checkOrigin } from "./proxy/check-origin";

const PORT = config.port;

const app = express();
// middleware
app.use(
  pinoHttp({
    quietReqLogger: true,
    logger,
    autoLogging: {
      ignore: (req) => {
        const ignored = ["/proxy/kobold/api/v1/model", "/health"];
        return ignored.includes(req.url as string);
      },
    },
    redact: {
      paths: [
        "req.headers.cookie",
        'res.headers["set-cookie"]',
        "req.headers.authorization",
        'req.headers["x-api-key"]',
        'req.headers["x-forwarded-for"]',
        'req.headers["x-real-ip"]',
        'req.headers["true-client-ip"]',
        'req.headers["cf-connecting-ip"]',
        // Don't log the prompt text on transform errors
        "body.messages",
        "body.prompt",
      ],
      censor: "********",
    },
  })
);

app.get("/health", (_req, res) => res.sendStatus(200));
app.use((req, _res, next) => {
  req.startTime = Date.now();
  req.retryCount = 0;
  next();
});
app.use(cors());
app.use(
  express.json({ limit: "10mb" }),
  express.urlencoded({ extended: true, limit: "10mb" })
);

// TODO: Detect (or support manual configuration of) whether the app is behind
// a load balancer/reverse proxy, which is necessary to determine request IP
// addresses correctly.
app.set("trust proxy", true);

// routes
app.use(checkOrigin);
app.get("/", handleInfoPage);
app.use("/admin", adminRouter);
app.use("/proxy", proxyRouter);

// 500 and 404
app.use((err: any, _req: unknown, res: express.Response, _next: unknown) => {
  if (err.status) {
    res.status(err.status).json({ error: err.message });
  } else {
    logger.error(err);
    res.status(500).json({
      error: {
        type: "proxy_error",
        message: err.message,
        stack: err.stack,
        proxy_note: `Reverse proxy encountered an internal server error.`,
      },
    });
  }
});
app.use((_req: unknown, res: express.Response) => {
  res.status(404).json({ error: "Not found" });
});

async function start() {
  logger.info("Server starting up...");
  await setBuildInfo();

  logger.info("Checking configs and external dependencies...");
  await assertConfigIsValid();

  keyPool.init();

  if (config.gatekeeper === "user_token") {
    await initUserStore();
  }

  if (config.promptLogging) {
    logger.info("Starting prompt logging...");
    logQueue.start();
  }

  if (config.queueMode !== "none") {
    logger.info("Starting request queue...");
    startRequestQueue();
  }

  app.listen(PORT, async () => {
    logger.info({ port: PORT }, "Now listening for connections.");
    registerUncaughtExceptionHandler();
  });

  logger.info(
    { build: process.env.BUILD_INFO, nodeEnv: process.env.NODE_ENV },
    "Startup complete."
  );
}

function registerUncaughtExceptionHandler() {
  process.on("uncaughtException", (err: any) => {
    logger.error(
      { err, stack: err?.stack },
      "UNCAUGHT EXCEPTION. Please report this error trace."
    );
  });
  process.on("unhandledRejection", (err: any) => {
    logger.error(
      { err, stack: err?.stack },
      "UNCAUGHT PROMISE REJECTION. Please report this error trace."
    );
  });
}

/**
 * Attepts to collect information about the current build from either the
 * environment or the git repo used to build the image (only works if not
 * .dockerignore'd). If you're running a sekrit club fork, you can no-op this
 * function and set the BUILD_INFO env var manually, though I would prefer you
 * didn't set it to something misleading.
 */
async function setBuildInfo() {
  // Render .dockerignore's the .git directory but provides info in the env
  if (process.env.RENDER) {
    const sha = process.env.RENDER_GIT_COMMIT?.slice(0, 7) || "unknown SHA";
    const branch = process.env.RENDER_GIT_BRANCH || "unknown branch";
    const repo = process.env.RENDER_GIT_REPO_SLUG || "unknown repo";
    const buildInfo = `${sha} (${branch}@${repo})`;
    process.env.BUILD_INFO = buildInfo;
    logger.info({ build: buildInfo }, "Got build info from Render config.");
    return;
  }

  try {
    // Ignore git's complaints about dubious directory ownership on Huggingface
    // (which evidently runs dockerized Spaces on Windows with weird NTFS perms)
    if (process.env.SPACE_ID) {
      childProcess.execSync("git config --global --add safe.directory /app");
    }

    const promisifyExec = (cmd: string) =>
      new Promise((resolve, reject) => {
        childProcess.exec(cmd, (err, stdout) =>
          err ? reject(err) : resolve(stdout)
        );
      });

    const promises = [
      promisifyExec("git rev-parse --short HEAD"),
      promisifyExec("git rev-parse --abbrev-ref HEAD"),
      promisifyExec("git config --get remote.origin.url"),
      promisifyExec("git status --porcelain"),
    ].map((p) => p.then((result: any) => result.toString().trim()));

    let [sha, branch, remote, status] = await Promise.all(promises);

    remote = remote.match(/.*[\/:]([\w-]+)\/([\w\-\.]+?)(?:\.git)?$/) || [];
    const repo = remote.slice(-2).join("/");
    status = status
      // ignore Dockerfile changes since that's how the user deploys the app
      .split("\n")
      .filter((line: string) => !line.endsWith("Dockerfile") && line);

    const changes = status.length > 0;

    const build = `${sha}${changes ? " (modified)" : ""} (${branch}@${repo})`;
    process.env.BUILD_INFO = build;
    logger.info({ build, status, changes }, "Got build info from Git.");
  } catch (error: any) {
    logger.error(
      {
        error,
        stdout: error.stdout.toString(),
        stderr: error.stderr.toString(),
      },
      "Failed to get commit SHA.",
      error
    );
    process.env.BUILD_INFO = "unknown";
  }
}

start();


code:
import { Express } from "express-serve-static-core";
import { AIService, Key } from "../key-management/index";
import { User } from "../proxy/auth/user-store";

declare global {
  namespace Express {
    interface Request {
      key?: Key;
      /** Denotes the format of the user's submitted request. */
      inboundApi: AIService | "kobold";
      /** Denotes the format of the request being proxied to the API. */
      outboundApi: AIService;
      user?: User;
      isStreaming?: boolean;
      startTime: number;
      retryCount: number;
      queueOutTime?: number;
      onAborted?: () => void;
      proceed: () => void;
      heartbeatInterval?: NodeJS.Timeout;
    }
  }
}


code:
This file is a listing of all credits to users. It does nothing regarding the function of the mod, but its good to keep track
and thank contributors!

    Much obliged and thanks for the contributions of:
    Eiko;
    RT (Cannonball Fun);
    CynderQuill;
    TheSevenSins;
    Saint Apollyon;
    Rylasasin;
    Tabunster;
    Artus;
    CodeRedAlert;
    Dara the Silver Dragon;
    Paco;
    Sir Bumpleton;


code:
//# Expanded Dialogues for Sexbound
//This project aims to expand upon Sexbound's dialogues in a community-based manner.

//As such, this project is open for contributions of users. Keep in mind the dialogue
// structure when making contributions.


code:
{
  "plugins" : {
    "pregnant" : {
      "birthMessage1" : "Congratulations! You have just given birth to a baby <babygender> named <babyname>!",

      "birthMessage2" : "<name> has just given birth to a baby <babygender> named <babyname>!",

      "impregnatedMessage1" : "You just impregnated <name>, and they will give birth in <daycount> day!",

      "impregnatedMessage2" : "You just impregnated <name>, and they will give birth in <daycount> days!",

      "impregnatedMessage3" : "Oopsy! You were just impregnated by <name>, and you will give birth in <daycount> day!",

      "impregnatedMessage4" : "Oopsy! You were just impregnated by <name>, and you will give birth in <daycount> days!",

      "abortionMessage1" : "All vital scans indicate that you are no longer pregnant!"
    }
  }
}
//File disabled for now, THE OOPSY IS GONE!


default: I'm built to last for days! I hope you're ready for that.

default: Big Ape probably doesn't want to watch this~!

default: You're brutal~! Look at my hole fidget~!

default: Oooh~! I think I much prefer this over bananas~!

default: Don't tantalize me with bananas! Shove yours inside me! Faster~!

default: Do you like my fur? Would you like it more if your cum coated it~?

default: I can't help but swivel my tail, your cock is too good~!

climaxing: Mmh~! Fill my needy primate hole with your worthy seed~!

climaxing: I s-scientifically endorse this seminal deposit!

pregnant: Do you want him to get you pregnant next?


climaxing: A-ah, cover my rod in your cream, my sweet.


default: Must be easy lifting me, with how fast you're fucking!

default: I knew you apes were big, but not this strong!

default: Kluex yes! Lift me, lift me to the stars!


default: You’re going to forget your name after I’m done fucking you tonight!

default: Ask for permission before you cum… I want to hear you beg for it.

default: You’re going to need crutches when I’m done fucking you.

climaxing:
C-clench it! Unnh, yeah! Cum!
Ooooh~ You're milking meee~
O-oh.. Cum all over the floor for me!

climaxing:
O-oh.. you're tight! Squirt for me!
O-oh.. Cum all over the floor for me!

default:
wow, your ass is freezing cold!
Yeah, take it, you fluffy bird raptor!

default:
wow, your pussy is freezing cold!
Yeah, take it, you fluffy bird raptor!

default:
Yeah, take it, you big chicken!
Your feathers are tickling me!

default: Is it just me, or is your ass getting warmer?

default: Is it just me, or is your pussy a little warmer than it should be?

default: You really do breed like a rabbit, don't you?!

default: If you're real, then that means I'm definately going to hell for this. Oh well, worth it.

default:
I'm laying the dragon!
Yeah, take it, you big dragon!

default: I have to admit, I'm a bit weirded out by this...

default: I'd make a pussy joke, but that would be just ass.

default:
I'm gonna fuck this pussy till it purs!
Yeah, take it, kitty!

default:
Your petals are this soaked for me?
Yeah, take it, plant!

default: So, is this reason enough for you as to why you shouldn't destroy all life? I hope so!

default: Maybe I should stop before you reconnect with your hivemind. On Second thought... nah.

default: I can't believe I'm fucking the thing that destroyed Earth!

default: Yeah, take it, you big plant... thing?! You are a plant, right?

default: This pussy was designed specifically for this!

default: You're so soft and lovely. I've wanted you all day~~~~!

default:
Your so soft and wet!
Yeah, take it, fish!

default: Your fur is tickling me! It's making me even harder!

default: If demons are real, does this mean I'm going to hell for this? ... Worth it.

default: If demons are real, does this mean I'm going to hell for htis? ... Worth it.

default:
Now we're breeding like mice!
Yeah, take it, you little mouse!

default: it feels like it's on fire!! ... Keep going.

default:
How am I even fitting it into you?
Pirate this!

default:
How am I even fitting it into you?
Pirate this!
I be plundering this penguin pussy!

default: Your fur is tickling me. It's making me even harder!

default: I have to admit, feels a bit weird fucking a bug.

default: So deep... ahn... your gonna enter my womb!

default: I've been so naughty! Punish me harder~!

default: Ooh, I'm so horny all of the sudden~! Keep going~!

climaxing: O-oh~! You're cumming~! Unng, paint my insides with your thick spunk~!

climaxing: Oh, your cum is so hot and thick inside me~!

climaxing: Thinking about your babies inside me makes my pussy so wet...

pregnant: Your belly is so big! It's got me all wet thinking about it!

pregnant: I can feel your pregnant belly against me! I didn't know that would make me so wet!~~

climaxing: You're soaking my asshole... you'd better lick that up!

default: Ohmygod! Human cocks aren't this thiiiiiick!

default: YESS! Stuff me full with your hairy cock!

climaxing: OH you're filling me up! Make me carry your little apes!

default: Your lizard cock feels so weird... yet so goood! Don't stop!

default: Your floofy feathers are tickling me! It's turning me on so much!

climaxing:
Hng yess! I want to have your chicks!
It's freezing!

default: Your fluffy feathers are tickling me! It's turning me on so much!

default: Your fur is so warm and so ticklish! It's turning me on so hard!

climaxing: Yesss! Give me your knot! Tie me up and fill me!

default: What they say about bunnies is true! Don't stop!

default: It keeps on going and going and going and going....

climaxing: Ungh, fill me u- wow, ready to go again? Such stamina!

default:
So gentle, yet so dominating!
Yesss! Give me your long deer cock!

default: If this is what hell feels like, keep on making me sin!

default: I admit this is a bit weird... but don't stop!

climaxing: Yess! give me your... whatever you call your children!

default: Your fur is tickling me! It's turning me on so much!

default: I can feel your knot slapping against my pussy!

climaxing:
Yess! Fill me full of your pups!
Yes, knot me! Tie me up and fill me!

default: Your cock feels amazing! Don't stop you big cute... glowing... furry thing!

default: Your cock feels weird inside of me. It's driving me nuts!

climaxing: So about this glowing cum... should I be worried? No? Okay then. Give me more!

default: Your cock is so scratchy... yet it feels so good. Don't stop!

default: Are those... spines? It hurts, yet it's so good!

climaxing: Oh god, your claws are digging into me! It feels so good!

default: I've never been this wet before, your nectar is driving me wild!

default: Your scent makes me... it makes me want your cock so badly...

climaxing: Your cum tingles! Is it going to make me pregnant?

pregnant: This nectar is... so strong! I need more!

default: This is all sorts of wrong... Don't stop!

default: I can't believe I'm being fucked by the thing that destroyed the Earth! It's so goood!

default: Your tentacle is wiggling around inside of me!

default: I hope my body is enough to convince you not to destroy the universe.

climaxing: Yess! Fill me with- wait, I'm not going to burst open from the inside, am I?

default: Oooohh~ I didn't know you could do that~~!

default: I... I didn't know it could extend like thiiiiiisss!

climaxing: You're squirting your oil down my thighs!

climaxing: I hope that's more than just oil, I want you to knock me up~!

default: You're so slippery! I want to slide on top of you next~!

default: Your rod is so slippery and thick! Can we try it in my asshole?

default: Your scales are hitting all the right places~~~~!

default: Oh god, your scales are rubbing just right! Don't stop!

pregnant: I can feel your eggs moving around, you're so full!

default: Your fur is tickling me! It turns me on so much!

default: Upgh! Yes! Pound me like the big dog you are!

default: I can feel your knot pounding against my pussy! Don't you dare stop!

climaxing:
Yess! Give me a litter of pups!!
Yesss! Knot me! Tie me up and fill me!

default: The ears and tail just make you all the hotter!

default: I'm so glad you have a canine dick instead of a human one. Can't wait for you to knot me!

default: So this is what being bred by a mouse feels like! Don't stop!

default: You have spines, yet they're soft. So good!

default: What will... what will happen if you come inside me?

climaxing: Steam is coming out! ohhh it feels so good!!!~

climaxing: I hope you knocked me up! It feels so hot inside me!

default: wow, for such a little guy you sure have a big worm!

default: Wh-Where did that contraption come from?

default: Never thought a bug could fuck this good! Keep going!

climaxing: Yess! Fill me full of your butterfly eggs!

default: Yessss~! Your cock is so good~~ I'm cominggg~!

default: I'm gushing all over your pussy~! Yesss~!

default: Ohmygod Yes~! Keep doing that! I'm Cominggg~!

default:
Oh, I'm soaking your feathers!
Your prey is cumming~!

climaxing:
It's like fucking in a sauna!
Yes let's cum together, it's sizzling!

default: I'm ready to burst but I need this so bad~!

default: I get so wet when I'm pregnant... I need you to lap it up!

default: I'm already pregnant, but I can't get enough cum~!

climaxing: My pregnant womb can't get enough of your sperm~!...

climaxing: Mmh~! Fill me up a second time~! I can't get pregnant again~!

climaxing: Yessss! Please I want to be even more full!

pregnant: It's so sexy that we're pregnant together!

default: You know... unnnf~... he could get you pregnant, too, if you wanted...

default: I need a big ape to fill up my swollen pussy!

default: We should get pregnant together next time!

default: You can be rough with me, I'm not carrying eggs!

default: Your feathers! I'm more ticklish now that I'm pregnant!!

default: I get so horny when I'm pregnant! Does that happen to Avalis?

default: I get so horny when I'm pregnant! Does that happen to Avians?

pregnant: I wish I could have... a big litter like yoursss~!

default: ... even though I'm pregnant, I can't stop thinking about your nectar!

climaxing: Your petals feel so good against my pregnant pussy!

pregnant: Since we got pregnant, your scent has become... intoxicating...

default: Your scaly cock is just what I need since I got pregnant!

climaxing: It's not enough that I'm knocked up, I need your milt!

default: It's almost too hot to touch, but my pregnant pussy needs to be filled~!

climaxing: Yes! Shoot me full of your hot gases! I'm already knocked up!


default: I'll fuck you until i feel your ass clenching around my cock!

default: You’re going to forget your name after I’m done fucking you tonight!

default: Ask for permission before you cum… I want to hear you beg for it.

default: You’re going to need crutches when I’m done fucking you.

climaxing:
C-clench my cock! Unnh, yeah! Cum!
Ooooh~ You're milking meee~

default: I fuck you until i feel your pussy clenching around my cock!

default: Yeah, take my cock, you fluffy bird raptor!

default: Is it just me, or is my cock getting warm in your ass?

default: Is it just me, or is your pussy a little warmer than it should be?

default: You really do breed like a rabbit, don't you?!

default: If you're real, then that means I'm definately going to hell for this. Oh well, worth it.

default:
I'm laying the dragon!
Yeah, take my cock, you big dragon!

default: I have to admit, I'm a bit weirded out by this...

default: Yeah, take my cock, you cute little foxy!

default: Yeah, take my cock, you little glow-coon!

default: I'd make a pussy joke, but that would be just ass.

default:
I'm gonna fuck this pussy till it purs!
Yeah, take my cock, kitty!

default:
Your petals are this soaked for me?
Yeah, take my cock, plant!

default: So, is this reason enough for you as to why you shouldn't destroy all life? I hope so!

default: Maybe I should stop before you reconnect with your hivemind. On Second thought... nah.

default: I can't believe I'm fucking the thing that destroyed Earth!

default: Yeah, take my cock, you big plant... thing?! You are a plant, right?

default: This pussy was designed specifically for my cock!

default: You're so soft and lovely. I've wanted you all day~~~~!

default:
Your so soft and wet!
Yeah, take my cock, fish!

default: Your fur is tickling me! It's making me even harder!

default: If demons are real, does this mean I'm going to hell for this? ... Worth it.

default: If demons are real, does this mean I'm going to hell for htis? ... Worth it.

default:
Now we're breeding like mice!
Yeah, take my cock, you little mouse!

default: My cock feels like it's on fire!! ... Keep going.

default:
How am I even fitting my cock into you?
Pirate this dick!
I be plundering this penguin pussy!

default: Your fur is tickling me. It's making me even harder!

default: I have to admit, feels a bit weird fucking a bug.

default: Yeah, take my cock, you ... dark... thing!

climaxing:
A-ahh, I'm cumming too! Take it!
Mnnh, cum for me! I'm c-cumming too!

default: I've been so naughty! Punish me harder~!

default: Ooh, I'm so horny all of the sudden~! Keep going~!

climaxing: O-oh~! You're cumming~! Unng, paint my insides with your thick spunk~!

climaxing: Oh, your cum is so hot and thick inside me~!

climaxing: Thinking about your babies inside me makes my pussy so wet...

pregnant: Your belly is so big! It's got me all hard thinking about it!

climaxing: You're soaking my asshole... you'd better lick that up!

default: Ohmygod! Human cocks aren't this thiiiiiick!

default: YESS! Stuff me full with your hairy cock!

climaxing: OH you're filling me up! Make me carry your little apes!

default: Your lizard cock feels so weird... yet so goood! Don't stop!

default: Your floofy feathers are tickling me! It's turning me on so much!

climaxing:
Hng yess! I want to have your chicks!
It's freezing!

default: Your fluffy feathers are tickling me! It's turning me on so much!

default: Your fur is so warm and so ticklish! It's turning me on so hard!

climaxing: Yesss! Give me your knot! Tie me up and fill me!

default: What they say about bunnies is true! Don't stop!

default: It keeps on going and going and going and going....

climaxing: Ungh, fill me u- wow, ready to go again? Such stamina!

default:
So gentle, yet so dominating!
Yesss! Give me your long deer cock!

default: If this is what hell feels like, keep on making me sin!

default: I admit this is a bit weird... but don't stop!

default: Your fur is tickling me! It's turning me on so much!

default: I can feel your knot slapping against my ass!

default: Your cock feels amazing! Don't stop you big cute... glowing... furry thing!

default: Your cock feels weird inside of me. It's driving me nuts!

climaxing: So about this glowing cum... should I be worried? No? Okay then. Give me more!

default: Your cock is so scratchy... yet it feels so good. Don't stop!

default: Are those... spines? It hurts, yet it's so good!

default: I've never been this hard before, your nectar is driving me wild!

default: Your scent makes me... it makes me want your cock so badly...

climaxing:
Your nectar is running everywhere~~
Your cum is making me dizzy~!
Your cum tingles!

default: This is all sorts of wrong... Don't stop!

default: I can't believe I'm being fucked by the thing that destroyed the Earth! It's so goood!

default: Your tentacle is wiggling around inside of me!

default: I hope my body is enough to convince you not to destroy the universe.

default: I've never been this wet before, your nectar is driving me wild!

default: Oooohh~ I didn't know you could do that~~!

default: I... I didn't know it could extend like thiiiiiisss!

default: You're so slippery! I want to slide on top of you next~!

default: Your scales are hitting all the right places~~~~!

default: Ohgod, your scales are rubbing just right! Don't stop!

default: Your fur is tickling me! It turns me on so much!

default: Upgh! Yes! Pound me like the big dog you are!

default: I can feel your knot pounding against my ass! Don't you dare stop!

default: The ears and tail just make you all the hotter!

default: I'm so glad you have a canine dick instead of a human one. Can't wait for you to knot me!

default: So this is what being bred by a mouse feels like! Don't stop!

default: You have spines, yet they're soft. So good!

default: What will... what will happen if you come inside me?

default: Wh-Where did that contraption come from?

default: wow, for such a little guy you sure have a big worm!

default: Never thought a bug could fuck this good! Keep going!

climaxing: Yess! Fill me full of your butterfly spunk!

default: Yessss~! Your cock is so good~~ I'm cominggg~!

default: Oooh~~! Your cloaca is rubbing on my balls!

climaxing:
Yes let's cum together, it's sizzling!
It's like fucking in a sauna!

climaxing: My pregnant ass can't get enough of your sperm~!...

climaxing: Your petals feel so good against my pregnant ass!


default: Lifting mate. Show strength. Lift up. Drop down. Much workout!

climaxing:
Mate cumming!
Proud demeanor. V-very warm. Good mate!

default: Lifting mate. Show strength. Lift up. Drop down. On cock. Much workout!

default: Tight ass. Not mate before? I see. No Miniknog. Not here!

default: Tight pussy. Not mate before? I see. No Miniknog. Not here!

default:
Lizard heavy!
Big lizard. Much scales. Much tight!

default: Ass cold. Weird feeling. But good! Not stop!

default: Pussy cold. Weird feeling. But good! Not stop!

default: Bird light. Easy lift. Slow drop. Thrust harder!

default: Bird ass. Very tight. Very light. Fenerox like!

default: Bird pussy. Very tight. Very light. Fenerox like!

default: Like Fenerox. Yet not. Close enough. Much warm!

default: Big bunny. So soft! Fenerox mate! All day!

default: Big bunny. So soft! Fenerox Mate! All day!

default: Big deer. Fenerox dominate. Very gently!

default: Deer pussy. So warm. So submissive. Fenerox love. Long time!

default: Demon creature. Scares Fenerox. Wants mating. Not disappoint. Live, yes?

default: Dragon lizard? Is submissive? Fenerox mate. Lay dragon!

default: Is female. Yet male? Fenerox confused. ''Not think. Just fuck?'' Good idea!

default: Half human. Half Fenerox. But colorful. Is weird. So good!

default: Is Fenerox. Yet not? Is confused. Not care. Close enough. Fenerox mate!

default: Is Fenerox. yet not? Is confused. Not care. Close enough. Fenerox mate!

default: Like Fenerox. Yet glows. Very odd. Yet pretty. Fenerox mate!

default: Kitty furry. Fur tickles. Same with you?

default: Fucking pussy. Very literal. Bad joke. Is sorry.

default: Fenerox mate. Make pups. So many. So good. Not stop!

default: Strange creature. Needs sex? Fenerox obeys. Impress tentacles!

default: Plant person. Feels weird. Very heavy. Not sure why.

default: Plant pussy. Very strange. Much sap. Yessss?

default: Metal person. Very heavy. Hard lift. I strong, make it!

default: Machine ass. Very tight. Very oily. No lube needed!

default: Plant person. Much beauty. Much attract. Much mate!

default:
Smooth skin. Fur tickles?
So smooth. Brush fur. Turn on? Me too.

default: Fish person, very wet. Hard grip, but manage. Hang on!

default: Wolf person. Fenerox submit. Want mate? Fenerox mate!

default: Smooth skin. Fox ears. Fox tail. Both beautiful. Fenerox love!

default: Strange person. Fenerox mate. Feels wrong. Not know why.

default: Weird person. Like Fenerox, yet not. No matter. Fenerox mate!

default: Mouse person. Fenerox catch! Make mate, no eat!

default: Cat-man! Want mate? Fenerox mate! Why not?

default: Cat-woman! Want mate? Fenerox mate! Why not? Is cute!

default: Glow person. Very hot. Almost burn! But light.

default: Little bird. Take cock. Not break? Fenerox surprised!

default: Little bird. Tightest ass. Clench cock. So good!

default: Small bird. Very tight! Not hurt? Mate more!

default: Little bird. Tightest pussy. Clench cock. So good!

default: Big fox. So beautiful. Fenerox love! Is lucky. Want mate!

default: big fox. So beautiful. Fenerox love! Is lucky. Want mate!

default: Bug person. Very strange. Yet beautuful. Fenerox mate! Long time!

default: Extremely hard. Tingling bits! Hea-heavy load!

default: Mate strong! Lift Fenerox. Drop Down. So happy!

climaxing: Mate cums! Deep inside! So full, so good!

climaxing: C-content! Very warm. Insides full. Thank you.

default: Apex rough. Fenerox love! Fuck harder! Show no mercy!

default: Apex big. Take Fenerox. Stretch insides. Oh yes!

default: Lizard man. So big! So dominant! Weird Cock. Fenerox love!

default: Cold bird. Cock cold. Chill insides. Feels strange. Very good.

default: Cold bird. Chill insides. Feels strange. Very good.

default: Not-Fenerox. But close. Much warmer. Fenerox love! Mates not-Fenerox!

default: Bunny person. Fucks Fenerox! So hard! So fast! Don't stop!

climaxing: You cum? Make bunnies? Going again? So soon? Fenerox tired!

default: Deer person. So forceful! Yet gentle. So long! Barely fits! Don't stop!

default: Scary person. Fenerox submits! Take body? Fenerox offers! Don't hurt!

default: Dragon mate. Very powerful. Very forceful. Fenerox captured. Is possesion. Use roughly!

default: Fox-human. Is female. And yet... has cock? Is confused. Oh well. Fuck Fenerox!

default: Like Fenerox. But glows. Fenerox mate? Very much!

climaxing: Cum glows! Is worried. Shouldn't be? I hope.

default:
Cat strong!
Dick scratchy. Yet good. Please more!
Insides scratch. Yet good. Take more!

default: Plant vine. Feels good. Yet strange. Fuck more!

default: Plant cock. So strange. So good. Need more!

default: Plant scent. Very strange. Fenerox wild!

climaxing:
Seed Fenerox!
Plant cums! Very Sticky. Very tingly!

default: Tentacle person. Take Fenerox. Much pleasure!

default: Plant person. Like floran. Yet gentle. Fenerox like!

default: Machine man. Well built. Much strong! So good!

climaxing: Cum! ... Cum weird. So thick. Is... black?

default: Machine woman. Well built. Much strong! So good!

default: Human strong. Is surprised! Please, more!

default:
Fish strong!
Strong fish. Like shark. Oh yes!
Fish cock. So wet. Is too! Fuck harder!

default: Big wolf. Fenerox submit. No eat. Make mate. Take body. Love mate!

default: Wolf forceful. Fucks Fenerox. Dominates Fenerox. Much pleasure!

climaxing: Wolf cums! Make pups? Hope so. They strong!

default: Half man. Half Fox. All good. Don't stop!

default: Mouse person. No eat. Too big. Mate Fenerox? ... why not! Much pleasure.

climaxing:
Cums again? How often. Lost count.
Cums inside. Makes mice? how many?

default: Body warm. Make Fenerox hot! Too hot! Aaah!

default: Penguin machine. Very strong. Fuck harder!

default: Little bird. Uses machine. Very curious.

default: Big fox. So beautiful. Mate Fenerox? Is lucky! So good!

default: Bug person. Mates Fenerox. But how? Not sure. Not care. Too good!

default: Dark Man! Very Cold. But Strong. Fuck more!

climaxing: Fuzzy insides. Very hot! M-m-massive climax!

default: Fenerox curious. Want pregnant too? Offer mate.


default: Lifting mate. Show strength. Lift up. Drop down. On cock. Much workout!

climaxing:
Pussy tightening. Mate cum!
Proud demeanor. V-very warm. Good mate!

default: Tight ass. Not mate before? I see. No Miniknog. Not here!

default: Tight pussy. Not mate before? I see. No Miniknog. Not here!

default:
Lizard heavy!
Big lizard. Much scales. Much tight!

default:
Lizard heavy!
Big lzard. Much scales. Much tight!

default: Ass cold. Weird feeling. But good! Not stop!

default: Pussy cold. Weird feeling. But good! Not stop!

default: Bird light. Easy lift. Slow drop. Thrust harder!

default: Bird ass. Very tight. Very light. Fenerox like!

default: Bird pussy. Very tight. Very light. Fenerox like!

default: Like Fenerox. Yet not. Close enough. Much warm!

default: Big bunny. So soft! Fenerox mate! All day!

default: Big bunny. So soft! Fenerox Mate! All day!

default: Big deer. Fenerox dominate. Very gently!

default: Deer pussy. So warm. So submissive. Fenerox love. Long time!

default: Demon creature. Scares Fenerox. Wants mating. Not disappoint. Live, yes?

default: Dragon lizard? Is submissive? Fenerox mate. Lay dragon!

default: Is female. Yet male? Fenerox confused. ''Not think, just fuck?'' Good idea!

default: Half human. Half Fenerox. But colorful. Is weird. So good!

default: Is fenerox. Yet not? Is confused. Not care. Close enough. Fenerox mate!

default: Is fenerox. yet not? Is confused. Not care. Close enough. Fenerox mate!

default: Like fenerox. Yet glows. Very odd. Yet pretty. Fenerox mate!

default: Kitty furry. Fur tickles. Same with you?

default: Fucking pussy. Very literal. Make bad joke. Is sorry.

default: Fenerox mate. Make pups. So many. So good. Not stop!

default: Strange creature. Needs sex? Fenerox obeys. Impress tentacles!

default: Plant person. Feels weird. Very heavy. Not sure why.

default: Plant ass. Very strange. Much sap. Yessss?

default: Plant pussy. Very strange. Much sap. Yessss?

default: Metal person. Very heavy. Hard lift. I strong, make it!

default: Machine ass. Very tight. Very oily. No lube needed!

default: Plant person. Much beauty. Much attract. Much mate!

default:
Human. Surprisingly light!
Smooth skin. Fur tickles?
So smooth. Brush fur. Turn on? Me too.

default: Fish person, very wet. Hard grip, but manage. Hang on!

default: Wolf person. Fenerox submit. Want mate? Fenerox mate!

default: Smooth skin. Ears and tail. Both beautiful. Fenerox love!

default: Strange person. Fenerox mate. Feels wrong. Not know why.

default: Weird person. Like fenerox, yet not. No matter. Fenerox mate!

default: Mouse person. Fenerox catch! Make mate, no eat!

default: cat-man! Want mate? Fenerox mate! Why not?

default: Cat-woman! Want mate? Fenerox mate! Why not? Is cute!

default: Glow person. Very hot. Almost burn! But light.

default: Little bird. Take cock. Not break? Fenerox surprised!

default: Little bird. Tightest ass. Clench cock. So good!

default: Small bird. Very tight! Not hurt? Fenerox fuck harder!

default: Little bird. Tightest pussy. Clench cock. So good!

default: Big fox. So beautiful. Fenerox love! Is lucky. Want mate!

default: Bug person. Very strange. Yet beautuful. Fenerox mate! Long time!

default: Extremely hard. Tingling bits! Hea-heavy load!

default: Fenerox cumming! Knotting pussy, knocking up! Make fat. With pups!

default: Breeding cold bird! Make cold puppies! ... Warm birds?

default: Breeding Cat! Make Dogs! Fenerox superior!

default: Breed plant! Making seeds! Hatch puppies!

default: Fenerox Cum! Machine breed? Is possible?

default: Breeding little bird. Make little puppies!

default: Mate strong! Lift Fenerox. Drop Down. So happy!

default: Apex rough. Fenerox love! Fuck harder! Show no mercy!

default:
Fish strong!
Strong fish. Like shark. Oh yes!


default: You’re going to forget your name after Floran isssss done fucking you tonight!

default: You’re a good little ssssslut, aren’t you?

default: Ask for permission before you cum… Floran wantsss to hear you beg for it.

default: You’re going to need crutches when Floran isssss done fucking you.

climaxing: C-clench Floran's cock! Unnh, yeah! Cum!

default: Ask for permission before you cum… Florans wantsss want to hear you beg for it.

default: You’re going to need crutches when I’m done fucking you.

climaxing: O-oh.. you're tight! Sssssquirt for Floran!

default:
Asssss cold!
Floran dominate ssstinky bird!

default:
Pussssssy Cold!
Floran dominate ssstinky bird!

default:
You female. Yet hasss dick. What?!
Floran dominate fox-woman!!

default:
Floran dominate Masshhhiiine!
Machine built for Floran's pleassssure!

default:
Fisssssh is in proper place!
Floran dominate Fisssssh!

default:
No! You will be fucked by floran!
Floran dominate demon!

default:
Glow man assss is hot! Too hot!
Floran dominate glow-man!

default:
Glow man pusssssy is hot! Too hot!
Floran dominate glow-man!

default:
Penguin tight!
Floran dominate little bird!

climaxing: Floran issss gonna take all your pollen!

climaxing: Yesss~! Ssspray your yummy pollen all up inssside~!

default: No good, hairling isssss gonna tire too fassssst!

default: No good, lisssard isssss gonna tire too fassssst!

default: No good, stinky chicken isssss gonna tire too fassssst!

default: No good, turkey isssss gonna tire too fassssst!

default: No good, burn-fox isssss gonna tire too fassssst!

default: No good, bunny isssss gonna tire too fassssst!

default: No good, deer isssss gonna tire too fassssst!

default: No good, demon isssss gonna tire too fassssst!

default: No good, dragon isssss gonna tire too fassssst!

default: No good, dick-fox-lady isssss gonna tire too fassssst!

default: No good, fox-lady isssss gonna tire too fassssst!

default: No good, fox isssss gonna tire too fassssst!

default: No good, glow-fox isssss gonna tire too fassssst!

default: No good, furball isssss gonna tire too fassssst!

default: No good, tentaclesss isssss gonna tire too fassssst!

default: No good, mutt isssss gonna tire too fassssst!

default: Floran better not tire too fassssst! Or else Floran sssstab!

default: Flower better not tire too fassssst! Or else Floran sssstab!

default: Weak flower person dominatesss floran! Isss not so weak...

default: Masshhhiiine built for Floran's pleassssure!

default: No good, meat man isssss gonna tire too fassssst!

default: No good, meat girl isssss gonna tire too fassssst!

default: No good, fish isssss gonna tire too fassssst!

default: Maybe fish iss not as weak asss Floran thought~!

default: No good, wolf isssss gonna tire too fassssst!

default: No good, man-fox isssss gonna tire too fassssst!

default: No good, weird fox isssss gonna tire too fassssst!

default: No good, rat isssss gonna tire too fassssst!

default: Mouse dominatesss floran! ... that makessss no sense.

default: Floran isss getting hungry. Fuck harder, or else Floran sssstab!

default: No good, man-cat isssss gonna tire too fassssst!

default: No good, glowie isssss gonna tire too fassssst!

default: tiny chick weak! Needssss metal thing to lift floran!

default: No good, bug isssss gonna tire too fassssst!

default: No good, spooky isssss gonna tire too fassssst!

default: Mate strong. Can get you pregnant too, yessss?


default: Arrogant. You’re going to forget your name after I’m done fucking you tonight!

default: Teasing. You’re a good little slut, aren’t you?

default: Commanding. Ask for permission before you cum… I want to hear you beg for it.

default: Boastful. You’re going to need crutches when I’m done fucking you.

climaxing: Pleasured. O-oh.. you're tight! Squirt for me!

default: Shocked. wow, your ass is freezing cold!

default: Dominating. Yeah, take my cock, you fluffy bird raptor!

default: Shocked. wow, your pussy is freezing cold!

default: Teasing. You really do breed like a rabbit, don't you?!

default: Resigned. If you're real, then that means I'm definately going to hell for this. Oh well, worth it.

default: Uncomfortable. I have to admit, I'm a bit weirded out by this...

default: Humorus. I'd make a pussy joke, but that would be just ass.

default: Boastful. I'm gonna fuck this pussy till it purs!

default: Questioning. Your petals are this soaked for me?

default: Hopeful. So, is this reason enough for you as to why you shouldn't destroy all life? I hope so!

default: Worried. Maybe I should stop before you reconnect with your hivemind. On second thought... nah.

default: Shocked. I can't believe I'm fucking the thing that destroyed Earth!

default: Compliment. You're so soft and lovely. I've wanted you all day~~~~!

default: Ticklish. Your fur is tickling me! It's making me even harder!

default: Resigned. If demons are real, does this mean I'm going to hell for this? ... Worth it.

default: Complimenting. You're so soft and lovely. I've wanted you all day~~~~!

default: Alarmed. Danger! Approaching unsafe heat levels..

default: Perplexed. How am I even fitting my cock into you?

default: Boastful. I be plundering this penguin pussy!

default: Ticklish. Your fur is tickling me. It's making me even harder!

default: Confessing. I have to admit, feels a bit weird fucking a bug.

default: Questioning. Why is bug pussy so damn sexy?!

climaxing: Demanding. Mnnh, cum for me! I'm c-cumming too!

default: Exctatic. So deep... ahn... your gonna enter my womb!

default: Lustful. Oohh~... You're making me so wet~!

default: Submissive. I've been so naughty! Punish me harder~!

default: Lustful. Ooh, I'm so horny all of the sudden~! Keep going~!

default: Impatient. I've wanted this for so long!

default: Lecherous. Cover my circuits with your genetic code~!

default: Lascivious. You're going to make me overheat~!

default: Inquisitive. Am I turning you on, baby~?

default: Affectionate. You are so skilled at this~!

default: Helpful. Applying additional lubricant...

default: Pleasured. Make my joints creak, lover~!

default: Dazed. Ahh~! My processors are running on high~!

climaxing: Demanding. O-oh~! You're cumming~! Unng, paint my insides with your thick spunk~!

climaxing: Satisfied. Oooh~~! You're drenching me down there!

climaxing: Satisfied. Oh, your cum is so hot and thick inside me~!

climaxing: Thoughtful. Thinking about your organic babies inside me makes my pussy so wet...

climaxing: Demanding. Oh god yes! Fill me to the brim!

climaxing: Lecherous. Cover my circuits with your genetic code~!

climaxing: Affectionate. ~Yes! I'll take all your cum!

climaxing: Lecherous. Cover my circuits in your genetic code~!

climaxing: Overheating. Activating cooling system~!

climaxing: Euphoric. My ports can't handle it~! A- Aahh~!

climaxing: Triumphal. Yes~! Empty your genetic code into me~!

pregnant: Observant. Your belly is so big! It's got me all wet thinking about it!

pregnant: Saucious. I can feel your pregnant belly against me! I didn't know that would make me so wet!~~

pregnant: Intrigued. Oh my! You're pregnant~! ...Can i get pregnant too~?

climaxing: Dominant. You're soaking my asshole... you'd better lick that up!

climaxing: Dominant. Save some for when I'm eating your ass!

climaxing: Concerned. A~Ahn~! You're oxidizing my rear!

default: Comparitive. Standard glitch cocks aren't this thick!

default: Demanding. YESS! Stuff me full with your hairy cock!

default: Blastphemous. OH! Yes! You're MY Big Ape!

default: Eager. Shove your Apex member inside of me~!

default: Naughty. Ah~! Make me your next project~!

default: Intrigued. Your fur is brushing against my port~!

default: Aroused. Your fur is warm against my backside~!

climaxing: Saucatious. OH you're filling me up! Make me carry your little apes!

climaxing: Urgent. Yes~! Empty your Apex genes into me~!

default: Observant. Your lizard cock feels so weird... yet so goood! Don't stop!

climaxing: Demanding. Yess! Fill me full of your swamp eggs!

default: Ticklish. Your floofy feathers are tickling me! It's turning me on so much!

default: Amused. Your feathers are tickling my diodes~!

default: Needy. Yes~! Dip your quills in my ink~!

default: Naughty. Ah~! Your talons are digging into my joints~!

climaxing: Demanding. Hng yess! I want to have your chicks!

climaxing: demanding. Hng yess! I want to have your chicks!

default: Blasphemous. I am your offering to Kluex!

default: Ticklish. Your fluffy feathers are tickling me! It's turning me on so much!

default: Aroused. Harder~! Fill me with your bird seed~!

climaxing: Demanding. Hng Yesss, I want to have your chicks!

default: Ticklish. Your fur is so warm and so ticklish! It's turning me on so hard!

default: Lustful. Yes... give me your big doggy dick!

climaxing: Demanding. Yesss! Give me your knot! Tie me up and fill me!

default: Commanding. Yess! Breed me like a rabbit!

default: Observant. What they say about bunnies is true! Don't stop!

default: Energized. It keeps on going and going and going and going....

climaxing: Demanding. Yess! Fill me full of little bunnies!

climaxing: Shocked. Ungh, fill me u- wow, ready to go again? Such stamina!

default: Observant. So gentle, yet so dominating!

default: Gluttonus. Yesss! Give me your long deer cock!

climaxing:
Greedy. Yess! Give me your fawns!
Alert. Deerkin DNA acquired~!

default: Blastphemus. If this is what hell feels like, keep on making me sin!

default: Confused. I admit this is a bit weird... but don't stop!

climaxing: Confused. Yess! give me your... whatever you call your children!

default: Ticklish. Your fur is tickling me! It's turning me on so much!

default: Lustful. Yes, fuck me, you adorable little foxy!

default: Lustful. I can feel your knot slapping against my pussy!

climaxing: Greedy. Yess! Fill me full of your pups!

climaxing: Demanding. Yes, knot me! Tie me up and fill me!

default: Encouraging. Your cock feels amazing! Don't stop you big cute... glowing... furry thing!

default: Observant. Your cock is glowing inside of me!

default: Lustful. Your cock feels weird inside of me. It's driving me nuts!

climaxing: Gluttonus. Yess! Fill me full of your glowing cum!

climaxing: Concerned. So about this glowing cum... should I be worried? No? Releived. Okay then. Give me more!

default: Mixed. Your cock is so scratchy... yet it feels so good. Don't stop!

default: Unsure. Are those... spines? It hurts, yet it's so good!

climaxing:
Demanding.Give me your kittens!
Alert. Felin DNA acquired~!

climaxing: Pleasured. Oh god, your claws are digging into me! It feels so good!

climaxing: Pleasureful. Oh god, your claws are digging into me! It feels so good!

default: Questioning. What's... what's in your pollen?

default: Lustful. I've never been this wet before, your nectar is driving me wild!

default: Shocked. Your petals are this soaked for me?

default: Shocked. I can feel you wriggling inside me!

default: Gluttonus. Your scent makes me... it makes me want your cock so badly...

default: Yearning. Please! Spread your seed inside me~!

climaxing: Observant. Your nectar is running everywhere~~

climaxing: Intoxicated. Your cum is making me dizzy~!

climaxing: Ticklish. Your cum tingles! Is it going to make me pregnant?

climaxing: Demanding. I want to carry your seedlings!!~~

pregnant: Observant. Your petals are so plump and swollen!

pregnant: Greedy. This nectar is... so strong! I need more!

climaxing: Shocked. Your nectar is running everywhere~~

default: Blasphemous. This is all sorts of wrong... Don't stop!

default: Confused. Why does this feel so damn good?

default: Sickened. I can't believe I'm being fucked by the thing that destroyed the Earth! It's so goood!

default: Lustful. Your tentacle is wiggling around inside of me!

default: Hopeful. I hope my body is enough to convince you not to destroy the universe.

climaxing: Lustful. Yess! Fill me with- Frightened. wait, I'm not going to burst open from the inside, am I?

default: Lustful. You're making my pussy tingle~!

default: Shocked. You didn't tell me you could vibrate~!

default: Pleasured. I can feel it twisting inside me!~~~~

default: Pleasured. It's... it's getting bigger inside me~!

default: Horny. Yes~! Transfer your genetic code into me~!

default: Eager. Plow me like I'm the village farm~!

default: Naughty. Treat me like I'm your slave~! Aaahh~!

default: Aroused. You fit into my port perfectly~!

climaxing: Horny. Yes~! Transfer your genetic code into me~!

climaxing: Desperate. Unload your oil inside of me~!

pregnant: Observant. I can hear the gears in your belly!

default: Lustful. Your soft belly turns me on so much!

default: Offering. My pussy input has been updated specifically for your cock!

default: Horny. My port is so wet for you, Human~!

default: Naughty. Keep pounding me~! Show me a Human's orgasm~!

default: Loving. A- Ahh~! Your tool feels so soft~!

default: Eager. I'm ready~! Give me your hot Human sperm~!

climaxing:
Alert. Human DNA acquired~!
Pleased. Ahhn~! You're cumming~!

default: Shocked. Ohgod, your scales are rubbing just right! Don't stop!

default: Horny. I knew you would like wet things~!

default: Perplexed. Ah~!... You're so forceful~!... Yet so graceful~!

default: Intrigued. You're producing so much lubricant down there~!

default: Chilled. Your body feels so cool against my circuits~!

default: Naughty. Make me carry your prosperous children~!

climaxing: Excited. Yes~! Make my steel slick with your Hylotl cum~!

pregnant: Gentle. I can feel your eggs moving around, you're so full!

default: Ticklish. Your fur is tickling me! It turns me on so much!

default: Demanding. Upgh! Yes! Pound me like the big dog you are!

default: Pleasured. I can feel your knot pounding against my pussy! Don't you dare stop!

climaxing: Greedy. Yess! Give me a litter of pups!!

climaxing: Demanding. Yesss! Knot me! Tie me up and fill me!

default: Happy. The ears and tail just make you all the hotter!

default: Relieved. I'm so glad you have a canine dick instead of a human one. Can't wait for you to knot me!

default: Demanding. Yes! Give it to me, doggie-boy!

climaxing: Demanding. Yes! Knot me! Tie me up and fill me!

default: Pleasured. So this is what being bred by a mouse feels like! Don't stop!

default: Lustful. You have spines, yet they're soft. So good!

climaxing:
Demanding. Yess! Fill me with a litter!
Alert. Neko DNA acquired~!

default: Alarmed. Your rod is burning hot inside me~~!

default: Worried. What will... what will happen if you come inside me?

default: Naughty. Ah~! Uhnn~! It feels like a forge down there~!

default: Intrigued. Oh~! I feel like I'm glowing~!

climaxing: Happy. Our juices are glowing together~~!

climaxing: Alerted. Internal oil fire detected. Extinquishing... done.

climaxing: Alarmed. Reaching critical temperatures~!

default: Shocked. How can such a little bird have such a big cock?!

default: Lustful. Your cock feels odd. Odd, but good!

default: Surprised. Never thought a bug could fuck this good! Keep going!

climaxing: Demanding. Yess! Fill me full of your butterfly eggs!

climaxing: Concerned. I'm leaking dark matter... should I be worried?

default: Climax. YES~! I'm cumming from your amazing rod!

default: Climaxing. Hnng~ My port is quivering with pure liquid luster!

climaxing: Double Climax. Ah~! Yes! Fill my port as i empty my tanks!

climaxing: ERR0R! Pleasure is overloading my body~~!

default: Pleasured. Your soft tool is making me cum uncontrollably!

climaxing: Aroused. Your warm body deep in my rear, filling me with cum... Its too much~!

default: Error! Aaahh~ Yeeeees~ Aaaaah~ More sex~!

default: Needy. Give me more of your samples of love~!

default: Hopeful. Ah, you'll help me assemble our kids later~?

default: Needy. My Incubator is full but I need ~MORE~!

default: Aroused. Please! Fuck me more~! Much more~!

climaxing:
ERR0R! My incubator is overflowing~!
Ahegao. Aaaaaaaahhhnnnnn~~~~

climaxing: Offering. Do you want him to get you pregnant next?