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/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 };