utilitas 1998.2.65 → 1999.1.2

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,53 @@ const init = async (options = {}) => {
512
487
  switch (provider) {
513
488
  case OPENAI:
514
489
  assertApiKey(provider, options);
515
- ais[id] = {
516
- id, provider, model, client: await OpenAI(options),
490
+ ais.push({
491
+ id, provider, model, priority: 0, initOrder: ais.length,
492
+ client: await OpenAI(options),
517
493
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
518
494
  embedding: async (i, o) => await createOpenAIEmbedding(id, i, o),
519
- };
495
+ });
520
496
  break;
521
497
  case AZURE_OPENAI:
522
498
  assertApiKey(provider, options);
523
499
  assert(options.endpoint,
524
- `{provider} api endpoint and deployment are required.`);
525
- ais[id] = {
526
- id, provider, model, client: await AzureOpenAI({
500
+ `${provider} api endpoint and deployment are required.`);
501
+ ais.push({
502
+ id, provider, model, priority: 0, initOrder: ais.length,
503
+ client: await AzureOpenAI({
527
504
  apiVersion: '2025-01-01-preview',
528
505
  deployment: model.name, ...options,
529
506
  }),
530
507
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
531
- };
508
+ });
532
509
  break;
533
510
  case AZURE:
534
511
  assertApiKey(provider, options);
535
512
  assert(options.baseURL, `${provider} api endpoint is required.`);
536
- ais[id] = {
537
- id, provider, model, client: await OpenAI(options),
513
+ ais.push({
514
+ id, provider, model, priority: 0, initOrder: ais.length,
515
+ client: await OpenAI(options),
538
516
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
539
- };
517
+ });
540
518
  break;
541
519
  case GEMINI:
542
520
  assertApiKey(provider, options);
543
521
  const { GoogleGenerativeAI } = await need('@google/generative-ai');
544
- ais[id] = {
545
- id, provider, model,
522
+ ais.push({
523
+ id, provider, model, priority: 0, initOrder: ais.length,
546
524
  client: new GoogleGenerativeAI(options.apiKey),
547
525
  prompt: async (cnt, opts) => await promptGemini(id, cnt, opts),
548
526
  embedding: async (i, o) => await createGeminiEmbedding(id, i, o),
549
- };
527
+ });
550
528
  break;
551
529
  case ANTHROPIC:
552
530
  assertApiKey(provider, options);
553
531
  const Anthropic = (await need('@anthropic-ai/sdk')).Anthropic;
554
- ais[id] = {
555
- id, provider, model, client: new Anthropic(options),
532
+ ais.push({
533
+ id, provider, model, priority: 0, initOrder: ais.length,
534
+ client: new Anthropic(options),
556
535
  prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
557
- };
536
+ });
558
537
  break;
559
538
  case VERTEX_ANTHROPIC:
560
539
  // https://github.com/anthropics/anthropic-sdk-typescript/tree/main/packages/vertex-sdk
@@ -562,21 +541,20 @@ const init = async (options = {}) => {
562
541
  const AnthropicVertex = (await need('@anthropic-ai/vertex-sdk')).AnthropicVertex;
563
542
  process.env['GOOGLE_APPLICATION_CREDENTIALS'] = options.credentials;
564
543
  process.env['ANTHROPIC_VERTEX_PROJECT_ID'] = options.projectId;
565
- ais[id] = {
566
- id, provider, model,
544
+ ais.push({
545
+ id, provider, model, priority: 0, initOrder: ais.length,
567
546
  client: new AnthropicVertex({ region: options?.region || 'us-east5' }),
568
547
  prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
569
- };
548
+ });
570
549
  break;
571
550
  case OLLAMA:
572
551
  // https://github.com/ollama/ollama/blob/main/docs/openai.md
573
552
  const baseURL = 'http://localhost:11434/v1/';
574
- ais[id] = {
575
- id, provider, model, client: await OpenAI({
576
- baseURL, apiKey: 'ollama', ...options
577
- }),
553
+ ais.push({
554
+ id, provider, model, priority: 0, initOrder: ais.length,
555
+ client: await OpenAI({ baseURL, apiKey: 'ollama', ...options }),
578
556
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
579
- };
557
+ });
580
558
  const phLog = m => log(`Ollama preheat: ${m?.message || m}`);
581
559
  ignoreErrFunc(async () => {
582
560
  phLog(await (await fetch(`${baseURL}completions`, {
@@ -589,15 +567,33 @@ const init = async (options = {}) => {
589
567
  default:
590
568
  throwError(`Invalid AI provider: ${options.provider || 'null'}.`);
591
569
  }
592
- return ais[id];
570
+ ais.sort((a, b) => a.priority - b.priority || a.initOrder - b.initOrder);
571
+ return ais.find(x => x.id === id);
593
572
  };
594
573
 
595
- const getAi = async (id, options) => {
574
+ const getAi = async (id, options = {}) => {
596
575
  if (id) {
597
- if (ais[id]) { return options?.client ? ais[id]?.client : ais[id]; }
598
- else { throwError(`AI not found: ${id}.`); }
576
+ const ai = ais.find(x => x.id === id);
577
+ assert(ais, `AI not found: ${id}.`);
578
+ return options?.client ? ai?.client : ai;
579
+ } else if (options?.select) {
580
+ const res = [];
581
+ for (let x of ais) {
582
+ let select = true;
583
+ for (let i in options.select) {
584
+ if (options.select[i] && i !== 'fast' && !x.model[i]) {
585
+ select = false; break;
586
+ }
587
+ }
588
+ select && (res.push(x));
589
+ }
590
+ const best = options.select?.fast ? res.filter(x => x.model.fast) : res;
591
+ if (best.length) { return options.all ? best : best[0]; }
592
+ assert(res.length, 'AI not found.');
593
+ log(`Best match AI not found, fallbacked: ${JSON.stringify(options.select)}.`);
594
+ return options.all ? res : res[0];
599
595
  }
600
- return ais;
596
+ return options.all ? ais : ais[0];
601
597
  };
602
598
 
603
599
  const countTokens = async (input, options) => {
@@ -822,11 +818,11 @@ const buildPrompts = async (model, input, options = {}) => {
822
818
  ...model?.supportedMimeTypes || [], ...model.supportedAudioTypes || []
823
819
  ].includes(x.mime_type));
824
820
  switch (options.flavor) {
825
- case CHATGPT:
821
+ case OPENAI:
826
822
  systemPrompt = buildGptMessage(options.systemPrompt, _system);
827
823
  prompt = buildGptMessage(content, options);
828
824
  break;
829
- case CLAUDE:
825
+ case ANTHROPIC:
830
826
  systemPrompt = options.systemPrompt;
831
827
  prompt = buildClaudeMessage(content, { ...options, cache_control: true });
832
828
  break;
@@ -841,11 +837,11 @@ const buildPrompts = async (model, input, options = {}) => {
841
837
  history = [];
842
838
  (options.messages?.length ? options.messages : []).map((x, i) => {
843
839
  switch (options.flavor) {
844
- case CHATGPT:
840
+ case OPENAI:
845
841
  history.push(buildGptMessage(x.request, _user));
846
842
  history.push(buildGptMessage(x.response, _assistant));
847
843
  break;
848
- case CLAUDE:
844
+ case ANTHROPIC:
849
845
  history.push(buildClaudeMessage(x.request, _user));
850
846
  history.push(buildClaudeMessage(x.response, _assistant));
851
847
  break;
@@ -857,13 +853,13 @@ const buildPrompts = async (model, input, options = {}) => {
857
853
  }
858
854
  });
859
855
  switch (options.flavor) {
860
- case CHATGPT:
856
+ case OPENAI:
861
857
  history = messages([
862
858
  systemPrompt, ...history, prompt,
863
859
  ...options.toolsResult?.length ? options.toolsResult : []
864
860
  ]);
865
861
  break;
866
- case CLAUDE:
862
+ case ANTHROPIC:
867
863
  history = messages([
868
864
  ...history, prompt,
869
865
  ...options.toolsResult?.length ? options.toolsResult : []
@@ -889,7 +885,7 @@ const buildPrompts = async (model, input, options = {}) => {
889
885
  content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
890
886
  }
891
887
  }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
892
- if ([CHATGPT].includes(options.flavor) || options.model === GEMMA_3_27B) {
888
+ if ([OPENAI].includes(options.flavor) || options.model === GEMMA_3_27B) {
893
889
  systemPrompt = null;
894
890
  }
895
891
  return { systemPrompt, history, prompt };
@@ -910,13 +906,13 @@ const handleToolsCall = async (msg, options) => {
910
906
  const calls = msg.tool_calls || msg.content || msg.parts || [];
911
907
  if (calls.length) {
912
908
  switch (options?.flavor) {
913
- case CLAUDE: preRes.push(msg); break;
909
+ case ANTHROPIC: preRes.push(msg); break;
914
910
  case GEMINI: preRes.push(msg); break;
915
- case CHATGPT: default: preRes.push(msg); break;
911
+ case OPENAI: default: preRes.push(msg); break;
916
912
  }
917
913
  for (const fn of calls) {
918
914
  switch (options?.flavor) {
919
- case CLAUDE:
915
+ case ANTHROPIC:
920
916
  input = fn.input = String.isString(fn?.input)
921
917
  ? parseJson(fn.input) : fn?.input;
922
918
  packMsg = (content, is_error) => ({
@@ -935,7 +931,7 @@ const handleToolsCall = async (msg, options) => {
935
931
  }
936
932
  });
937
933
  break;
938
- case CHATGPT: default:
934
+ case OPENAI: default:
939
935
  input = parseJson(fn?.function?.arguments);
940
936
  packMsg = (content = '', e = false) => ({
941
937
  role: TOOL, tool_call_id: fn.id,
@@ -972,7 +968,7 @@ const handleToolsCall = async (msg, options) => {
972
968
  }
973
969
  if (content.length) {
974
970
  switch (options?.flavor) {
975
- case CLAUDE: content = [{ role: user, content }]; break;
971
+ case ANTHROPIC: content = [{ role: user, content }]; break;
976
972
  case GEMINI: content = [{ role: FUNC, parts: content }]; break;
977
973
  }
978
974
  }
@@ -994,7 +990,7 @@ const promptOpenAI = async (aiId, content, options = {}) => {
994
990
  options.result ?? '', Buffer.alloc(0), null, [], false,
995
991
  provider === AZURE
996
992
  ];
997
- options.flavor = CHATGPT;
993
+ options.flavor = OPENAI;
998
994
  options.model = options.model || model.name;
999
995
  const { history }
1000
996
  = await buildPrompts(MODELS[options.model], content, options);
@@ -1073,7 +1069,7 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1073
1069
  + '46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1074
1070
  ); // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1075
1071
  const { systemPrompt: system, history }
1076
- = await buildPrompts(model, content, { ...options, flavor: CLAUDE });
1072
+ = await buildPrompts(model, content, { ...options, flavor: ANTHROPIC });
1077
1073
  const resp = await client.beta.messages.create({
1078
1074
  model: options.model, ...history, system, stream: true,
1079
1075
  max_tokens: options.extendedThinking ? 128000 : model.maxOutputTokens,
@@ -1124,7 +1120,7 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1124
1120
  ]
1125
1121
  };
1126
1122
  const { toolsResult, toolsResponse } = await handleToolsCall(
1127
- event, { ...options, result, flavor: CLAUDE },
1123
+ event, { ...options, result, flavor: ANTHROPIC },
1128
1124
  );
1129
1125
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1130
1126
  return await promptAnthropic(aiId, content, {
@@ -1326,7 +1322,7 @@ const listGptFineTuningEvents = async (aiId, job_id, options) => {
1326
1322
 
1327
1323
  const tailGptFineTuningEvents = async (aiId, job_id, options) => {
1328
1324
  assert(job_id, 'Job ID is required.');
1329
- const [loopName, listOpts] = [`GPT - ${job_id} `, {
1325
+ const [loopName, listOpts] = [`GPT-${job_id}`, {
1330
1326
  ...options, params: { ...options?.params, order: 'ascending' }
1331
1327
  }];
1332
1328
  let lastEvent;
@@ -1344,33 +1340,23 @@ const tailGptFineTuningEvents = async (aiId, job_id, options) => {
1344
1340
  }, 3, 2, 1, loopName, { silent, ...options });
1345
1341
  };
1346
1342
 
1347
- const initChat = async (options) => {
1348
- options = {
1349
- engines: options?.engines || { [DEFAULT_MODELS[CHAT]]: {} },
1350
- ...options || {},
1351
- };
1352
- if (options?.sessions) {
1343
+ const initChat = async (options = {}) => {
1344
+ if (options.sessions) {
1353
1345
  assert(
1354
1346
  options.sessions?.get && options.sessions?.set,
1355
1347
  'Invalid session storage provider.'
1356
1348
  );
1357
1349
  chatConfig.sessions = options.sessions;
1358
1350
  }
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
- );
1351
+ options.instructions && (chatConfig.systemPrompt = options.instructions);
1352
+ // Use Gemini instead of ChatGPT because of the longer package.
1353
+ const [spTokens, ais] = await Promise.all([countTokens([buildGeminiHistory(
1354
+ chatConfig.systemPrompt, { role: system }
1355
+ )]), getAi(null, { all: true })]);
1356
+ for (const ai of ais) {
1357
+ const mxPmpt = ai.model.maxInputTokens / 2;
1358
+ assert(spTokens < mxPmpt,
1359
+ `System prompt is too long: ${spTokens} / ${mxPmpt} tokens.`);
1374
1360
  }
1375
1361
  return chatConfig;
1376
1362
  };
@@ -1406,53 +1392,30 @@ const resetSession = async (sessionId, options) => {
1406
1392
  return await setSession(sessionId, session);
1407
1393
  };
1408
1394
 
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
- }
1395
+ const talk = async (input, options = {}) => {
1396
+ let [chat, sessionId] =
1397
+ [{ request: input || ATTACHMENTS }, options.sessionId || newSessionId()];
1398
+ const session = await getSession(sessionId, options);
1399
+ const resp = await prompt(input, {
1400
+ messages: session.messages, log: true, ...options,
1401
+ });
1443
1402
  chat.response = resp.text;
1444
1403
  chat.request && chat.response && session.messages.push(chat);
1445
1404
  await setSession(sessionId, session, options);
1446
- return { sessionId, ...packResult(resp) };
1405
+ return {
1406
+ sessionId, ...resp, spoken: renderText(
1407
+ resp.text, { noCode: true, noLink: true }
1408
+ ).replace(/\[\^\d\^\]/ig, ''),
1409
+ };
1447
1410
  };
1448
1411
 
1449
- const getMaxChatPromptLimit = (options) => {
1412
+ const getMaxChatPromptLimit = async (options) => {
1450
1413
  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;
1414
+ (await getAi(null, { all: true })).map(x => {
1415
+ if (options?.aiId && options?.aiId !== x.id) { return; }
1416
+ const maxInputTokens = x.model.maxInputTokens;
1454
1417
  resp = resp ? Math.min(resp, maxInputTokens) : maxInputTokens;
1455
- }
1418
+ });
1456
1419
  assert(resp > 0, 'Chat engine has not been initialized.');
1457
1420
  return options?.raw ? resp : Math.min(resp, MAX_INPUT_TOKENS);
1458
1421
  };
@@ -1486,28 +1449,17 @@ const distillFile = async (attachments, o) => {
1486
1449
  attachments = await Promise.all(attachments);
1487
1450
  // print(attachments);
1488
1451
  return await prompt(strPmt, {
1489
- fast: true, multimodal: true, simple: true, ...o, attachments,
1452
+ simple: true, select: { vision: true, fast: true }, ...o, attachments,
1490
1453
  });
1491
1454
  };
1492
1455
 
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.');
1456
+ const prompt = async (input, options = {}) => {
1457
+ const ai = await getAi(options?.aiId, options);
1458
+ const tag = `${ai.provider} (${ai.model.name})`;
1459
+ options.log && log(`Prompt ${tag}: ${JSON.stringify(input)}`);
1460
+ const resp = await ai.prompt(input, options);
1461
+ options.log && log(`Response ${tag}: ${JSON.stringify(resp.text)}`);
1462
+ return resp;
1511
1463
  };
1512
1464
 
1513
1465
  const trimPrompt = async (getPrompt, trimFunc, contextWindow, options) => {
@@ -1549,23 +1501,16 @@ const analyzeSessions = async (sessionIds, options) => {
1549
1501
  x, JSON.stringify(sses[x]).length,
1550
1502
  ]).sort((x, y) => y[1] - x[1])[0][0]];
1551
1503
  }
1552
- }, getMaxChatPromptLimit(options));
1504
+ }, await getMaxChatPromptLimit(options));
1553
1505
  const aiResp = Object.keys(sses) ? (await prompt(getInput(), {
1554
- jsonMode: true, fast: true, simple: true, ...options || {}
1506
+ jsonMode: true, simple: true, select: { json: true, fase: true },
1507
+ ...options || {}
1555
1508
  })) : {};
1556
1509
  assert(aiResp, 'Unable to analyze sessions.');
1557
1510
  ids.map(x => resp[x] = aiResp[x] || null);
1558
1511
  return Array.isArray(sessionIds) ? resp : resp[sessionIds[0]];
1559
1512
  };
1560
1513
 
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
1514
  export default init;
1570
1515
  export {
1571
1516
  ATTACHMENT_TOKEN_COST, CLOUD_37_SONNET, CODE_INTERPRETER, DEEPSEEK_R1,