UNPKG

unlimited-ai

Version:

Fast, minimal Node.js wrapper for the Voids API AI chat completions.

287 lines (286 loc) 9.32 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); //#region \0rolldown/runtime.js 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 __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, 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)); //#endregion let ky = require("ky"); ky = __toESM(ky, 1); let closewords = require("closewords"); //#region src/config.ts const config = { API_URL: "https://api.voids.top/v1/chat/completions", MODELS_URL: "https://api.voids.top/v1/models" }; //#endregion //#region src/utils.ts const VALID_ROLES = new Set([ "system", "user", "assistant" ]); function isValidMessage(msg) { return typeof msg === "object" && msg !== null && "role" in msg && "content" in msg && VALID_ROLES.has(msg["role"]) && typeof msg["content"] === "string"; } function validateMessage(message) { if (!isValidMessage(message)) throw new TypeError("message must be { role: 'system'|'user'|'assistant', content: string }."); } function validateMessages(messages) { if (!Array.isArray(messages) || !messages.every(isValidMessage)) throw new TypeError("messages must be an array of { role: 'system'|'user'|'assistant', content: string }."); } //#endregion //#region src/generate.ts async function generate(model, messages, raw = false) { if (typeof model !== "string" || model.length === 0) throw new TypeError("model must be a non-empty string."); validateMessages(messages); const data = await ky.default.post(config.API_URL, { json: { model, messages } }).json(); return raw ? data : data.choices[0]?.message.content ?? ""; } async function* stream(model, messages) { if (typeof model !== "string" || model.length === 0) throw new TypeError("model must be a non-empty string."); validateMessages(messages); const { body } = await ky.default.post(config.API_URL, { json: { model, messages, stream: true }, timeout: false }); if (!body) throw new Error("Response body is null."); const reader = body.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { const trimmed = line.trimEnd(); if (!trimmed.startsWith("data: ")) continue; const data = trimmed.slice(6); if (data === "[DONE]") return; try { const content = JSON.parse(data).choices[0]?.delta.content; if (content) yield content; } catch {} } } } finally { reader.releaseLock(); } } async function ask(model, prompt, system) { const messages = []; if (system !== void 0) messages.push({ role: "system", content: system }); messages.push({ role: "user", content: prompt }); return generate(model, messages); } //#endregion //#region src/models.ts async function models() { return (await ky.default.get(config.MODELS_URL).json()).data.map((d) => d.id); } //#endregion //#region src/search.ts async function searchModels(word) { if (typeof word !== "string") throw new TypeError("word must be a string."); return (0, closewords.closeWords)(word, await models()); } //#endregion //#region src/client.ts var AI = class { model = ""; messages = []; useSearch = false; conversationMap = /* @__PURE__ */ new Map(); activeConversationId = null; constructor(init) { if (init?.model !== void 0) { if (typeof init.model !== "string" || init.model.length === 0) throw new TypeError("model must be a non-empty string."); this.model = init.model; } if (init?.messages !== void 0) { validateMessages(init.messages); this.messages = [...init.messages]; } if (init?.system !== void 0) { if (typeof init.system !== "string") throw new TypeError("system must be a string."); this.messages.unshift({ role: "system", content: init.system }); } } setModel(model, search = false) { if (typeof model !== "string" || model.length === 0) throw new TypeError("model must be a non-empty string."); this.model = model; this.useSearch = search; return this; } setMessages(messages) { validateMessages(messages); this.messages = [...messages]; return this; } addMessage(message) { validateMessage(message); this.messages.push(message); return this; } removeMessage(index) { if (!Number.isInteger(index)) throw new TypeError("index must be an integer."); if (index < 0 || index >= this.messages.length) throw new RangeError(`index out of bounds. Valid range: 0 to ${this.messages.length - 1}.`); this.messages.splice(index, 1); return this; } clearMessages() { this.messages = []; return this; } setSystem(content) { if (typeof content !== "string") throw new TypeError("content must be a string."); const idx = this.messages.findIndex((m) => m.role === "system"); if (idx !== -1) this.messages.splice(idx, 1); this.messages.unshift({ role: "system", content }); return this; } useConversation(id) { if (id !== null && (typeof id !== "string" || id.length === 0)) throw new TypeError("id must be a non-empty string."); this.activeConversationId = id; if (id !== null && !this.conversationMap.has(id)) this.conversationMap.set(id, []); return this; } listConversations() { return [...this.conversationMap.keys()]; } getConversationMessages(id) { return [...this.conversationMap.get(id) ?? []]; } clearConversation(id) { if (this.conversationMap.has(id)) this.conversationMap.set(id, []); return this; } deleteConversation(id) { this.conversationMap.delete(id); return this; } exportConversations(id) { if (id !== void 0) return [...this.conversationMap.get(id) ?? []]; const result = {}; for (const [key, messages] of this.conversationMap) result[key] = [...messages]; return result; } importConversations(data, replace = false) { if (replace) this.conversationMap.clear(); for (const [id, messages] of Object.entries(data)) { validateMessages(messages); this.conversationMap.set(id, [...messages]); } return this; } async generate(raw) { if (!this.model) throw new Error("model is not set. Call setModel() first."); if (this.messages.length === 0) throw new Error("messages are empty. Call addMessage() or setMessages() first."); const model = this.useSearch ? (await searchModels(this.model))[0] ?? this.model : this.model; if (raw) return generate(model, this.messages, true); return generate(model, this.messages); } async ask(prompt, conversationId) { if (typeof prompt !== "string") throw new TypeError("prompt must be a string."); if (!this.model) throw new Error("model is not set. Call setModel() first."); const id = conversationId ?? this.activeConversationId; const model = this.useSearch ? (await searchModels(this.model))[0] ?? this.model : this.model; if (id !== null) { if (!this.conversationMap.has(id)) this.conversationMap.set(id, []); const history = this.conversationMap.get(id); history.push({ role: "user", content: prompt }); const reply = await generate(model, [...this.messages, ...history]); history.push({ role: "assistant", content: reply }); return reply; } return generate(model, [...this.messages, { role: "user", content: prompt }]); } async *stream(prompt, conversationId) { if (typeof prompt !== "string") throw new TypeError("prompt must be a string."); if (!this.model) throw new Error("model is not set. Call setModel() first."); const id = conversationId ?? this.activeConversationId; const model = this.useSearch ? (await searchModels(this.model))[0] ?? this.model : this.model; if (id !== null) { if (!this.conversationMap.has(id)) this.conversationMap.set(id, []); const history = this.conversationMap.get(id); history.push({ role: "user", content: prompt }); let fullReply = ""; for await (const chunk of stream(model, [...this.messages, ...history])) { fullReply += chunk; yield chunk; } history.push({ role: "assistant", content: fullReply }); } else for await (const chunk of stream(model, [...this.messages, { role: "user", content: prompt }])) yield chunk; } getFormat() { return { model: this.model, messages: [...this.messages] }; } }; //#endregion exports.AI = AI; exports.ask = ask; exports.config = config; exports.generate = generate; exports.models = models; exports.searchModels = searchModels; exports.stream = stream; //# sourceMappingURL=index.cjs.map