legiontret 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/index.d.ts +76 -0
- package/index.js +388 -0
- package/package.json +34 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LegionTret TypeScript declarations
|
|
3
|
+
*/
|
|
4
|
+
declare module 'legiontret' {
|
|
5
|
+
interface ClientOptions {
|
|
6
|
+
host?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface GenerateOptions {
|
|
12
|
+
system?: string;
|
|
13
|
+
stream?: boolean;
|
|
14
|
+
options?: GenerateParams;
|
|
15
|
+
format?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface GenerateParams {
|
|
19
|
+
num_predict?: number;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
top_p?: number;
|
|
22
|
+
top_k?: number;
|
|
23
|
+
repeat_penalty?: number;
|
|
24
|
+
seed?: number;
|
|
25
|
+
stop?: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ChatMessage {
|
|
29
|
+
role: 'system' | 'user' | 'assistant';
|
|
30
|
+
content: string;
|
|
31
|
+
images?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ModelInfo {
|
|
35
|
+
name: string;
|
|
36
|
+
model: string;
|
|
37
|
+
modified_at: string;
|
|
38
|
+
size: number;
|
|
39
|
+
digest: string;
|
|
40
|
+
details: {
|
|
41
|
+
format: string;
|
|
42
|
+
family: string;
|
|
43
|
+
families: string[];
|
|
44
|
+
parameter_size: string;
|
|
45
|
+
quantization_level: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class LegionTretError extends Error {
|
|
50
|
+
statusCode?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class ConnectionError extends LegionTretError {}
|
|
54
|
+
class ModelNotFoundError extends LegionTretError {}
|
|
55
|
+
|
|
56
|
+
class Client {
|
|
57
|
+
constructor(options?: ClientOptions);
|
|
58
|
+
listModels(): Promise<ModelInfo[]>;
|
|
59
|
+
showModel(name: string): Promise<any>;
|
|
60
|
+
deleteModel(name: string): Promise<any>;
|
|
61
|
+
generate(model: string, prompt: string, options?: GenerateOptions): Promise<any>;
|
|
62
|
+
generateStream(model: string, prompt: string, options?: GenerateOptions): AsyncGenerator<string>;
|
|
63
|
+
chat(model: string, messages: ChatMessage[], options?: any): Promise<any>;
|
|
64
|
+
chatStream(model: string, messages: ChatMessage[], options?: any): AsyncGenerator<string>;
|
|
65
|
+
embeddings(model: string, prompt: string): Promise<any>;
|
|
66
|
+
openaiModels(): Promise<any>;
|
|
67
|
+
openaiChat(model: string, messages: ChatMessage[], options?: any): Promise<any>;
|
|
68
|
+
isRunning(): Promise<boolean>;
|
|
69
|
+
version(): Promise<string>;
|
|
70
|
+
systemInfo(): Promise<any>;
|
|
71
|
+
search(query: string): Promise<any[]>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { Client, LegionTretError, ConnectionError, ModelNotFoundError };
|
|
75
|
+
export default Client;
|
|
76
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LegionTret JavaScript Client
|
|
3
|
+
* A simple JavaScript/TypeScript library for interacting with the LegionTret API.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { Client } from 'legiontret';
|
|
7
|
+
*
|
|
8
|
+
* const client = new Client();
|
|
9
|
+
*
|
|
10
|
+
* // Generate text
|
|
11
|
+
* const response = await client.generate('llama3', 'Why is the sky blue?');
|
|
12
|
+
* console.log(response);
|
|
13
|
+
*
|
|
14
|
+
* // Chat
|
|
15
|
+
* const response = await client.chat('llama3', [
|
|
16
|
+
* { role: 'user', content: 'Hello!' }
|
|
17
|
+
* ]);
|
|
18
|
+
* console.log(response);
|
|
19
|
+
*
|
|
20
|
+
* // List models
|
|
21
|
+
* const models = await client.listModels();
|
|
22
|
+
* models.forEach(m => console.log(m.name));
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
26
|
+
const DEFAULT_PORT = 11434;
|
|
27
|
+
const DEFAULT_TIMEOUT = 300000; // 5 minutes
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Custom error types for LegionTret
|
|
31
|
+
*/
|
|
32
|
+
class LegionTretError extends Error {
|
|
33
|
+
constructor(message, statusCode) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'LegionTretError';
|
|
36
|
+
this.statusCode = statusCode;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class ConnectionError extends LegionTretError {
|
|
41
|
+
constructor(host, port) {
|
|
42
|
+
super(`Cannot connect to LegionTret at ${host}:${port}. Make sure the server is running: legiontret serve`);
|
|
43
|
+
this.name = 'ConnectionError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class ModelNotFoundError extends LegionTretError {
|
|
48
|
+
constructor(modelName) {
|
|
49
|
+
super(`Model not found: ${modelName}`);
|
|
50
|
+
this.name = 'ModelNotFoundError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* LegionTret API Client
|
|
56
|
+
*/
|
|
57
|
+
class Client {
|
|
58
|
+
/**
|
|
59
|
+
* Create a new LegionTret client.
|
|
60
|
+
* @param {Object} options - Client options
|
|
61
|
+
* @param {string} [options.host='127.0.0.1'] - Host address
|
|
62
|
+
* @param {number} [options.port=11434] - Port number
|
|
63
|
+
* @param {number} [options.timeout=300000] - Request timeout in ms
|
|
64
|
+
*/
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
this.host = options.host || DEFAULT_HOST;
|
|
67
|
+
this.port = options.port || DEFAULT_PORT;
|
|
68
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
69
|
+
this.baseUrl = `http://${this.host}:${this.port}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build a full URL from a path
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
_url(path) {
|
|
77
|
+
return `${this.baseUrl}${path}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Make a request to the API
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
async _request(method, path, body = null, { stream = false } = {}) {
|
|
85
|
+
const options = {
|
|
86
|
+
method,
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
88
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (body && method !== 'GET') {
|
|
92
|
+
options.body = JSON.stringify(body);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(this._url(path), options);
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
if (response.status === 404) {
|
|
100
|
+
throw new ModelNotFoundError(body?.model || 'unknown');
|
|
101
|
+
}
|
|
102
|
+
const errorText = await response.text();
|
|
103
|
+
throw new LegionTretError(`HTTP ${response.status}: ${errorText}`, response.status);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return response;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
|
109
|
+
throw new ConnectionError(this.host, this.port);
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Model Management ──────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* List all locally available models.
|
|
119
|
+
* @returns {Promise<Array>} List of model objects
|
|
120
|
+
*/
|
|
121
|
+
async listModels() {
|
|
122
|
+
const response = await this._request('GET', '/api/tags');
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
return data.models || [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Show details for a specific model.
|
|
129
|
+
* @param {string} name - Model name
|
|
130
|
+
* @returns {Promise<Object>} Model details
|
|
131
|
+
*/
|
|
132
|
+
async showModel(name) {
|
|
133
|
+
const response = await this._request('POST', '/api/show', { name });
|
|
134
|
+
return response.json();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Delete a local model.
|
|
139
|
+
* @param {string} name - Model name to delete
|
|
140
|
+
* @returns {Promise<Object>} Response
|
|
141
|
+
*/
|
|
142
|
+
async deleteModel(name) {
|
|
143
|
+
const response = await this._request('DELETE', '/api/delete', { name });
|
|
144
|
+
return response.json();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Generation ────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate text from a prompt.
|
|
151
|
+
* @param {string} model - Model name
|
|
152
|
+
* @param {string} prompt - Text prompt
|
|
153
|
+
* @param {Object} [options] - Generation options
|
|
154
|
+
* @param {string} [options.system] - System prompt
|
|
155
|
+
* @param {boolean} [options.stream=false] - Stream the response
|
|
156
|
+
* @param {Object} [options.options] - Generation parameters
|
|
157
|
+
* @param {string} [options.format] - Output format
|
|
158
|
+
* @returns {Promise<Object|AsyncGenerator>} Response or stream
|
|
159
|
+
*/
|
|
160
|
+
async generate(model, prompt, options = {}) {
|
|
161
|
+
const body = {
|
|
162
|
+
model,
|
|
163
|
+
prompt,
|
|
164
|
+
stream: options.stream || false,
|
|
165
|
+
};
|
|
166
|
+
if (options.system) body.system = options.system;
|
|
167
|
+
if (options.options) body.options = options.options;
|
|
168
|
+
if (options.format) body.format = options.format;
|
|
169
|
+
|
|
170
|
+
const response = await this._request('POST', '/api/generate', body, {
|
|
171
|
+
stream: options.stream,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (options.stream) {
|
|
175
|
+
return this._parseStream(response);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return response.json();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generate text with streaming (async generator).
|
|
183
|
+
* @param {string} model - Model name
|
|
184
|
+
* @param {string} prompt - Text prompt
|
|
185
|
+
* @param {Object} [options] - Generation options
|
|
186
|
+
* @yields {string} Text chunks
|
|
187
|
+
*/
|
|
188
|
+
async *generateStream(model, prompt, options = {}) {
|
|
189
|
+
const body = {
|
|
190
|
+
model,
|
|
191
|
+
prompt,
|
|
192
|
+
stream: true,
|
|
193
|
+
};
|
|
194
|
+
if (options.system) body.system = options.system;
|
|
195
|
+
if (options.options) body.options = options.options;
|
|
196
|
+
|
|
197
|
+
const response = await this._request('POST', '/api/generate', body, { stream: true });
|
|
198
|
+
yield* this._parseTextStream(response);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── Chat ──────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Send a chat completion request.
|
|
205
|
+
* @param {string} model - Model name
|
|
206
|
+
* @param {Array<Object>} messages - Chat messages
|
|
207
|
+
* @param {Object} [options] - Generation options
|
|
208
|
+
* @returns {Promise<Object|AsyncGenerator>} Response or stream
|
|
209
|
+
*/
|
|
210
|
+
async chat(model, messages, options = {}) {
|
|
211
|
+
const body = {
|
|
212
|
+
model,
|
|
213
|
+
messages,
|
|
214
|
+
stream: options.stream || false,
|
|
215
|
+
};
|
|
216
|
+
if (options.options) body.options = options.options;
|
|
217
|
+
if (options.format) body.format = options.format;
|
|
218
|
+
|
|
219
|
+
const response = await this._request('POST', '/api/chat', body, {
|
|
220
|
+
stream: options.stream,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (options.stream) {
|
|
224
|
+
return this._parseStream(response);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return response.json();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Chat with streaming (async generator).
|
|
232
|
+
* @param {string} model - Model name
|
|
233
|
+
* @param {Array<Object>} messages - Chat messages
|
|
234
|
+
* @param {Object} [options] - Generation options
|
|
235
|
+
* @yields {string} Text chunks
|
|
236
|
+
*/
|
|
237
|
+
async *chatStream(model, messages, options = {}) {
|
|
238
|
+
const body = {
|
|
239
|
+
model,
|
|
240
|
+
messages,
|
|
241
|
+
stream: true,
|
|
242
|
+
};
|
|
243
|
+
if (options.options) body.options = options.options;
|
|
244
|
+
|
|
245
|
+
const response = await this._request('POST', '/api/chat', body, { stream: true });
|
|
246
|
+
yield* this._parseTextStream(response);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ─── Embeddings ────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Generate embeddings for a prompt.
|
|
253
|
+
* @param {string} model - Model name
|
|
254
|
+
* @param {string} prompt - Text to embed
|
|
255
|
+
* @returns {Promise<Object>} Embedding response
|
|
256
|
+
*/
|
|
257
|
+
async embeddings(model, prompt) {
|
|
258
|
+
const response = await this._request('POST', '/api/embeddings', { model, prompt });
|
|
259
|
+
return response.json();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ─── OpenAI-Compatible ─────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* List models using OpenAI-compatible endpoint.
|
|
266
|
+
* @returns {Promise<Object>} Model list
|
|
267
|
+
*/
|
|
268
|
+
async openaiModels() {
|
|
269
|
+
const response = await this._request('GET', '/api/v1/models');
|
|
270
|
+
return response.json();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Chat completion using OpenAI-compatible endpoint.
|
|
275
|
+
* @param {string} model - Model name
|
|
276
|
+
* @param {Array<Object>} messages - Messages
|
|
277
|
+
* @param {Object} [options] - Options
|
|
278
|
+
* @returns {Promise<Object>} Chat completion
|
|
279
|
+
*/
|
|
280
|
+
async openaiChat(model, messages, options = {}) {
|
|
281
|
+
const body = {
|
|
282
|
+
model,
|
|
283
|
+
messages,
|
|
284
|
+
temperature: options.temperature || 0.7,
|
|
285
|
+
max_tokens: options.maxTokens || 2048,
|
|
286
|
+
stream: options.stream || false,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const response = await this._request('POST', '/api/v1/chat/completions', body, {
|
|
290
|
+
stream: options.stream,
|
|
291
|
+
});
|
|
292
|
+
return response.json();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── System ────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Check if the LegionTret server is running.
|
|
299
|
+
* @returns {Promise<boolean>}
|
|
300
|
+
*/
|
|
301
|
+
async isRunning() {
|
|
302
|
+
try {
|
|
303
|
+
await this._request('GET', '/health');
|
|
304
|
+
return true;
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get the server version.
|
|
312
|
+
* @returns {Promise<string>}
|
|
313
|
+
*/
|
|
314
|
+
async version() {
|
|
315
|
+
const response = await this._request('GET', '/api/version');
|
|
316
|
+
const data = await response.json();
|
|
317
|
+
return data.version || 'unknown';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get system information.
|
|
322
|
+
* @returns {Promise<Object>}
|
|
323
|
+
*/
|
|
324
|
+
async systemInfo() {
|
|
325
|
+
const response = await this._request('GET', '/api/system');
|
|
326
|
+
return response.json();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Search for models in the registry.
|
|
331
|
+
* @param {string} query - Search query
|
|
332
|
+
* @returns {Promise<Array>} Matching models
|
|
333
|
+
*/
|
|
334
|
+
async search(query) {
|
|
335
|
+
const response = await this._request('GET', `/api/search?q=${encodeURIComponent(query)}`);
|
|
336
|
+
const data = await response.json();
|
|
337
|
+
return data.models || [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ─── Private Helpers ───────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Parse a streaming response as JSON chunks
|
|
344
|
+
* @private
|
|
345
|
+
*/
|
|
346
|
+
async *_parseStream(response) {
|
|
347
|
+
const reader = response.body.getReader();
|
|
348
|
+
const decoder = new TextDecoder();
|
|
349
|
+
let buffer = '';
|
|
350
|
+
|
|
351
|
+
while (true) {
|
|
352
|
+
const { done, value } = await reader.read();
|
|
353
|
+
if (done) break;
|
|
354
|
+
|
|
355
|
+
buffer += decoder.decode(value, { stream: true });
|
|
356
|
+
const lines = buffer.split('\n');
|
|
357
|
+
buffer = lines.pop() || '';
|
|
358
|
+
|
|
359
|
+
for (const line of lines) {
|
|
360
|
+
if (line.trim()) {
|
|
361
|
+
try {
|
|
362
|
+
yield JSON.parse(line);
|
|
363
|
+
} catch {
|
|
364
|
+
// Skip malformed lines
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Parse a streaming response and yield text content
|
|
373
|
+
* @private
|
|
374
|
+
*/
|
|
375
|
+
async *_parseTextStream(response) {
|
|
376
|
+
for await (const chunk of this._parseStream(response)) {
|
|
377
|
+
if (chunk.response) {
|
|
378
|
+
yield chunk.response;
|
|
379
|
+
} else if (chunk.message?.content) {
|
|
380
|
+
yield chunk.message.content;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Export
|
|
387
|
+
module.exports = { Client, LegionTretError, ConnectionError, ModelNotFoundError };
|
|
388
|
+
module.exports.default = Client;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "legiontret",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JavaScript client for LegionTret - Run LLMs locally",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"llm",
|
|
12
|
+
"ai",
|
|
13
|
+
"legiontret",
|
|
14
|
+
"ollama",
|
|
15
|
+
"gemma",
|
|
16
|
+
"llama",
|
|
17
|
+
"mistral",
|
|
18
|
+
"local-ai"
|
|
19
|
+
],
|
|
20
|
+
"author": "Death Legion Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/deathlegionteam/legiontret.git"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16.0.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"index.js",
|
|
31
|
+
"index.d.ts",
|
|
32
|
+
"README.md"
|
|
33
|
+
]
|
|
34
|
+
}
|