utilitas 1998.2.65 → 1998.2.66

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/lib/alan.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { fileTypeFromBuffer } from 'file-type';
2
2
  import { end, loop } from './event.mjs';
3
3
  import { createWavHeader } from './media.mjs';
4
- import get, { checkSearch, search } from './shot.mjs';
4
+ import { checkSearch, search } from './shot.mjs';
5
5
  import { BASE64, BUFFER, DATAURL, MIME_BINARY, STREAM, convert } from './storage.mjs';
6
6
  import { create as createUoid } from './uoid.mjs';
7
7
  import { distill } from './web.mjs';
@@ -46,32 +46,31 @@ const _NEED = [
46
46
  ];
47
47
 
48
48
  const [
49
- OPENAI, GEMINI, CHATGPT, OPENAI_EMBEDDING, GEMINI_EMEDDING, OPENAI_TRAINING,
50
- OLLAMA, CLAUDE, GPT_4O_MINI, GPT_4O, GPT_O1, GPT_O3_MINI, GEMINI_20_FLASH,
49
+ OPENAI, GEMINI, OPENAI_EMBEDDING, GEMINI_EMEDDING, OPENAI_TRAINING,
50
+ OLLAMA, GPT_4O_MINI, GPT_4O, GPT_O1, GPT_O3_MINI, GEMINI_20_FLASH,
51
51
  GEMINI_20_FLASH_THINKING, GEMINI_20_PRO, NOVA, EMBEDDING_001, DEEPSEEK_R1,
52
- DEEPSEEK_R1_70B, DEEPSEEK_R1_32B, MD_CODE, CHATGPT_REASONING,
53
- TEXT_EMBEDDING_3_SMALL, TEXT_EMBEDDING_3_LARGE, CLOUD_37_SONNET, AUDIO, WAV,
54
- CHATGPT_MINI, ATTACHMENTS, CHAT, OPENAI_VOICE, MEDIUM, LOW, HIGH,
55
- GPT_REASONING_EFFORT, THINK, THINK_STR, THINK_END, AZURE, TOOLS_STR,
56
- TOOLS_END, TOOLS, TEXT, THINKING, OK, FUNC, GPT_45, REDACTED_THINKING,
57
- GEMMA_3_27B, AZURE_OPENAI, ANTHROPIC, VERTEX_ANTHROPIC, GEMMA327B, size8k,
58
- ais, MAX_TOOL_RECURSION, LOG, name, user, system, assistant, MODEL,
59
- JSON_OBJECT, TOOL, silent, NOT_INIT, INVALID_FILE, tokenSafeRatio,
60
- GPT_QUERY_LIMIT, minsOfDay, CONTENT_IS_REQUIRED,
52
+ DEEPSEEK_R1_70B, DEEPSEEK_R1_32B, MD_CODE, TEXT_EMBEDDING_3_SMALL,
53
+ TEXT_EMBEDDING_3_LARGE, CLOUD_37_SONNET, AUDIO, WAV, ATTACHMENTS, CHAT,
54
+ OPENAI_VOICE, MEDIUM, LOW, HIGH, GPT_REASONING_EFFORT, THINK, THINK_STR,
55
+ THINK_END, AZURE, TOOLS_STR, TOOLS_END, TOOLS, TEXT, THINKING, OK, FUNC,
56
+ GPT_45, REDACTED_THINKING, GEMMA_3_27B, AZURE_OPENAI, ANTHROPIC,
57
+ VERTEX_ANTHROPIC, GEMMA327B, size8k, ais, MAX_TOOL_RECURSION, LOG, name,
58
+ user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent, NOT_INIT,
59
+ INVALID_FILE, tokenSafeRatio, GPT_QUERY_LIMIT, minsOfDay,
60
+ CONTENT_IS_REQUIRED,
61
61
  ] = [
62
- 'OPENAI', 'GEMINI', 'CHATGPT', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
63
- 'OPENAI_TRAINING', 'OLLAMA', 'CLAUDE', 'gpt-4o-mini', 'gpt-4o', 'o1',
64
- 'o3-mini', 'gemini-2.0-flash', 'gemini-2.0-flash-thinking-exp',
62
+ 'OpenAI', 'Gemini', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
63
+ 'OPENAI_TRAINING', 'Ollama', 'gpt-4o-mini', 'gpt-4o', 'o1', 'o3-mini',
64
+ 'gemini-2.0-flash', 'gemini-2.0-flash-thinking-exp',
65
65
  'gemini-2.0-pro-exp', 'nova', 'embedding-001', 'deepseek-r1',
66
- 'deepseek-r1:70b', 'deepseek-r1:32b', '```', 'CHATGPT_REASONING',
67
- 'text-embedding-3-small', 'text-embedding-3-large',
68
- 'claude-3-7-sonnet@20250219', 'audio', 'wav', 'CHATGPT_MINI',
66
+ 'deepseek-r1:70b', 'deepseek-r1:32b', '```', 'text-embedding-3-small',
67
+ 'text-embedding-3-large', 'claude-3-7-sonnet@20250219', 'audio', 'wav',
69
68
  '[ATTACHMENTS]', 'CHAT', 'OPENAI_VOICE', 'medium', 'low', 'high',
70
69
  'medium', 'think', '<think>', '</think>', 'AZURE', '<tools>',
71
70
  '</tools>', 'tools', 'text', 'thinking', 'OK', 'function',
72
71
  'gpt-4.5-preview', 'redacted_thinking', 'gemma-3-27b-it',
73
72
  'AZURE OPENAI', 'ANTHROPIC', 'VERTEX ANTHROPIC', 'gemma3:27b',
74
- 7680 * 4320, {}, 10, { log: true }, 'Alan', 'user', 'system',
73
+ 7680 * 4320, [], 10, { log: true }, 'Alan', 'user', 'system',
75
74
  'assistant', 'model', 'json_object', 'tool', true,
76
75
  'AI engine has not been initialized.', 'Invalid file data.', 1.1, 100,
77
76
  60 * 24, 'Content is required.',
@@ -90,23 +89,16 @@ const [
90
89
  'text/plain', 'audio/x-wav', 'audio/ogg',
91
90
  ];
92
91
 
93
- const [tool, provider, messages, text] = [
94
- type => ({ type }), provider => ({ provider }),
95
- messages => ({ messages }), text => ({ text }),
96
- ];
97
-
92
+ const [tool, messages, text]
93
+ = [type => ({ type }), messages => ({ messages }), text => ({ text })];
98
94
  const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
99
95
  = ['code_interpreter', 'retrieval', FUNC].map(tool);
100
- const chatConfig
101
- = { sessions: new Map(), engines: {}, systemPrompt: INSTRUCTIONS };
102
96
  const [sessionType, aiType]
103
97
  = [`${name.toUpperCase()}-SESSION`, `${name.toUpperCase()}-AI`];
104
98
  const [newSessionId, newAiId]
105
99
  = [sessionType, aiType].map(type => () => createUoid({ type }));
100
+ const chatConfig = { sessions: new Map(), systemPrompt: INSTRUCTIONS };
106
101
  const tokenSafe = count => Math.ceil(count * tokenSafeRatio);
107
- const clients = {};
108
- const unifyProvider = options => unifyType(options?.provider, 'AI provider');
109
- const unifyEngine = options => unifyType(options?.engine, 'AI engine');
110
102
  const trimTailing = text => text.replace(/[\.\s]*$/, '');
111
103
  const renderText = (t, o) => _renderText(t, { extraCodeBlock: 0, ...o || {} });
112
104
  const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
@@ -128,18 +120,8 @@ const DEFAULT_MODELS = {
128
120
  [OPENAI_EMBEDDING]: TEXT_EMBEDDING_3_SMALL,
129
121
  [GEMINI_EMEDDING]: EMBEDDING_001,
130
122
  [OPENAI_TRAINING]: GPT_4O_MINI, // https://platform.openai.com/docs/guides/fine-tuning
131
-
132
-
133
- [CHATGPT_MINI]: GPT_4O_MINI,
134
- [CHATGPT_REASONING]: GPT_O3_MINI,
135
123
  };
136
124
 
137
-
138
-
139
-
140
-
141
-
142
-
143
125
  DEFAULT_MODELS[CHAT] = DEFAULT_MODELS[GEMINI];
144
126
 
145
127
  const tokenRatioByWords = Math.min(
@@ -164,17 +146,14 @@ const MODELS = {
164
146
  requestLimitsRPM: 10000,
165
147
  tokenLimitsTPD: 1000000000,
166
148
  tokenLimitsTPM: 10000000,
167
- trainingData: 'Oct 2023',
149
+ audio: 'gpt-4o-mini-audio-preview',
150
+ fast: true,
168
151
  json: true,
169
- vision: true,
170
152
  tools: true,
171
- audio: 'gpt-4o-mini-audio-preview',
172
- supportedMimeTypes: [
173
- png, jpeg, gif, webp,
174
- ],
175
- supportedAudioTypes: [
176
- wav,
177
- ],
153
+ vision: true,
154
+ supportedMimeTypes: [png, jpeg, gif, webp],
155
+ supportedAudioTypes: [wav],
156
+ trainingData: 'Oct 2023',
178
157
  },
179
158
  [GPT_4O]: {
180
159
  contextWindow: 128000,
@@ -183,17 +162,13 @@ const MODELS = {
183
162
  requestLimitsRPM: 10000,
184
163
  tokenLimitsTPD: 20000000,
185
164
  tokenLimitsTPM: 2000000,
186
- trainingData: 'Oct 2023',
165
+ audio: 'gpt-4o-audio-preview',
187
166
  json: true,
188
- vision: true,
189
167
  tools: true,
190
- audio: 'gpt-4o-audio-preview',
191
- supportedMimeTypes: [
192
- png, jpeg, gif, webp,
193
- ],
194
- supportedAudioTypes: [
195
- wav,
196
- ],
168
+ vision: true,
169
+ supportedMimeTypes: [png, jpeg, gif, webp],
170
+ supportedAudioTypes: [wav],
171
+ trainingData: 'Oct 2023',
197
172
  },
198
173
  [GPT_O1]: {
199
174
  contextWindow: 200000,
@@ -202,14 +177,14 @@ const MODELS = {
202
177
  requestLimitsRPM: 10000,
203
178
  tokenLimitsTPD: 200000000,
204
179
  tokenLimitsTPM: 2000000,
205
- trainingData: 'Oct 2023',
206
180
  json: true,
207
181
  reasoning: true,
208
- vision: true,
209
182
  tools: true,
183
+ vision: true,
210
184
  supportedMimeTypes: [
211
185
  png, jpeg, gif, webp,
212
186
  ],
187
+ trainingData: 'Oct 2023',
213
188
  },
214
189
  [GPT_O3_MINI]: {
215
190
  contextWindow: 200000,
@@ -218,14 +193,13 @@ const MODELS = {
218
193
  requestLimitsRPM: 10000,
219
194
  tokenLimitsTPD: 1000000000,
220
195
  tokenLimitsTPM: 10000000,
221
- trainingData: 'Oct 2023',
196
+ fast: true,
222
197
  json: true,
223
198
  reasoning: true,
224
- vision: true,
225
199
  tools: true,
226
- supportedMimeTypes: [
227
- png, jpeg, gif, webp,
228
- ],
200
+ vision: true,
201
+ supportedMimeTypes: [png, jpeg, gif, webp],
202
+ trainingData: 'Oct 2023',
229
203
  },
230
204
  [GPT_45]: {
231
205
  contextWindow: 128000,
@@ -235,20 +209,18 @@ const MODELS = {
235
209
  tokenLimitsTPD: 100000000,
236
210
  tokenLimitsTPM: 1000000,
237
211
  json: true,
238
- vision: true,
239
212
  tools: true,
240
- supportedMimeTypes: [
241
- png, jpeg, gif, webp,
242
- ],
213
+ vision: true,
214
+ supportedMimeTypes: [png, jpeg, gif, webp],
243
215
  trainingData: 'Oct 2023',
244
216
  },
245
217
  [GEMINI_20_FLASH]: {
246
218
  // https://ai.google.dev/gemini-api/docs/models/gemini
247
219
  // https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts?hl=en#gemini-send-multimodal-samples-pdf-nodejs
248
220
  // Audio / Video Comming Soon: https://ai.google.dev/gemini-api/docs/models/gemini#gemini-2.0-flash
221
+ audioCostTokens: 1000000, // 8.4 hours => 1 million tokens
249
222
  contextWindow: 1048576,
250
223
  imageCostTokens: size8k / (768 * 768) * 258,
251
- audioCostTokens: 1000000, // 8.4 hours => 1 million tokens
252
224
  maxAudioLength: 60 * 60 * 8.4, // 9.5 hours
253
225
  maxAudioPerPrompt: 1,
254
226
  maxFileSize: 20 * 1024 * 1024, // 20 MB
@@ -260,17 +232,18 @@ const MODELS = {
260
232
  maxVideoLengthWithAudio: 60 * 50, // 50 minutes
261
233
  maxVideoLengthWithoutAudio: 60 * 60, // 1 hour
262
234
  maxVideoPerPrompt: 10,
263
- requestLimitsRPM: 2000,
264
235
  requestLimitsRPD: 1500,
236
+ requestLimitsRPM: 2000,
265
237
  tokenLimitsTPM: 4 * 1000000,
266
- trainingData: 'August 2024',
267
- vision: true,
238
+ fast: true,
268
239
  json: true,
269
240
  tools: true,
241
+ vision: true,
270
242
  supportedMimeTypes: [
271
243
  png, jpeg, mov, mpeg, mp4, mpg, avi, wmv, mpegps, flv, pdf, aac,
272
244
  flac, mp3, m4a, mpga, opus, pcm, wav, webm, tgpp,
273
245
  ],
246
+ trainingData: 'August 2024',
274
247
  },
275
248
  [GEMINI_20_FLASH_THINKING]: {
276
249
  // https://cloud.google.com/vertex-ai/generative-ai/docs/thinking-mode?hl=en
@@ -284,12 +257,10 @@ const MODELS = {
284
257
  requestLimitsRPM: 1000,
285
258
  requestLimitsRPD: 1500,
286
259
  tokenLimitsTPM: 4 * 1000000,
287
- trainingData: 'August 2024',
288
- vision: true,
289
260
  reasoning: true,
290
- supportedMimeTypes: [
291
- png, jpeg,
292
- ],
261
+ vision: true,
262
+ supportedMimeTypes: [png, jpeg],
263
+ trainingData: 'August 2024',
293
264
  },
294
265
  [GEMINI_20_PRO]: {
295
266
  contextWindow: 2097152,
@@ -302,21 +273,22 @@ const MODELS = {
302
273
  requestLimitsRPM: 1000,
303
274
  requestLimitsRPD: 1500,
304
275
  tokenLimitsTPM: 4 * 1000000,
305
- trainingData: 'August 2024',
306
- vision: true,
307
276
  json: true,
277
+ vision: true,
308
278
  supportedMimeTypes: [
309
279
  png, jpeg, mov, mpeg, mp4, mpg, avi, wmv, mpegps, flv, pdf, aac,
310
280
  flac, mp3, m4a, mpga, opus, pcm, wav, webm, tgpp,
311
281
  ],
282
+ trainingData: 'August 2024',
312
283
  },
313
284
  [GEMMA_3_27B]: {
314
285
  contextWindow: 128 * 1000,
315
286
  imageCostTokens: 256,
316
287
  maxImageSize: 896 * 896,
317
288
  maxOutputTokens: 1024 * 8,
318
- vision: true,
289
+ fast: true,
319
290
  json: true,
291
+ vision: true,
320
292
  supportedMimeTypes: [png, jpeg],
321
293
  },
322
294
  [DEEPSEEK_R1]: {
@@ -360,12 +332,12 @@ const MODELS = {
360
332
  requestLimitsRPM: 50,
361
333
  tokenLimitsITPM: 40000,
362
334
  tokenLimitsOTPM: 8000,
363
- trainingData: 'Apr 2024', // ?
335
+ json: true,
364
336
  reasoning: true,
365
337
  tools: true,
366
- supportedMimeTypes: [
367
- png, jpeg, gif, webp, pdf,
368
- ],
338
+ vision: true,
339
+ supportedMimeTypes: [png, jpeg, gif, webp, pdf],
340
+ trainingData: 'Apr 2024',
369
341
  },
370
342
  };
371
343
 
@@ -401,10 +373,13 @@ const MAX_TRIM_TRY = MAX_INPUT_TOKENS / 1000;
401
373
 
402
374
  let tokeniser;
403
375
 
404
- const unifyType = (type, name) => {
405
- const TYPE = ensureString(type, { case: 'UP' });
406
- assert(TYPE, `${name} is required.`);
407
- return TYPE;
376
+ const unifyProvider = provider => {
377
+ assert(provider = (provider || '').trim(), 'AI provider is required.');
378
+ for (let type of [OPENAI, AZURE_OPENAI, AZURE, GEMINI, ANTHROPIC,
379
+ VERTEX_ANTHROPIC, OLLAMA]) {
380
+ if (insensitiveCompare(provider, type)) { return type; }
381
+ }
382
+ throwError(`Invalid AI provider: ${provider}.`);
408
383
  };
409
384
 
410
385
  const tools = [
@@ -502,8 +477,8 @@ const toolsGemini = async () => (await toolsOpenAI()).map(x => ({
502
477
  }));
503
478
 
504
479
  const init = async (options = {}) => {
505
- const id = newAiId();
506
- const provider = unifyProvider(options);
480
+ const id = options.id || newAiId();
481
+ const provider = unifyProvider(options?.provider);
507
482
  const modelName = options.model || DEFAULT_MODELS[provider];
508
483
  assert(modelName, `Model is required for provider: ${provider}.`);
509
484
  let model = options.modelConfig || MODELS[modelName];
@@ -512,49 +487,49 @@ const init = async (options = {}) => {
512
487
  switch (provider) {
513
488
  case OPENAI:
514
489
  assertApiKey(provider, options);
515
- ais[id] = {
490
+ ais.push({
516
491
  id, provider, model, client: await OpenAI(options),
517
492
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
518
493
  embedding: async (i, o) => await createOpenAIEmbedding(id, i, o),
519
- };
494
+ });
520
495
  break;
521
496
  case AZURE_OPENAI:
522
497
  assertApiKey(provider, options);
523
498
  assert(options.endpoint,
524
- `{provider} api endpoint and deployment are required.`);
525
- ais[id] = {
499
+ `${provider} api endpoint and deployment are required.`);
500
+ ais.push({
526
501
  id, provider, model, client: await AzureOpenAI({
527
502
  apiVersion: '2025-01-01-preview',
528
503
  deployment: model.name, ...options,
529
504
  }),
530
505
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
531
- };
506
+ });
532
507
  break;
533
508
  case AZURE:
534
509
  assertApiKey(provider, options);
535
510
  assert(options.baseURL, `${provider} api endpoint is required.`);
536
- ais[id] = {
511
+ ais.push({
537
512
  id, provider, model, client: await OpenAI(options),
538
513
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
539
- };
514
+ });
540
515
  break;
541
516
  case GEMINI:
542
517
  assertApiKey(provider, options);
543
518
  const { GoogleGenerativeAI } = await need('@google/generative-ai');
544
- ais[id] = {
519
+ ais.push({
545
520
  id, provider, model,
546
521
  client: new GoogleGenerativeAI(options.apiKey),
547
522
  prompt: async (cnt, opts) => await promptGemini(id, cnt, opts),
548
523
  embedding: async (i, o) => await createGeminiEmbedding(id, i, o),
549
- };
524
+ });
550
525
  break;
551
526
  case ANTHROPIC:
552
527
  assertApiKey(provider, options);
553
528
  const Anthropic = (await need('@anthropic-ai/sdk')).Anthropic;
554
- ais[id] = {
529
+ ais.push({
555
530
  id, provider, model, client: new Anthropic(options),
556
531
  prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
557
- };
532
+ });
558
533
  break;
559
534
  case VERTEX_ANTHROPIC:
560
535
  // https://github.com/anthropics/anthropic-sdk-typescript/tree/main/packages/vertex-sdk
@@ -562,21 +537,21 @@ const init = async (options = {}) => {
562
537
  const AnthropicVertex = (await need('@anthropic-ai/vertex-sdk')).AnthropicVertex;
563
538
  process.env['GOOGLE_APPLICATION_CREDENTIALS'] = options.credentials;
564
539
  process.env['ANTHROPIC_VERTEX_PROJECT_ID'] = options.projectId;
565
- ais[id] = {
540
+ ais.push({
566
541
  id, provider, model,
567
542
  client: new AnthropicVertex({ region: options?.region || 'us-east5' }),
568
543
  prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
569
- };
544
+ });
570
545
  break;
571
546
  case OLLAMA:
572
547
  // https://github.com/ollama/ollama/blob/main/docs/openai.md
573
548
  const baseURL = 'http://localhost:11434/v1/';
574
- ais[id] = {
549
+ ais.push({
575
550
  id, provider, model, client: await OpenAI({
576
551
  baseURL, apiKey: 'ollama', ...options
577
552
  }),
578
553
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
579
- };
554
+ });
580
555
  const phLog = m => log(`Ollama preheat: ${m?.message || m}`);
581
556
  ignoreErrFunc(async () => {
582
557
  phLog(await (await fetch(`${baseURL}completions`, {
@@ -589,15 +564,32 @@ const init = async (options = {}) => {
589
564
  default:
590
565
  throwError(`Invalid AI provider: ${options.provider || 'null'}.`);
591
566
  }
592
- return ais[id];
567
+ return ais.find(x => x.id === id);
593
568
  };
594
569
 
595
- const getAi = async (id, options) => {
570
+ const getAi = async (id, options = {}) => {
596
571
  if (id) {
597
- if (ais[id]) { return options?.client ? ais[id]?.client : ais[id]; }
598
- else { throwError(`AI not found: ${id}.`); }
572
+ const ai = ais.find(x => x.id === id);
573
+ assert(ais, `AI not found: ${id}.`);
574
+ return options?.client ? ai?.client : ai;
575
+ } else if (options?.select) {
576
+ const res = [];
577
+ for (let x of ais) {
578
+ let select = true;
579
+ for (let i in options.select) {
580
+ if (options.select[i] && i !== 'fast' && !x.model[i]) {
581
+ select = false; break;
582
+ }
583
+ }
584
+ select && (res.push(x));
585
+ }
586
+ const best = options.select?.fast ? res.filter(x => x.model.fast) : res;
587
+ if (best.length) { return options.all ? best : best[0]; }
588
+ assert(res.length, 'AI not found.');
589
+ log(`Best match AI not found, fallbacked: ${JSON.stringify(options.select)}.`);
590
+ return options.all ? res : res[0];
599
591
  }
600
- return ais;
592
+ return options.all ? ais : ais[0];
601
593
  };
602
594
 
603
595
  const countTokens = async (input, options) => {
@@ -822,11 +814,11 @@ const buildPrompts = async (model, input, options = {}) => {
822
814
  ...model?.supportedMimeTypes || [], ...model.supportedAudioTypes || []
823
815
  ].includes(x.mime_type));
824
816
  switch (options.flavor) {
825
- case CHATGPT:
817
+ case OPENAI:
826
818
  systemPrompt = buildGptMessage(options.systemPrompt, _system);
827
819
  prompt = buildGptMessage(content, options);
828
820
  break;
829
- case CLAUDE:
821
+ case ANTHROPIC:
830
822
  systemPrompt = options.systemPrompt;
831
823
  prompt = buildClaudeMessage(content, { ...options, cache_control: true });
832
824
  break;
@@ -841,11 +833,11 @@ const buildPrompts = async (model, input, options = {}) => {
841
833
  history = [];
842
834
  (options.messages?.length ? options.messages : []).map((x, i) => {
843
835
  switch (options.flavor) {
844
- case CHATGPT:
836
+ case OPENAI:
845
837
  history.push(buildGptMessage(x.request, _user));
846
838
  history.push(buildGptMessage(x.response, _assistant));
847
839
  break;
848
- case CLAUDE:
840
+ case ANTHROPIC:
849
841
  history.push(buildClaudeMessage(x.request, _user));
850
842
  history.push(buildClaudeMessage(x.response, _assistant));
851
843
  break;
@@ -857,13 +849,13 @@ const buildPrompts = async (model, input, options = {}) => {
857
849
  }
858
850
  });
859
851
  switch (options.flavor) {
860
- case CHATGPT:
852
+ case OPENAI:
861
853
  history = messages([
862
854
  systemPrompt, ...history, prompt,
863
855
  ...options.toolsResult?.length ? options.toolsResult : []
864
856
  ]);
865
857
  break;
866
- case CLAUDE:
858
+ case ANTHROPIC:
867
859
  history = messages([
868
860
  ...history, prompt,
869
861
  ...options.toolsResult?.length ? options.toolsResult : []
@@ -889,7 +881,7 @@ const buildPrompts = async (model, input, options = {}) => {
889
881
  content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
890
882
  }
891
883
  }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
892
- if ([CHATGPT].includes(options.flavor) || options.model === GEMMA_3_27B) {
884
+ if ([OPENAI].includes(options.flavor) || options.model === GEMMA_3_27B) {
893
885
  systemPrompt = null;
894
886
  }
895
887
  return { systemPrompt, history, prompt };
@@ -910,13 +902,13 @@ const handleToolsCall = async (msg, options) => {
910
902
  const calls = msg.tool_calls || msg.content || msg.parts || [];
911
903
  if (calls.length) {
912
904
  switch (options?.flavor) {
913
- case CLAUDE: preRes.push(msg); break;
905
+ case ANTHROPIC: preRes.push(msg); break;
914
906
  case GEMINI: preRes.push(msg); break;
915
- case CHATGPT: default: preRes.push(msg); break;
907
+ case OPENAI: default: preRes.push(msg); break;
916
908
  }
917
909
  for (const fn of calls) {
918
910
  switch (options?.flavor) {
919
- case CLAUDE:
911
+ case ANTHROPIC:
920
912
  input = fn.input = String.isString(fn?.input)
921
913
  ? parseJson(fn.input) : fn?.input;
922
914
  packMsg = (content, is_error) => ({
@@ -935,7 +927,7 @@ const handleToolsCall = async (msg, options) => {
935
927
  }
936
928
  });
937
929
  break;
938
- case CHATGPT: default:
930
+ case OPENAI: default:
939
931
  input = parseJson(fn?.function?.arguments);
940
932
  packMsg = (content = '', e = false) => ({
941
933
  role: TOOL, tool_call_id: fn.id,
@@ -972,7 +964,7 @@ const handleToolsCall = async (msg, options) => {
972
964
  }
973
965
  if (content.length) {
974
966
  switch (options?.flavor) {
975
- case CLAUDE: content = [{ role: user, content }]; break;
967
+ case ANTHROPIC: content = [{ role: user, content }]; break;
976
968
  case GEMINI: content = [{ role: FUNC, parts: content }]; break;
977
969
  }
978
970
  }
@@ -994,7 +986,7 @@ const promptOpenAI = async (aiId, content, options = {}) => {
994
986
  options.result ?? '', Buffer.alloc(0), null, [], false,
995
987
  provider === AZURE
996
988
  ];
997
- options.flavor = CHATGPT;
989
+ options.flavor = OPENAI;
998
990
  options.model = options.model || model.name;
999
991
  const { history }
1000
992
  = await buildPrompts(MODELS[options.model], content, options);
@@ -1073,7 +1065,7 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1073
1065
  + '46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1074
1066
  ); // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1075
1067
  const { systemPrompt: system, history }
1076
- = await buildPrompts(model, content, { ...options, flavor: CLAUDE });
1068
+ = await buildPrompts(model, content, { ...options, flavor: ANTHROPIC });
1077
1069
  const resp = await client.beta.messages.create({
1078
1070
  model: options.model, ...history, system, stream: true,
1079
1071
  max_tokens: options.extendedThinking ? 128000 : model.maxOutputTokens,
@@ -1124,7 +1116,7 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1124
1116
  ]
1125
1117
  };
1126
1118
  const { toolsResult, toolsResponse } = await handleToolsCall(
1127
- event, { ...options, result, flavor: CLAUDE },
1119
+ event, { ...options, result, flavor: ANTHROPIC },
1128
1120
  );
1129
1121
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1130
1122
  return await promptAnthropic(aiId, content, {
@@ -1326,7 +1318,7 @@ const listGptFineTuningEvents = async (aiId, job_id, options) => {
1326
1318
 
1327
1319
  const tailGptFineTuningEvents = async (aiId, job_id, options) => {
1328
1320
  assert(job_id, 'Job ID is required.');
1329
- const [loopName, listOpts] = [`GPT - ${job_id} `, {
1321
+ const [loopName, listOpts] = [`GPT-${job_id}`, {
1330
1322
  ...options, params: { ...options?.params, order: 'ascending' }
1331
1323
  }];
1332
1324
  let lastEvent;
@@ -1344,33 +1336,23 @@ const tailGptFineTuningEvents = async (aiId, job_id, options) => {
1344
1336
  }, 3, 2, 1, loopName, { silent, ...options });
1345
1337
  };
1346
1338
 
1347
- const initChat = async (options) => {
1348
- options = {
1349
- engines: options?.engines || { [DEFAULT_MODELS[CHAT]]: {} },
1350
- ...options || {},
1351
- };
1352
- if (options?.sessions) {
1339
+ const initChat = async (options = {}) => {
1340
+ if (options.sessions) {
1353
1341
  assert(
1354
1342
  options.sessions?.get && options.sessions?.set,
1355
1343
  'Invalid session storage provider.'
1356
1344
  );
1357
1345
  chatConfig.sessions = options.sessions;
1358
1346
  }
1359
- options?.instructions && (chatConfig.systemPrompt = options.instructions);
1360
- for (const i in options.engines) {
1361
- const key = ensureString(i, { case: 'UP' });
1362
- const model = DEFAULT_MODELS[key];
1363
- assert(model, `Invalid chat model: '${i}'.`);
1364
- chatConfig.engines[key] = options.engines[i];
1365
- chatConfig.engines[key].model = chatConfig.engines[key].model || model;
1366
- const mxPmpt = MODELS[chatConfig.engines[key].model].maxInputTokens / 2;
1367
- const pmptTokens = await countTokens([buildGeminiHistory(
1368
- chatConfig.systemPrompt, { role: system }
1369
- )]); // Use Gemini instead of ChatGPT because of the longer pack
1370
- assert(
1371
- pmptTokens < mxPmpt,
1372
- `System prompt is too long: ${pmptTokens} / ${mxPmpt} tokens.`
1373
- );
1347
+ options.instructions && (chatConfig.systemPrompt = options.instructions);
1348
+ // Use Gemini instead of ChatGPT because of the longer package.
1349
+ const [spTokens, ais] = await Promise.all([countTokens([buildGeminiHistory(
1350
+ chatConfig.systemPrompt, { role: system }
1351
+ )]), getAi(null, { all: true })]);
1352
+ for (const ai of ais) {
1353
+ const mxPmpt = ai.model.maxInputTokens / 2;
1354
+ assert(spTokens < mxPmpt,
1355
+ `System prompt is too long: ${spTokens} / ${mxPmpt} tokens.`);
1374
1356
  }
1375
1357
  return chatConfig;
1376
1358
  };
@@ -1406,53 +1388,30 @@ const resetSession = async (sessionId, options) => {
1406
1388
  return await setSession(sessionId, session);
1407
1389
  };
1408
1390
 
1409
- const packResult = resp => {
1410
- const result = {
1411
- ...resp, spoken: renderText(
1412
- resp.text, { noCode: true, noLink: true }
1413
- ).replace(/\[\^\d\^\]/ig, ''),
1414
- };
1415
- log(`Response (${result.model}): ${JSON.stringify(result.text)}`);
1416
- // log(result);
1417
- return result;
1418
- };
1419
-
1420
- const talk = async (input, options) => {
1421
- let [engine, chat, resp, sessionId] = [
1422
- unifyEngine({
1423
- engine: Object.keys(chatConfig.engines)?.[0] || DEFAULT_MODELS[CHAT],
1424
- ...options,
1425
- }), { request: input || ATTACHMENTS }, null,
1426
- options?.sessionId || newSessionId(),
1427
- ];
1428
- assert(chatConfig.engines[engine], NOT_INIT);
1429
- const session = await getSession(sessionId, { engine, ...options });
1430
- log(`Prompt (${engine}): ${JSON.stringify(input)}`);
1431
- const pmtOptions = {
1432
- messages: session.messages, model: chatConfig.engines[engine].model,
1433
- ...options,
1434
- };
1435
- switch (engine) {
1436
- case CHATGPT: resp = await promptOpenAI(input, pmtOptions); break;
1437
- case GEMINI: resp = await promptGemini(input, pmtOptions); break;
1438
- case CLAUDE: resp = await promptAnthropic(input, pmtOptions); break;
1439
- // case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1440
- case AZURE: resp = await promptAzure(input, pmtOptions); break;
1441
- default: throwError(`Invalid AI engine: '${engine}'.`);
1442
- }
1391
+ const talk = async (input, options = {}) => {
1392
+ let [chat, sessionId] =
1393
+ [{ request: input || ATTACHMENTS }, options.sessionId || newSessionId()];
1394
+ const session = await getSession(sessionId, options);
1395
+ const resp = await prompt(input, {
1396
+ messages: session.messages, log: true, ...options,
1397
+ });
1443
1398
  chat.response = resp.text;
1444
1399
  chat.request && chat.response && session.messages.push(chat);
1445
1400
  await setSession(sessionId, session, options);
1446
- return { sessionId, ...packResult(resp) };
1401
+ return {
1402
+ sessionId, ...resp, spoken: renderText(
1403
+ resp.text, { noCode: true, noLink: true }
1404
+ ).replace(/\[\^\d\^\]/ig, ''),
1405
+ };
1447
1406
  };
1448
1407
 
1449
- const getMaxChatPromptLimit = (options) => {
1408
+ const getMaxChatPromptLimit = async (options) => {
1450
1409
  let resp = 0;
1451
- for (const i in chatConfig.engines) {
1452
- if (options?.engine && i !== options.engine) { continue; }
1453
- const maxInputTokens = MODELS[chatConfig.engines[i].model].maxInputTokens;
1410
+ (await getAi(null, { all: true })).map(x => {
1411
+ if (options?.aiId && options?.aiId !== x.id) { return; }
1412
+ const maxInputTokens = x.model.maxInputTokens;
1454
1413
  resp = resp ? Math.min(resp, maxInputTokens) : maxInputTokens;
1455
- }
1414
+ });
1456
1415
  assert(resp > 0, 'Chat engine has not been initialized.');
1457
1416
  return options?.raw ? resp : Math.min(resp, MAX_INPUT_TOKENS);
1458
1417
  };
@@ -1486,28 +1445,17 @@ const distillFile = async (attachments, o) => {
1486
1445
  attachments = await Promise.all(attachments);
1487
1446
  // print(attachments);
1488
1447
  return await prompt(strPmt, {
1489
- fast: true, multimodal: true, simple: true, ...o, attachments,
1448
+ simple: true, select: { vision: true, fast: true }, ...o, attachments,
1490
1449
  });
1491
1450
  };
1492
1451
 
1493
- const prompt = async (input, options) => {
1494
- let egn = options?.engine && unifyEngine(options);
1495
- const engines = PREFERRED_ENGINES.slice();
1496
- options?.multimodal && engines.sort((x, y) => x.multimodal - y.multimodal);
1497
- for (const engine of engines) {
1498
- if ((egn ? engine.client === egn : true) && clients[engine.client]) {
1499
- const extra = {};
1500
- if (engine.client === OPENAI) {
1501
- if (options?.fast) {
1502
- extra.model = DEFAULT_MODELS[CHATGPT_MINI];
1503
- } else if (options?.reasoning) {
1504
- extra.model = DEFAULT_MODELS[CHATGPT_REASONING];
1505
- }
1506
- }
1507
- return await engine.func(input, { ...extra, ...options || {} });
1508
- }
1509
- }
1510
- throwError('No AI provider is available.');
1452
+ const prompt = async (input, options = {}) => {
1453
+ const ai = await getAi(options?.aiId, options);
1454
+ const tag = `${ai.provider} (${ai.model.name})`;
1455
+ options.log && log(`Prompt ${tag}: ${JSON.stringify(input)}`);
1456
+ const resp = await ai.prompt(input, options);
1457
+ options.log && log(`Response ${tag}: ${JSON.stringify(resp.text)}`);
1458
+ return resp;
1511
1459
  };
1512
1460
 
1513
1461
  const trimPrompt = async (getPrompt, trimFunc, contextWindow, options) => {
@@ -1549,23 +1497,16 @@ const analyzeSessions = async (sessionIds, options) => {
1549
1497
  x, JSON.stringify(sses[x]).length,
1550
1498
  ]).sort((x, y) => y[1] - x[1])[0][0]];
1551
1499
  }
1552
- }, getMaxChatPromptLimit(options));
1500
+ }, await getMaxChatPromptLimit(options));
1553
1501
  const aiResp = Object.keys(sses) ? (await prompt(getInput(), {
1554
- jsonMode: true, fast: true, simple: true, ...options || {}
1502
+ jsonMode: true, simple: true, select: { json: true, fase: true },
1503
+ ...options || {}
1555
1504
  })) : {};
1556
1505
  assert(aiResp, 'Unable to analyze sessions.');
1557
1506
  ids.map(x => resp[x] = aiResp[x] || null);
1558
1507
  return Array.isArray(sessionIds) ? resp : resp[sessionIds[0]];
1559
1508
  };
1560
1509
 
1561
- const PREFERRED_ENGINES = [
1562
- { client: OPENAI, func: promptOpenAI, multimodal: 0 },
1563
- { client: GEMINI, func: promptGemini, multimodal: 1 },
1564
- { client: CLAUDE, func: promptAnthropic, multimodal: 2 },
1565
- // { client: AZURE, func: promptAzure, multimodal: 3 },
1566
- // { client: OLLAMA, func: promptOllama, multimodal: 99 },
1567
- ]; // keep gpt first to avoid gemini grounding by default
1568
-
1569
1510
  export default init;
1570
1511
  export {
1571
1512
  ATTACHMENT_TOKEN_COST, CLOUD_37_SONNET, CODE_INTERPRETER, DEEPSEEK_R1,