UNPKG

chatgpt-agent

Version:

Client for the unofficial ChatGPT API with progressive responses and more.

378 lines (366 loc) 11.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Agent: () => Agent, Conversation: () => Conversation, Session: () => Session, converse: () => converse, make_headers: () => make_headers, moderate: () => moderate, refresh: () => refresh }); module.exports = __toCommonJS(src_exports); // src/agent.ts var import_node_events3 = __toESM(require("events")); // src/session.ts var import_node_events2 = __toESM(require("events")); // src/conversation.ts var import_node_events = __toESM(require("events")); // src/request.ts var import_node_crypto = require("crypto"); var import_node_fetch = __toESM(require("node-fetch")); // src/debug.ts var import_debug = __toESM(require("debug")); var log = (0, import_debug.default)("chatgpt"); // src/config.ts var CHATGPT_BACKEND = process.env.CHATGPT_BACKEND || "https://chat.openai.com/backend-api"; var CHATGPT_TIMEOUT = Number(process.env.CHATGPT_TIMEOUT) || 6e4; var CHATGPT_RETRY = Number(process.env.CHATGPT_RETRY) || 1; // src/request.ts function make_headers(token) { const headers = new import_node_fetch.Headers(); if (token) { headers.set("Authorization", `Bearer ${token}`); } headers.set("Content-Type", "application/json"); headers.set( "User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" ); return headers; } async function moderate(token, input, backend = CHATGPT_BACKEND, timeout = CHATGPT_TIMEOUT) { const headers = make_headers(token); log(headers); for (let i = 0; i <= CHATGPT_RETRY; i++) { try { const controller = new AbortController(); const timeout_id = setTimeout(() => controller.abort(), timeout); const res = await (0, import_node_fetch.default)(`${backend}/moderations`, { headers, body: JSON.stringify({ input, model: "text-moderation-playground" }), method: "POST", signal: controller.signal }); clearTimeout(timeout_id); if (controller.signal.aborted) { throw new Error("Request timed out"); } log("sent moderation request", res.status); if (res.status !== 200) { try { const data2 = await res.clone().json(); log("moderation error", data2); throw new Error(data2?.error); } catch { const text = await res.clone().text(); log("moderation error", text); throw new Error(text); } } const data = await res.json(); return data; } catch (err) { if (i === CHATGPT_RETRY - 1) { throw err; } log("retrying moderation request", i + 1); } } throw new Error("Failed to moderate"); } async function converse(token, content, conversation_id, parent_id, backend = CHATGPT_BACKEND, timeout = CHATGPT_TIMEOUT) { const headers = make_headers(token); headers.set("Accept", "text/event-stream"); log(headers); for (let i = 0; i <= CHATGPT_RETRY; i++) { try { const controller = new AbortController(); const timeout_id = setTimeout(() => controller.abort(), timeout); const res = await (0, import_node_fetch.default)(`${backend}/conversation`, { headers, body: JSON.stringify({ action: "next", messages: [ { id: (0, import_node_crypto.randomUUID)(), role: "user", content: { content_type: "text", parts: [content] } } ], parent_message_id: parent_id || (0, import_node_crypto.randomUUID)(), conversation_id, model: "text-davinci-002-render" }), method: "POST", signal: controller.signal }); clearTimeout(timeout_id); if (controller.signal.aborted) { throw new Error("Request timed out"); } log("sent conversation request", res.status); if (res.status !== 200) { try { const data = await res.clone().json(); log("conversation error", data); throw new Error(data?.error?.detail); } catch { const text = await res.clone().text(); log("conversation error", text); throw new Error(text); } } return res.body; } catch (err) { if (i === CHATGPT_RETRY - 1) { throw err; } log("retrying conversation request", i + 1); } } throw new Error("Failed to converse"); } async function refresh(refresh_token) { const headers = make_headers(); headers.set("Cookie", `__Secure-next-auth.session-token=${refresh_token}`); log(headers); const res = await (0, import_node_fetch.default)("https://chat.openai.com/api/auth/session", { headers }); log("sent refresh request", res.status); if (res.status !== 200) { try { const data2 = await res.clone().json(); log("refresh error", data2); throw new Error(data2?.error); } catch { const text = await res.clone().text(); log("refresh error", text); throw new Error(text); } } const data = await res.json(); if (data?.error) { throw new Error(data.error); } const new_refresh_token = res.headers.get("set-cookie")?.split(";")[0]; const refresh2 = new_refresh_token?.split("=")[1]; if (data?.accessToken) { const new_refresh_token2 = res.headers.get("set-cookie")?.split(";")[0]; if (new_refresh_token2) { log("refreshed refresh token", new_refresh_token2); } return { token: data.accessToken, refresh: refresh2 || "" }; } else { throw new Error("No access token returned"); } } // src/conversation.ts var verbose = log.extend("verbose"); var Conversation = class extends import_node_events.default { constructor(session, message) { super(); this.session = session; this.message = message; this.response = this.run(); } async run() { if (this.session.agent.validate() === false) { try { await this.session.agent.refresh(); } catch (err) { this.emit("error", err); } } const result = await this.converse().catch((err) => (this.emit("error", err), "")); log("Conversation Result", result); return result; } async converse() { const history = this.session.history.filter((h) => h.author === "assistant"); const stream = await converse( this.session.agent.token, this.message, this.session.id.startsWith("tmp-") ? void 0 : this.session.id, history[history.length - 1]?.id, this.session.agent.backend ); let last; return await new Promise((resolve) => { let data = ""; stream.on("data", (chunk) => { data += chunk.toString(); const split = data.indexOf("\n\n"); if (split !== -1) { const part = data.slice(0, split).replace(/^data: /, ""); verbose("Partially Received", part); if (part.startsWith("[DONE]")) { return; } try { const json = JSON.parse(part); this.emit("partial", json.message.content.parts.join("\n")); last = json; } catch (err) { this.emit("error", err); } data = data.slice(split + 2); } }); stream.on("end", () => { const response = last?.message.content.parts.join("\n"); log("Received", response); this.session.history.push({ id: last?.message.id, author: "assistant", message: response, conversation: this }); this.session.rename(last?.conversation_id); this.emit("complete", response); resolve(response); }); }); } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } off(event, listener) { return super.off(event, listener); } emit(event, ...args) { return super.emit(event, ...args); } }; // src/session.ts var Session = class extends import_node_events2.default { constructor(agent) { super(); this.agent = agent; this.id = `tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`; this.history = []; } talk(message) { this.history.push({ author: "user", message, id: `${Date.now()}-${Math.random().toString(36).slice(2)}` }); return new Conversation(this, message); } rename(id) { this.emit("rename", { old: this.id, new: id }); this.id = id; } replies() { return this.history.filter((h) => h.author === "assistant" && h.conversation).map((h) => h.conversation); } on(event, listener) { return super.on(event, listener); } }; // src/agent.ts var Agent = class extends import_node_events3.default { constructor(token, refresh_token, { backend = CHATGPT_BACKEND, timeout = CHATGPT_TIMEOUT } = {}) { super(); this.token = token; this.refresh_token = refresh_token; this.sessions = /* @__PURE__ */ new Map(); this.backend = backend; this.timeout = timeout; } session() { const sess = new Session(this); this.sessions.set(sess.id, sess); sess.on("rename", (data) => { this.sessions.delete(data.old); this.sessions.set(data.new, sess); }); return sess; } async refresh() { if (!this.refresh_token) { throw new Error("No refresh token"); } const result = await refresh(this.refresh_token); this.token = result.token; this.refresh_token = result.refresh; this.emit("refresh", result); } validate() { try { const [header, payload, signature] = this.token.split("."); if (!header || !payload || !signature) { return false; } const payloadJSON = JSON.parse(Buffer.from(payload, "base64").toString()); if (payloadJSON.exp < Date.now() / 1e3) { return false; } return true; } catch { return false; } } on(event, listener) { return super.on(event, listener); } off(event, listener) { return super.off(event, listener); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Agent, Conversation, Session, converse, make_headers, moderate, refresh });