algochat-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +698 -0
- package/examples/advanced.js +71 -0
- package/examples/basic.js +35 -0
- package/examples/multimodal.js +36 -0
- package/examples/streaming.js +26 -0
- package/examples/whatsapp-baileys.js +190 -0
- package/package.json +41 -0
- package/src/client.js +380 -0
- package/src/index.d.ts +144 -0
- package/src/index.js +254 -0
- package/src/models.js +87 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* algochat-ai — Public API
|
|
5
|
+
*
|
|
6
|
+
* This is the main entry point for the algochat-ai npm package.
|
|
7
|
+
* It exposes a clean, high-level API on top of the AlgoChatClient.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const algochat = require('algochat-ai');
|
|
11
|
+
*
|
|
12
|
+
* // Simple one-shot chat (string input)
|
|
13
|
+
* const reply = await algochat.chat('What is 2+2?');
|
|
14
|
+
*
|
|
15
|
+
* // Full messages array (OpenAI style)
|
|
16
|
+
* const reply = await algochat.chat([
|
|
17
|
+
* { role: 'system', content: 'You are a helpful assistant.' },
|
|
18
|
+
* { role: 'user', content: 'What is 2+2?' },
|
|
19
|
+
* ]);
|
|
20
|
+
*
|
|
21
|
+
* // Streaming
|
|
22
|
+
* await algochat.chatStream('Tell me a joke', (chunk) => process.stdout.write(chunk));
|
|
23
|
+
*
|
|
24
|
+
* // Custom instance with options
|
|
25
|
+
* const client = new algochat.AlgoChatClient({ debug: true, timeout: 60000 });
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const AlgoChatClient = require('./client');
|
|
29
|
+
const { resolveModel, getModelList, ALGOCHAT_MODELS, DEFAULT_MODEL } = require('./models');
|
|
30
|
+
const { v4: uuidv4 } = require('uuid');
|
|
31
|
+
|
|
32
|
+
// ─── Singleton client (shared across simple helper calls) ─────────────────────
|
|
33
|
+
let _defaultClient = null;
|
|
34
|
+
function _getDefaultClient(options) {
|
|
35
|
+
if (!_defaultClient) _defaultClient = new AlgoChatClient(options || {});
|
|
36
|
+
return _defaultClient;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── High-level helpers ───────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Send a message and get the full AI response as a string.
|
|
43
|
+
*
|
|
44
|
+
* @param {string|Array} input
|
|
45
|
+
* - string: a single user message
|
|
46
|
+
* - Array: full OpenAI-style messages array [{role, content}, ...]
|
|
47
|
+
* @param {object} [options]
|
|
48
|
+
* @param {string} [options.model] - Model ID (default: gemini-3-flash-preview)
|
|
49
|
+
* @param {string} [options.systemPrompt] - Override the default system prompt
|
|
50
|
+
* @param {boolean} [options.debug] - Enable verbose logging
|
|
51
|
+
* @param {number} [options.timeout] - Request timeout in ms (default: 90000)
|
|
52
|
+
* @returns {Promise<string>} - The AI's response text
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const reply = await algochat.chat('Hello!');
|
|
56
|
+
* console.log(reply);
|
|
57
|
+
*/
|
|
58
|
+
async function chat(input, options = {}) {
|
|
59
|
+
const client = _getDefaultClient(options);
|
|
60
|
+
const { model: requestedModel, systemPrompt } = options;
|
|
61
|
+
|
|
62
|
+
let messages;
|
|
63
|
+
let extractedSystemPrompt = systemPrompt;
|
|
64
|
+
|
|
65
|
+
if (typeof input === 'string') {
|
|
66
|
+
messages = [{ role: 'user', content: input }];
|
|
67
|
+
} else if (Array.isArray(input)) {
|
|
68
|
+
// Extract system message(s) from the array
|
|
69
|
+
messages = input.filter((m) => {
|
|
70
|
+
if (m.role === 'system') {
|
|
71
|
+
extractedSystemPrompt = extractedSystemPrompt || m.content;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
throw new TypeError('input must be a string or an array of messages');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (messages.length === 0) {
|
|
81
|
+
throw new Error('No user messages provided');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const resolvedModel = resolveModel(requestedModel);
|
|
85
|
+
const { stream } = await client.createChatStream({
|
|
86
|
+
model: resolvedModel,
|
|
87
|
+
messages,
|
|
88
|
+
systemPrompt: extractedSystemPrompt,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return client.streamToText(stream);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Stream the AI response chunk by chunk.
|
|
96
|
+
*
|
|
97
|
+
* @param {string|Array} input - Same as `chat()`
|
|
98
|
+
* @param {Function} onChunk - Called with each text delta (string)
|
|
99
|
+
* @param {object} [options] - Same as `chat()`
|
|
100
|
+
* @returns {Promise<string>} - The full accumulated response text
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* await algochat.chatStream('Tell me a story', (chunk) => {
|
|
104
|
+
* process.stdout.write(chunk);
|
|
105
|
+
* });
|
|
106
|
+
*/
|
|
107
|
+
async function chatStream(input, onChunk, options = {}) {
|
|
108
|
+
if (typeof onChunk !== 'function') throw new TypeError('onChunk must be a function');
|
|
109
|
+
|
|
110
|
+
const client = _getDefaultClient(options);
|
|
111
|
+
const { model: requestedModel, systemPrompt } = options;
|
|
112
|
+
|
|
113
|
+
let messages;
|
|
114
|
+
let extractedSystemPrompt = systemPrompt;
|
|
115
|
+
|
|
116
|
+
if (typeof input === 'string') {
|
|
117
|
+
messages = [{ role: 'user', content: input }];
|
|
118
|
+
} else if (Array.isArray(input)) {
|
|
119
|
+
messages = input.filter((m) => {
|
|
120
|
+
if (m.role === 'system') {
|
|
121
|
+
extractedSystemPrompt = extractedSystemPrompt || m.content;
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
throw new TypeError('input must be a string or an array of messages');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const resolvedModel = resolveModel(requestedModel);
|
|
131
|
+
const { stream } = await client.createChatStream({
|
|
132
|
+
model: resolvedModel,
|
|
133
|
+
messages,
|
|
134
|
+
systemPrompt: extractedSystemPrompt,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
let fullText = '';
|
|
139
|
+
let buffer = '';
|
|
140
|
+
|
|
141
|
+
stream.on('data', (chunk) => {
|
|
142
|
+
buffer += chunk.toString();
|
|
143
|
+
const lines = buffer.split('\n');
|
|
144
|
+
buffer = lines.pop();
|
|
145
|
+
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const trimmed = line.trim();
|
|
148
|
+
if (!trimmed.startsWith('data:')) continue;
|
|
149
|
+
const jsonStr = trimmed.slice(5).trim();
|
|
150
|
+
if (!jsonStr || jsonStr === '[DONE]') continue;
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(jsonStr);
|
|
153
|
+
if (parsed.type === 'text-delta' && parsed.delta) {
|
|
154
|
+
fullText += parsed.delta;
|
|
155
|
+
onChunk(parsed.delta);
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// skip
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
stream.on('end', () => resolve(fullText));
|
|
164
|
+
stream.on('error', reject);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create an OpenAI-compatible chat completion object (non-streaming).
|
|
170
|
+
* Useful when you need the same response shape as openai's SDK.
|
|
171
|
+
*
|
|
172
|
+
* @param {Array} messages - OpenAI-style messages
|
|
173
|
+
* @param {object} [options]
|
|
174
|
+
* @param {string} [options.model]
|
|
175
|
+
* @param {string} [options.systemPrompt]
|
|
176
|
+
* @returns {Promise<object>} - OpenAI-compatible completion object
|
|
177
|
+
*/
|
|
178
|
+
async function createCompletion(messages, options = {}) {
|
|
179
|
+
const text = await chat(messages, options);
|
|
180
|
+
const resolvedModel = resolveModel(options.model);
|
|
181
|
+
const now = Math.floor(Date.now() / 1000);
|
|
182
|
+
const id = `chatcmpl-${uuidv4().replace(/-/g, '').slice(0, 24)}`;
|
|
183
|
+
const promptTokens = Math.ceil(JSON.stringify(messages).length / 4);
|
|
184
|
+
const completionTokens = Math.ceil(text.length / 4);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
id,
|
|
188
|
+
object: 'chat.completion',
|
|
189
|
+
created: now,
|
|
190
|
+
model: resolvedModel,
|
|
191
|
+
choices: [
|
|
192
|
+
{
|
|
193
|
+
index: 0,
|
|
194
|
+
message: { role: 'assistant', content: text },
|
|
195
|
+
finish_reason: 'stop',
|
|
196
|
+
logprobs: null,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
usage: {
|
|
200
|
+
prompt_tokens: promptTokens,
|
|
201
|
+
completion_tokens: completionTokens,
|
|
202
|
+
total_tokens: promptTokens + completionTokens,
|
|
203
|
+
},
|
|
204
|
+
system_fingerprint: null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Send a message with an image (multimodal).
|
|
210
|
+
*
|
|
211
|
+
* @param {string} text - The text question about the image
|
|
212
|
+
* @param {string} imageUrl - An https URL or base64 data URI (data:image/...)
|
|
213
|
+
* @param {object} [options]
|
|
214
|
+
* @returns {Promise<string>} - The AI response
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* const desc = await algochat.chatWithImage(
|
|
218
|
+
* 'What is in this image?',
|
|
219
|
+
* 'https://example.com/photo.jpg'
|
|
220
|
+
* );
|
|
221
|
+
*/
|
|
222
|
+
async function chatWithImage(text, imageUrl, options = {}) {
|
|
223
|
+
return chat(
|
|
224
|
+
[
|
|
225
|
+
{
|
|
226
|
+
role: 'user',
|
|
227
|
+
content: [
|
|
228
|
+
{ type: 'text', text },
|
|
229
|
+
{ type: 'image_url', image_url: { url: imageUrl } },
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
options
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Module exports ───────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
// High-level helpers
|
|
241
|
+
chat,
|
|
242
|
+
chatStream,
|
|
243
|
+
chatWithImage,
|
|
244
|
+
createCompletion,
|
|
245
|
+
|
|
246
|
+
// Factory for custom instances
|
|
247
|
+
AlgoChatClient,
|
|
248
|
+
|
|
249
|
+
// Model helpers
|
|
250
|
+
resolveModel,
|
|
251
|
+
getModelList,
|
|
252
|
+
ALGOCHAT_MODELS,
|
|
253
|
+
DEFAULT_MODEL,
|
|
254
|
+
};
|
package/src/models.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Model definitions and resolution helpers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ALGOCHAT_MODELS = {
|
|
8
|
+
'gemini-3-flash-preview': {
|
|
9
|
+
id: 'gemini-3-flash-preview',
|
|
10
|
+
displayName: 'Gemini 3 Flash Preview',
|
|
11
|
+
contextWindow: 128000,
|
|
12
|
+
maxOutputTokens: 8192,
|
|
13
|
+
requiresAuth: false, // Free — works without an AlgoChat account
|
|
14
|
+
},
|
|
15
|
+
'gemini-2.5-pro': {
|
|
16
|
+
id: 'gemini-2.5-pro',
|
|
17
|
+
displayName: 'Gemini 2.5 Pro',
|
|
18
|
+
contextWindow: 200000,
|
|
19
|
+
maxOutputTokens: 8192,
|
|
20
|
+
requiresAuth: true, // Needs a logged-in AlgoChat session
|
|
21
|
+
},
|
|
22
|
+
'gemini-2.0-flash': {
|
|
23
|
+
id: 'gemini-2.0-flash',
|
|
24
|
+
displayName: 'Gemini 2.0 Flash',
|
|
25
|
+
contextWindow: 128000,
|
|
26
|
+
maxOutputTokens: 8192,
|
|
27
|
+
requiresAuth: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** OpenAI model aliases → AlgoChat model IDs */
|
|
32
|
+
const MODEL_ALIAS_MAP = {
|
|
33
|
+
'gpt-4o': 'gemini-3-flash-preview',
|
|
34
|
+
'gpt-4': 'gemini-3-flash-preview',
|
|
35
|
+
'gpt-3.5-turbo': 'gemini-3-flash-preview',
|
|
36
|
+
'gpt-4-turbo': 'gemini-3-flash-preview',
|
|
37
|
+
'gpt-4o-mini': 'gemini-3-flash-preview',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const DEFAULT_MODEL = 'gemini-3-flash-preview';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve any model name (OpenAI alias or AlgoChat native) to an AlgoChat model ID.
|
|
44
|
+
* Falls back to the default free model if unrecognised.
|
|
45
|
+
* @param {string} requestedModel
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function resolveModel(requestedModel) {
|
|
49
|
+
if (!requestedModel) return DEFAULT_MODEL;
|
|
50
|
+
if (ALGOCHAT_MODELS[requestedModel]) return requestedModel;
|
|
51
|
+
if (MODEL_ALIAS_MAP[requestedModel]) return MODEL_ALIAS_MAP[requestedModel];
|
|
52
|
+
return DEFAULT_MODEL;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return all available models (native + aliases) in OpenAI /v1/models format.
|
|
57
|
+
* @returns {Array}
|
|
58
|
+
*/
|
|
59
|
+
function getModelList() {
|
|
60
|
+
const now = Math.floor(Date.now() / 1000);
|
|
61
|
+
|
|
62
|
+
const models = Object.values(ALGOCHAT_MODELS).map((m) => ({
|
|
63
|
+
id: m.id,
|
|
64
|
+
object: 'model',
|
|
65
|
+
created: now,
|
|
66
|
+
owned_by: 'algochat',
|
|
67
|
+
permission: [],
|
|
68
|
+
root: m.id,
|
|
69
|
+
parent: null,
|
|
70
|
+
requiresAuth: m.requiresAuth,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
const aliases = Object.entries(MODEL_ALIAS_MAP).map(([alias, target]) => ({
|
|
74
|
+
id: alias,
|
|
75
|
+
object: 'model',
|
|
76
|
+
created: now,
|
|
77
|
+
owned_by: 'algochat-alias',
|
|
78
|
+
permission: [],
|
|
79
|
+
root: target,
|
|
80
|
+
parent: null,
|
|
81
|
+
requiresAuth: false,
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
return [...models, ...aliases];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { ALGOCHAT_MODELS, MODEL_ALIAS_MAP, DEFAULT_MODEL, resolveModel, getModelList };
|