UNPKG

unlimited-ai

Version:

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

1 lines 17.1 kB
{"version":3,"file":"index.cjs","names":["streamRequest"],"sources":["../src/config.ts","../src/utils.ts","../src/generate.ts","../src/models.ts","../src/search.ts","../src/client.ts"],"sourcesContent":["export const config = {\n API_URL: 'https://api.voids.top/v1/chat/completions',\n MODELS_URL: 'https://api.voids.top/v1/models',\n} as const;\n\nexport type Config = typeof config;\n","import type { Message } from './types.js';\n\nconst VALID_ROLES = new Set<string>(['system', 'user', 'assistant']);\n\nexport function isValidMessage(msg: unknown): msg is Message {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n 'role' in msg &&\n 'content' in msg &&\n VALID_ROLES.has((msg as Record<string, unknown>)['role'] as string) &&\n typeof (msg as Record<string, unknown>)['content'] === 'string'\n );\n}\n\nexport function validateMessage(message: unknown): asserts message is Message {\n if (!isValidMessage(message)) {\n throw new TypeError(\"message must be { role: 'system'|'user'|'assistant', content: string }.\");\n }\n}\n\nexport function validateMessages(messages: unknown): asserts messages is Message[] {\n if (!Array.isArray(messages) || !messages.every(isValidMessage)) {\n throw new TypeError(\n \"messages must be an array of { role: 'system'|'user'|'assistant', content: string }.\",\n );\n }\n}\n","import ky from 'ky';\nimport { config } from './config.js';\nimport { validateMessages } from './utils.js';\nimport type { CompletionResponse, Message, StreamChunk } from './types.js';\n\nexport async function generate(\n model: string,\n messages: Message[],\n raw: true,\n): Promise<CompletionResponse>;\nexport async function generate(model: string, messages: Message[], raw?: false): Promise<string>;\nexport async function generate(\n model: string,\n messages: Message[],\n raw = false,\n): Promise<string | CompletionResponse> {\n if (typeof model !== 'string' || model.length === 0) {\n throw new TypeError('model must be a non-empty string.');\n }\n validateMessages(messages);\n\n const data = await ky\n .post(config.API_URL, { json: { model, messages } })\n .json<CompletionResponse>();\n return raw ? data : (data.choices[0]?.message.content ?? '');\n}\n\nexport async function* stream(model: string, messages: Message[]): AsyncGenerator<string> {\n if (typeof model !== 'string' || model.length === 0) {\n throw new TypeError('model must be a non-empty string.');\n }\n validateMessages(messages);\n\n const response = await ky.post(config.API_URL, {\n json: { model, messages, stream: true },\n timeout: false,\n });\n\n const { body } = response;\n if (!body) throw new Error('Response body is null.');\n\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n const trimmed = line.trimEnd();\n if (!trimmed.startsWith('data: ')) continue;\n const data = trimmed.slice(6);\n if (data === '[DONE]') return;\n\n try {\n const chunk = JSON.parse(data) as StreamChunk;\n const content = chunk.choices[0]?.delta.content;\n if (content) yield content;\n } catch {\n // skip malformed chunks\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport async function ask(model: string, prompt: string, system?: string): Promise<string> {\n const messages: Message[] = [];\n if (system !== undefined) messages.push({ role: 'system', content: system });\n messages.push({ role: 'user', content: prompt });\n return generate(model, messages);\n}\n","import ky from 'ky';\nimport { config } from './config.js';\nimport type { ModelListResponse } from './types.js';\n\nexport async function models(): Promise<string[]> {\n const data = await ky.get(config.MODELS_URL).json<ModelListResponse>();\n return data.data.map((d) => d.id);\n}\n","import { closeWords } from 'closewords';\nimport { models } from './models.js';\n\nexport async function searchModels(word: string): Promise<string[]> {\n if (typeof word !== 'string') throw new TypeError('word must be a string.');\n const data = await models();\n return closeWords(word, data);\n}\n","import { generate, stream as streamRequest } from './generate.js';\nimport { searchModels } from './search.js';\nimport { validateMessage, validateMessages } from './utils.js';\nimport type { CompletionResponse, ConversationStore, Message } from './types.js';\n\nexport class AI {\n private model = '';\n private messages: Message[] = [];\n private useSearch = false;\n private conversationMap = new Map<string, Message[]>();\n private activeConversationId: string | null = null;\n\n constructor(init?: { model?: string; messages?: Message[]; system?: string }) {\n if (init?.model !== undefined) {\n if (typeof init.model !== 'string' || init.model.length === 0) {\n throw new TypeError('model must be a non-empty string.');\n }\n this.model = init.model;\n }\n if (init?.messages !== undefined) {\n validateMessages(init.messages);\n this.messages = [...init.messages];\n }\n if (init?.system !== undefined) {\n if (typeof init.system !== 'string') throw new TypeError('system must be a string.');\n this.messages.unshift({ role: 'system', content: init.system });\n }\n }\n\n setModel(model: string, search = false): this {\n if (typeof model !== 'string' || model.length === 0) {\n throw new TypeError('model must be a non-empty string.');\n }\n this.model = model;\n this.useSearch = search;\n return this;\n }\n\n setMessages(messages: Message[]): this {\n validateMessages(messages);\n this.messages = [...messages];\n return this;\n }\n\n addMessage(message: Message): this {\n validateMessage(message);\n this.messages.push(message);\n return this;\n }\n\n removeMessage(index: number): this {\n if (!Number.isInteger(index)) throw new TypeError('index must be an integer.');\n if (index < 0 || index >= this.messages.length) {\n throw new RangeError(`index out of bounds. Valid range: 0 to ${this.messages.length - 1}.`);\n }\n this.messages.splice(index, 1);\n return this;\n }\n\n clearMessages(): this {\n this.messages = [];\n return this;\n }\n\n setSystem(content: string): this {\n if (typeof content !== 'string') throw new TypeError('content must be a string.');\n const idx = this.messages.findIndex((m) => m.role === 'system');\n if (idx !== -1) {\n this.messages.splice(idx, 1);\n }\n this.messages.unshift({ role: 'system', content });\n return this;\n }\n\n // --- Conversation management ---\n\n useConversation(id: string | null): this {\n if (id !== null && (typeof id !== 'string' || id.length === 0)) {\n throw new TypeError('id must be a non-empty string.');\n }\n this.activeConversationId = id;\n if (id !== null && !this.conversationMap.has(id)) {\n this.conversationMap.set(id, []);\n }\n return this;\n }\n\n listConversations(): string[] {\n return [...this.conversationMap.keys()];\n }\n\n getConversationMessages(id: string): Message[] {\n return [...(this.conversationMap.get(id) ?? [])];\n }\n\n clearConversation(id: string): this {\n if (this.conversationMap.has(id)) {\n this.conversationMap.set(id, []);\n }\n return this;\n }\n\n deleteConversation(id: string): this {\n this.conversationMap.delete(id);\n return this;\n }\n\n exportConversations(id: string): Message[];\n exportConversations(): ConversationStore;\n exportConversations(id?: string): ConversationStore | Message[] {\n if (id !== undefined) {\n return [...(this.conversationMap.get(id) ?? [])];\n }\n const result: ConversationStore = {};\n for (const [key, messages] of this.conversationMap) {\n result[key] = [...messages];\n }\n return result;\n }\n\n importConversations(data: ConversationStore, replace = false): this {\n if (replace) this.conversationMap.clear();\n for (const [id, messages] of Object.entries(data)) {\n validateMessages(messages);\n this.conversationMap.set(id, [...messages]);\n }\n return this;\n }\n\n // --- Core ---\n\n async generate(raw: true): Promise<CompletionResponse>;\n async generate(raw?: false): Promise<string>;\n async generate(raw?: boolean): Promise<string | CompletionResponse> {\n if (!this.model) throw new Error('model is not set. Call setModel() first.');\n if (this.messages.length === 0) {\n throw new Error('messages are empty. Call addMessage() or setMessages() first.');\n }\n const model = this.useSearch ? ((await searchModels(this.model))[0] ?? this.model) : this.model;\n if (raw) return generate(model, this.messages, true);\n return generate(model, this.messages);\n }\n\n async ask(prompt: string, conversationId?: string): Promise<string> {\n if (typeof prompt !== 'string') throw new TypeError('prompt must be a string.');\n if (!this.model) throw new Error('model is not set. Call setModel() first.');\n\n const id = conversationId ?? this.activeConversationId;\n const model = this.useSearch ? ((await searchModels(this.model))[0] ?? this.model) : this.model;\n\n if (id !== null) {\n if (!this.conversationMap.has(id)) this.conversationMap.set(id, []);\n const history = this.conversationMap.get(id)!;\n history.push({ role: 'user', content: prompt });\n const reply = await generate(model, [...this.messages, ...history]);\n history.push({ role: 'assistant', content: reply });\n return reply;\n }\n\n // Stateless: use static context + prompt, save nothing\n return generate(model, [...this.messages, { role: 'user', content: prompt }]);\n }\n\n async *stream(prompt: string, conversationId?: string): AsyncGenerator<string> {\n if (typeof prompt !== 'string') throw new TypeError('prompt must be a string.');\n if (!this.model) throw new Error('model is not set. Call setModel() first.');\n\n const id = conversationId ?? this.activeConversationId;\n const model = this.useSearch ? ((await searchModels(this.model))[0] ?? this.model) : this.model;\n\n if (id !== null) {\n if (!this.conversationMap.has(id)) this.conversationMap.set(id, []);\n const history = this.conversationMap.get(id)!;\n history.push({ role: 'user', content: prompt });\n let fullReply = '';\n for await (const chunk of streamRequest(model, [...this.messages, ...history])) {\n fullReply += chunk;\n yield chunk;\n }\n history.push({ role: 'assistant', content: fullReply });\n } else {\n // Stateless: use static context + prompt, save nothing\n for await (const chunk of streamRequest(model, [\n ...this.messages,\n { role: 'user', content: prompt },\n ])) {\n yield chunk;\n }\n }\n }\n\n getFormat(): { model: string; messages: Message[] } {\n return { model: this.model, messages: [...this.messages] };\n }\n}\n"],"mappings":"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"}