total5 0.0.17-5 → 0.0.17-6

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,8 @@
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
18
19
 
19
20
  ========================
20
21
  0.0.16
package/global.js CHANGED
@@ -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-6",
4
4
  "description": "Total.js framework v5",
5
5
  "main": "index.js",
6
6
  "directories": {
package/utils.js CHANGED
@@ -6293,6 +6293,7 @@ SP.toJSONSchema = SP.parseSchema = function(name, url) {
6293
6293
  obj.required = required;
6294
6294
 
6295
6295
  obj.errors = errors;
6296
+ obj.aitool = jsonschemaaitool;
6296
6297
  obj.transform = exports.jsonschematransform;
6297
6298
  obj.validate = function(value, partial, path) {
6298
6299
  let err = new F.TBuilders.ErrorBuilder();
@@ -6307,6 +6308,28 @@ SP.toJSONSchema = SP.parseSchema = function(name, url) {
6307
6308
  return obj;
6308
6309
  };
6309
6310
 
6311
+ function jsonschemaaitool(name) {
6312
+ let t = this;
6313
+ let properties = {};
6314
+ let required = t.required;
6315
+ for (let key in t.properties) {
6316
+ let prop = t.properties[key];
6317
+ properties[key] = { type: prop.type, description: t.errors[key] };
6318
+ }
6319
+ return {
6320
+ type: 'function',
6321
+ function: {
6322
+ name: name,
6323
+ description: t.summary || t.description,
6324
+ parameters: {
6325
+ type: 'object',
6326
+ properties: properties,
6327
+ required: required || []
6328
+ }
6329
+ }
6330
+ }
6331
+ }
6332
+
6310
6333
  exports.jsonschematransform = function(value, partial, error, path) {
6311
6334
 
6312
6335
  let self = this;