total5 0.0.17-5 → 0.0.17-7

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/aimodel.js CHANGED
@@ -12,6 +12,7 @@ function AI(model) {
12
12
  t.payload = {};
13
13
  t.payload.model = model;
14
14
  t.payload.messages = [];
15
+ t.payload.tools = [];
15
16
  }
16
17
 
17
18
  // Enables think mode
@@ -21,6 +22,10 @@ AI.prototype.think = function () {
21
22
  return t;
22
23
  };
23
24
 
25
+ AI.prototype.parser = function(provider) {
26
+ return parseAIStream(provider);
27
+ };
28
+
24
29
  // Internal function
25
30
  // Appends content message
26
31
  AI.prototype.message = function(role, content, merge) {
@@ -78,8 +83,9 @@ AI.prototype.assistant = function(content, merge) {
78
83
  return this.message('assistant', content, merge);
79
84
  };
80
85
 
81
- AI.prototype.tool = function(content, merge) {
82
- return this.message('tool', content, merge);
86
+ AI.prototype.tool = function(content) {
87
+ this.payload.tools.push(content);
88
+ return this;
83
89
  };
84
90
 
85
91
  AI.prototype.promise = function($) {
@@ -130,6 +136,48 @@ AI.prototype.run = function() {
130
136
  return t;
131
137
  };
132
138
 
139
+ function parseJSON(value) {
140
+ if (!value)
141
+ return null;
142
+
143
+ if (typeof value === 'object')
144
+ return value;
145
+
146
+ try {
147
+ return JSON.parse(value);
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ function normalize(value) {
154
+
155
+ if (value == null)
156
+ return {};
157
+
158
+ if (typeof(value) === 'object')
159
+ return value;
160
+
161
+ if (typeof(value) === 'string') {
162
+ try {
163
+ return JSON.parse(value);
164
+ } catch {
165
+ return { _raw: value };
166
+ }
167
+ }
168
+
169
+ return { _raw: value };
170
+ }
171
+
172
+ function createTool(id, name) {
173
+ return {
174
+ id: id || null,
175
+ name: name || null,
176
+ arguments: '',
177
+ input: null
178
+ };
179
+ }
180
+
133
181
  exports.exec = function(model) {
134
182
  return new AI(model);
135
183
  };
@@ -145,3 +193,443 @@ exports.newai = function(model, config, callback) {
145
193
  for (const m of models)
146
194
  F.aimodels[m] = { config, callback };
147
195
  };
196
+
197
+ function parseJSON(value) {
198
+ if (!value)
199
+ return null;
200
+
201
+ if (typeof value === 'object')
202
+ return value;
203
+
204
+ try {
205
+ return JSON.parse(value);
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+
211
+ function normalize(value) {
212
+ if (value == null)
213
+ return {};
214
+
215
+ if (typeof value === 'object')
216
+ return value;
217
+
218
+ if (typeof value === 'string') {
219
+ try {
220
+ return JSON.parse(value);
221
+ } catch {
222
+ return { _raw: value };
223
+ }
224
+ }
225
+
226
+ return { _raw: value };
227
+ }
228
+
229
+ function createTool(id, name) {
230
+ return {
231
+ id: id || null,
232
+ name: name || null,
233
+ arguments: '',
234
+ input: null
235
+ };
236
+ }
237
+
238
+ class AIStreamParser {
239
+
240
+ constructor(provider) {
241
+ this.provider = provider || 'generic';
242
+ this.content = '';
243
+ this.tools = [];
244
+ this.reasoning = 'content';
245
+ this.buffer = Buffer.alloc(0);
246
+ this.toolmap = Object.create(null);
247
+ }
248
+
249
+ write(chunk) {
250
+ if (chunk == null)
251
+ return this.output();
252
+
253
+ if (chunk instanceof Buffer || typeof(chunk) === 'string' || chunk instanceof Uint8Array || chunk instanceof ArrayBuffer)
254
+ return this.writeBuffer(chunk);
255
+
256
+ if (typeof(chunk) === 'object')
257
+ return this.writeObject(chunk);
258
+
259
+ return this.output();
260
+ }
261
+
262
+ writeBuffer(chunk) {
263
+
264
+ this.appendBuffer(chunk);
265
+
266
+ let index;
267
+
268
+ while ((index = this.buffer.indexOf(0x0A)) !== -1) { // \n
269
+ let lineBuffer = this.buffer.subarray(0, index);
270
+ this.buffer = this.buffer.subarray(index + 1);
271
+
272
+ // CRLF support: remove \r
273
+ if (lineBuffer.length && lineBuffer[lineBuffer.length - 1] === 0x0D)
274
+ lineBuffer = lineBuffer.subarray(0, lineBuffer.length - 1);
275
+
276
+ this.processLineBuffer(lineBuffer);
277
+ }
278
+
279
+ return this.output();
280
+ }
281
+
282
+ appendBuffer(chunk) {
283
+
284
+ if (chunk == null)
285
+ return;
286
+
287
+ if (Buffer.isBuffer(chunk)) {
288
+ // OK
289
+ } else if (chunk instanceof Uint8Array) {
290
+ chunk = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
291
+ } else if (chunk instanceof ArrayBuffer) {
292
+ chunk = Buffer.from(chunk);
293
+ } else {
294
+ chunk = Buffer.from(String(chunk), 'utf8');
295
+ }
296
+
297
+ this.buffer = this.buffer.length ? Buffer.concat([this.buffer, chunk]) : chunk;
298
+ }
299
+
300
+ processLineBuffer(lineBuffer) {
301
+ let line = lineBuffer.toString('utf8').trim();
302
+
303
+ if (!line)
304
+ return;
305
+
306
+ // SSE format: data: {...}
307
+ if (line.startsWith('data:'))
308
+ line = line.substring(5).trim();
309
+
310
+ if (!line || line === '[DONE]')
311
+ return;
312
+
313
+ const json = parseJSON(line);
314
+ if (json)
315
+ this.writeObject(json);
316
+ }
317
+
318
+ end() {
319
+ if (this.buffer.length)
320
+ this.processLineBuffer(this.buffer);
321
+ this.buffer = Buffer.alloc(0);
322
+ this.finalizeTools();
323
+ return this.output();
324
+ }
325
+
326
+ writeObject(chunk) {
327
+ switch (this.provider) {
328
+ case 'ollama':
329
+ this.parseOllama(chunk);
330
+ break;
331
+ case 'openai_chat':
332
+ case 'openai':
333
+ this.parseOpenAI(chunk);
334
+ break;
335
+ case 'openai_responses':
336
+ this.parseOpenAIresponse(chunk);
337
+ break;
338
+ case 'claude':
339
+ case 'anthropic':
340
+ this.parseClaude(chunk);
341
+ break;
342
+ case 'gemini':
343
+ case 'google':
344
+ this.parseGemini(chunk);
345
+ break;
346
+
347
+ default:
348
+ this.parseGeneric(chunk);
349
+ break;
350
+ }
351
+ return this.output();
352
+ }
353
+
354
+ addContent(value) {
355
+ if (!value)
356
+ return;
357
+ this.content += value;
358
+ if (!this.tools.length)
359
+ this.reasoning = 'content';
360
+ }
361
+
362
+ getTool(key, id, name) {
363
+
364
+ key = key || id || name || String(this.tools.length);
365
+
366
+ let tool = this.toolmap[key];
367
+ if (!tool) {
368
+ tool = createTool(id, name);
369
+ this.toolmap[key] = tool;
370
+ this.tools.push(tool);
371
+ }
372
+
373
+ if (id)
374
+ tool.id = id;
375
+
376
+ if (name)
377
+ tool.name = name;
378
+
379
+ this.reasoning = 'tool';
380
+
381
+ return tool;
382
+ }
383
+
384
+ appendToolArguments(tool, value) {
385
+
386
+ if (value == null)
387
+ return;
388
+
389
+ if (typeof value === 'object') {
390
+ tool.input = value;
391
+ tool.arguments = JSON.stringify(value);
392
+ return;
393
+ }
394
+
395
+ tool.arguments += value;
396
+ }
397
+
398
+ finalizeTools() {
399
+ for (const tool of this.tools) {
400
+ if (tool.input == null)
401
+ tool.input = normalize(tool.arguments);
402
+ }
403
+ }
404
+
405
+ output() {
406
+ this.finalizeTools();
407
+ return {
408
+ content: this.content,
409
+ tools: this.tools.map(tool => ({
410
+ id: tool.id,
411
+ name: tool.name,
412
+ arguments: tool.input || normalize(tool.arguments)
413
+ })),
414
+ reasoning: this.tools.length ? 'tool' : this.reasoning
415
+ };
416
+ }
417
+
418
+ // ------------------------------------------------------------
419
+ // Ollama stream
420
+ // ------------------------------------------------------------
421
+
422
+ parseOllama(chunk) {
423
+
424
+ const message = chunk.message || {};
425
+
426
+ if (message.content)
427
+ this.addContent(message.content);
428
+
429
+ const calls = message.tool_calls || [];
430
+
431
+ for (let i = 0; i < calls.length; i++) {
432
+ const call = calls[i];
433
+ const fn = call.function || {};
434
+ const key = call.id || fn.name || `ollama_${i}`;
435
+
436
+ const tool = this.getTool(key, call.id, fn.name);
437
+
438
+ if (fn.arguments != null)
439
+ this.appendToolArguments(tool, fn.arguments);
440
+ }
441
+ }
442
+
443
+ // ------------------------------------------------------------
444
+ // OpenAI Chat Completions stream
445
+ // ------------------------------------------------------------
446
+ parseOpenAI(chunk) {
447
+ const choices = chunk.choices || [];
448
+
449
+ for (const choice of choices) {
450
+ const delta = choice.delta || {};
451
+
452
+ if (delta.content)
453
+ this.addContent(delta.content);
454
+
455
+ const calls = delta.tool_calls || [];
456
+
457
+ for (const call of calls) {
458
+ const index = call.index != null ? call.index : 0;
459
+ const fn = call.function || {};
460
+ const key = call.id || `openai_chat_${index}`;
461
+
462
+ const tool = this.getTool(key, call.id, fn.name);
463
+
464
+ if (fn.arguments != null)
465
+ this.appendToolArguments(tool, fn.arguments);
466
+ }
467
+ }
468
+ }
469
+
470
+ // ------------------------------------------------------------
471
+ // OpenAI Responses API stream
472
+ // ------------------------------------------------------------
473
+
474
+ parseOpenAIresponse(chunk) {
475
+ switch (chunk.type) {
476
+ case 'response.output_text.delta':
477
+ case 'response.refusal.delta':
478
+ this.addContent(chunk.delta);
479
+ break;
480
+
481
+ case 'response.function_call_arguments.delta': {
482
+ const key = chunk.item_id || chunk.call_id || `openai_response_${chunk.output_index || 0}`;
483
+ const tool = this.getTool(key, chunk.call_id || chunk.item_id, chunk.name);
484
+
485
+ this.appendToolArguments(tool, chunk.delta);
486
+ break;
487
+ }
488
+
489
+ case 'response.output_item.added': {
490
+ const item = chunk.item || {};
491
+
492
+ if (item.type === 'function_call') {
493
+ const key = item.id || item.call_id;
494
+ const tool = this.getTool(key, item.call_id || item.id, item.name);
495
+
496
+ if (item.arguments)
497
+ this.appendToolArguments(tool, item.arguments);
498
+ }
499
+
500
+ break;
501
+ }
502
+
503
+ case 'response.output_item.done': {
504
+ const item = chunk.item || {};
505
+
506
+ if (item.type === 'function_call') {
507
+ const key = item.id || item.call_id;
508
+ const tool = this.getTool(key, item.call_id || item.id, item.name);
509
+
510
+ if (item.arguments)
511
+ this.appendToolArguments(tool, item.arguments);
512
+ }
513
+
514
+ break;
515
+ }
516
+ }
517
+ }
518
+
519
+ // ------------------------------------------------------------
520
+ // Claude / Anthropic stream
521
+ // ------------------------------------------------------------
522
+
523
+ parseClaude(chunk) {
524
+ switch (chunk.type) {
525
+ case 'content_block_start': {
526
+ const block = chunk.content_block || {};
527
+
528
+ if (block.type === 'tool_use') {
529
+ const key = block.id || `claude_${chunk.index || 0}`;
530
+ const tool = this.getTool(key, block.id, block.name);
531
+
532
+ if (block.input)
533
+ this.appendToolArguments(tool, block.input);
534
+ }
535
+
536
+ break;
537
+ }
538
+
539
+ case 'content_block_delta': {
540
+ const delta = chunk.delta || {};
541
+
542
+ if (delta.type === 'text_delta')
543
+ this.addContent(delta.text);
544
+
545
+ if (delta.type === 'input_json_delta') {
546
+ const key = `claude_${chunk.index || 0}`;
547
+ const tool = this.getTool(key, null, null);
548
+
549
+ this.appendToolArguments(tool, delta.partial_json);
550
+ }
551
+
552
+ break;
553
+ }
554
+
555
+ case 'message_delta': {
556
+ const delta = chunk.delta || {};
557
+
558
+ if (delta.stop_reason === 'tool_use')
559
+ this.reasoning = 'tool';
560
+
561
+ break;
562
+ }
563
+ }
564
+ }
565
+
566
+ // ------------------------------------------------------------
567
+ // Gemini stream
568
+ // ------------------------------------------------------------
569
+
570
+ parseGemini(chunk) {
571
+ const candidates = chunk.candidates || [];
572
+
573
+ for (const candidate of candidates) {
574
+ const parts = candidate.content?.parts || [];
575
+
576
+ for (let i = 0; i < parts.length; i++) {
577
+ const part = parts[i];
578
+
579
+ if (part.text)
580
+ this.addContent(part.text);
581
+
582
+ if (part.functionCall) {
583
+ const fn = part.functionCall;
584
+ const key = `gemini_${i}_${fn.name}`;
585
+
586
+ const tool = this.getTool(key, key, fn.name);
587
+ this.appendToolArguments(tool, fn.args || {});
588
+ }
589
+ }
590
+ }
591
+ }
592
+
593
+ // ------------------------------------------------------------
594
+ // Fallback parser
595
+ // ------------------------------------------------------------
596
+
597
+ parseGeneric(chunk) {
598
+ if (chunk.content)
599
+ this.addContent(chunk.content);
600
+
601
+ if (chunk.text)
602
+ this.addContent(chunk.text);
603
+
604
+ if (chunk.message?.content)
605
+ this.addContent(chunk.message.content);
606
+
607
+ const calls = chunk.tool_calls || chunk.tools || chunk.message?.tool_calls || [];
608
+
609
+ for (let i = 0; i < calls.length; i++) {
610
+ const call = calls[i];
611
+ const fn = call.function || call;
612
+
613
+ const tool = this.getTool(
614
+ call.id || `generic_${i}`,
615
+ call.id,
616
+ fn.name
617
+ );
618
+
619
+ this.appendToolArguments(tool, fn.arguments || fn.args || fn.input || {});
620
+ }
621
+ }
622
+
623
+ reset() {
624
+ this.content = '';
625
+ this.tools = [];
626
+ this.reasoning = 'content';
627
+ this.buffer = Buffer.alloc(0);
628
+ this.toolmap = Object.create(null);
629
+ return this;
630
+ }
631
+ }
632
+
633
+ exports.parser = function(provider) {
634
+ return new AIStreamParser(provider);
635
+ };
package/changelog.txt CHANGED
@@ -14,7 +14,9 @@
14
14
  - fixed typo in send method data assignment
15
15
  - added `PROXYCLIENT(proxyserver, ip:port, [logger])` method
16
16
  - added `PROXYSERVER(endpoint, socketendpoint, [logger])` method
17
- - added `AIMODEL.tool(content, [merge])` method.
17
+ - added `AIMODEL.tool(content, [merge])` method
18
+ - added `AIPARSER(provider)` method for parsing AI responses
19
+ - extended `String.prototype` by adding the `String.parseContext()` method to parse a specific text file format
18
20
 
19
21
  ========================
20
22
  0.0.16
package/global.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Total.js Globals
2
2
  // The MIT License
3
- // Copyright 2023 (c) Peter Širka <petersirka@gmail.com>
3
+ // Copyright 2023-2026 (c) Peter Širka <petersirka@gmail.com>
4
4
 
5
5
  'use strict';
6
6
 
@@ -266,6 +266,7 @@ global.NEWAPI = (name, config, callback) => F.TApi.newapi(name, config, callback
266
266
 
267
267
  // AI
268
268
  global.AIMODEL = (name, schema, data, $) => F.TAIModel.exec(name, schema, data, $);
269
+ global.AIPARSER = (provider) => F.TAIModel.parser(provider);
269
270
  global.NEWAIMODEL = (name, config, callback) => F.TAIModel.newai(name, config, callback);
270
271
 
271
272
  // NoSQL
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total5",
3
- "version": "0.0.17-5",
3
+ "version": "0.0.17-7",
4
4
  "description": "Total.js framework v5",
5
5
  "main": "index.js",
6
6
  "directories": {
package/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Total.js Utils
2
2
  // The MIT License
3
- // Copyright 2012-2023 (c) Peter Širka <petersirka@gmail.com>
3
+ // Copyright 2012-2026 (c) Peter Širka <petersirka@gmail.com>
4
4
 
5
5
  'use strict';
6
6
 
@@ -6051,6 +6051,31 @@ function extractnested(str, minDepth = 0) {
6051
6051
  return { text: out, parts };
6052
6052
  }
6053
6053
 
6054
+ SP.parseContext = function() {
6055
+
6056
+ const text = this;
6057
+ const REG = /-{20,}\s*\n([\s\S]*?)\n-{20,}\s*\n([\s\S]*?)(?=\n-{20,}\s*\n|$)/g;
6058
+ const output = [];
6059
+
6060
+ let match;
6061
+
6062
+ while ((match = REG.exec(text))) {
6063
+ const header = {};
6064
+ const body = match[2].trim();
6065
+ for (const line of match[1].split('\n')) {
6066
+ const index = line.indexOf(':');
6067
+ if (index === -1)
6068
+ continue;
6069
+ const key = line.substring(0, index).trim().toLowerCase();
6070
+ const value = line.substring(index + 1).trim();
6071
+ header[key] = value;
6072
+ }
6073
+ output.push({ ...header, body });
6074
+ }
6075
+
6076
+ return output;
6077
+ };
6078
+
6054
6079
  SP.toJSONSchema = SP.parseSchema = function(name, url) {
6055
6080
 
6056
6081
  let obj = {};
@@ -6293,6 +6318,7 @@ SP.toJSONSchema = SP.parseSchema = function(name, url) {
6293
6318
  obj.required = required;
6294
6319
 
6295
6320
  obj.errors = errors;
6321
+ obj.aitool = jsonschemaaitool;
6296
6322
  obj.transform = exports.jsonschematransform;
6297
6323
  obj.validate = function(value, partial, path) {
6298
6324
  let err = new F.TBuilders.ErrorBuilder();
@@ -6307,6 +6333,28 @@ SP.toJSONSchema = SP.parseSchema = function(name, url) {
6307
6333
  return obj;
6308
6334
  };
6309
6335
 
6336
+ function jsonschemaaitool(name) {
6337
+ let t = this;
6338
+ let properties = {};
6339
+ let required = t.required;
6340
+ for (let key in t.properties) {
6341
+ let prop = t.properties[key];
6342
+ properties[key] = { type: prop.type, description: t.errors[key] };
6343
+ }
6344
+ return {
6345
+ type: 'function',
6346
+ function: {
6347
+ name: name,
6348
+ description: t.summary || t.description,
6349
+ parameters: {
6350
+ type: 'object',
6351
+ properties: properties,
6352
+ required: required || []
6353
+ }
6354
+ }
6355
+ }
6356
+ }
6357
+
6310
6358
  exports.jsonschematransform = function(value, partial, error, path) {
6311
6359
 
6312
6360
  let self = this;