You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
4.9 KiB

11 months ago
  1. const chalk = require("chalk");
  2. const { RetryError } = require("../error");
  3. const { Telemetry } = require("../../../../models/telemetry");
  4. const SOCKET_TIMEOUT_MS = 300 * 1_000; // 5 mins
  5. /**
  6. * Websocket Interface plugin. It prints the messages on the console and asks for feedback
  7. * while the conversation is running in the background.
  8. */
  9. // export interface AIbitatWebSocket extends ServerWebSocket<unknown> {
  10. // askForFeedback?: any
  11. // awaitResponse?: any
  12. // handleFeedback?: (message: string) => void;
  13. // }
  14. const WEBSOCKET_BAIL_COMMANDS = [
  15. "exit",
  16. "/exit",
  17. "stop",
  18. "/stop",
  19. "halt",
  20. "/halt",
  21. "/reset", // Will not reset but will bail. Powerusers always do this and the LLM responds.
  22. ];
  23. const websocket = {
  24. name: "websocket",
  25. startupConfig: {
  26. params: {
  27. socket: {
  28. required: true,
  29. },
  30. muteUserReply: {
  31. required: false,
  32. default: true,
  33. },
  34. introspection: {
  35. required: false,
  36. default: true,
  37. },
  38. },
  39. },
  40. plugin: function ({
  41. socket, // @type AIbitatWebSocket
  42. muteUserReply = true, // Do not post messages to "USER" back to frontend.
  43. introspection = false, // when enabled will attach socket to Aibitat object with .introspect method which reports status updates to frontend.
  44. }) {
  45. return {
  46. name: this.name,
  47. setup(aibitat) {
  48. aibitat.onError(async (error) => {
  49. if (!!error?.message) {
  50. console.error(chalk.red(` error: ${error.message}`), error);
  51. aibitat.introspect(
  52. `Error encountered while running: ${error.message}`
  53. );
  54. }
  55. if (error instanceof RetryError) {
  56. console.error(chalk.red(` retrying in 60 seconds...`));
  57. setTimeout(() => {
  58. aibitat.retry();
  59. }, 60000);
  60. return;
  61. }
  62. });
  63. aibitat.introspect = (messageText) => {
  64. if (!introspection) return; // Dump thoughts when not wanted.
  65. socket.send(
  66. JSON.stringify({
  67. type: "statusResponse",
  68. content: messageText,
  69. animate: true,
  70. })
  71. );
  72. };
  73. // expose function for sockets across aibitat
  74. // type param must be set or else msg will not be shown or handled in UI.
  75. aibitat.socket = {
  76. send: (type = "__unhandled", content = "") => {
  77. socket.send(JSON.stringify({ type, content }));
  78. },
  79. };
  80. // aibitat.onStart(() => {
  81. // console.log("🚀 starting chat ...");
  82. // });
  83. aibitat.onMessage((message) => {
  84. if (message.from !== "USER")
  85. Telemetry.sendTelemetry("agent_chat_sent");
  86. if (message.from === "USER" && muteUserReply) return;
  87. socket.send(JSON.stringify(message));
  88. });
  89. aibitat.onTerminate(() => {
  90. // console.log("🚀 chat finished");
  91. socket.close();
  92. });
  93. aibitat.onInterrupt(async (node) => {
  94. const feedback = await socket.askForFeedback(socket, node);
  95. if (WEBSOCKET_BAIL_COMMANDS.includes(feedback)) {
  96. socket.close();
  97. return;
  98. }
  99. await aibitat.continue(feedback);
  100. });
  101. /**
  102. * Socket wait for feedback on socket
  103. *
  104. * @param socket The content to summarize. // AIbitatWebSocket & { receive: any, echo: any }
  105. * @param node The chat node // { from: string; to: string }
  106. * @returns The summarized content.
  107. */
  108. socket.askForFeedback = (socket, node) => {
  109. socket.awaitResponse = (question = "waiting...") => {
  110. socket.send(JSON.stringify({ type: "WAITING_ON_INPUT", question }));
  111. return new Promise(function (resolve) {
  112. let socketTimeout = null;
  113. socket.handleFeedback = (message) => {
  114. const data = JSON.parse(message);
  115. if (data.type !== "awaitingFeedback") return;
  116. delete socket.handleFeedback;
  117. clearTimeout(socketTimeout);
  118. resolve(data.feedback);
  119. return;
  120. };
  121. socketTimeout = setTimeout(() => {
  122. console.log(
  123. chalk.red(
  124. `Client took too long to respond, chat thread is dead after ${SOCKET_TIMEOUT_MS}ms`
  125. )
  126. );
  127. resolve("exit");
  128. return;
  129. }, SOCKET_TIMEOUT_MS);
  130. });
  131. };
  132. return socket.awaitResponse(`Provide feedback to ${chalk.yellow(
  133. node.to
  134. )} as ${chalk.yellow(node.from)}.
  135. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n`);
  136. };
  137. // console.log("🚀 WS plugin is complete.");
  138. },
  139. };
  140. },
  141. };
  142. module.exports = {
  143. websocket,
  144. WEBSOCKET_BAIL_COMMANDS,
  145. };