utilitas 1998.2.64 → 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
@@ -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) => {
@@ -702,12 +694,8 @@ const buildGeminiHistory = (text, options) => buildGeminiMessage(
702
694
  text, { ...options || {}, history: true }
703
695
  );
704
696
 
705
- const [getOpenAIClient] = [OPENAI].map(
706
- x => async options => await init({ ...provider(x), ...options })
707
- );
708
-
709
- const listOpenAIModels = async (options) => {
710
- const { client } = await getOpenAIClient(options);
697
+ const listOpenAIModels = async (aiId, options) => {
698
+ const { client } = await getAi(aiId);
711
699
  const resp = await client.models.list();
712
700
  return options?.raw ? resp : resp.data;
713
701
  };
@@ -826,11 +814,11 @@ const buildPrompts = async (model, input, options = {}) => {
826
814
  ...model?.supportedMimeTypes || [], ...model.supportedAudioTypes || []
827
815
  ].includes(x.mime_type));
828
816
  switch (options.flavor) {
829
- case CHATGPT:
817
+ case OPENAI:
830
818
  systemPrompt = buildGptMessage(options.systemPrompt, _system);
831
819
  prompt = buildGptMessage(content, options);
832
820
  break;
833
- case CLAUDE:
821
+ case ANTHROPIC:
834
822
  systemPrompt = options.systemPrompt;
835
823
  prompt = buildClaudeMessage(content, { ...options, cache_control: true });
836
824
  break;
@@ -845,11 +833,11 @@ const buildPrompts = async (model, input, options = {}) => {
845
833
  history = [];
846
834
  (options.messages?.length ? options.messages : []).map((x, i) => {
847
835
  switch (options.flavor) {
848
- case CHATGPT:
836
+ case OPENAI:
849
837
  history.push(buildGptMessage(x.request, _user));
850
838
  history.push(buildGptMessage(x.response, _assistant));
851
839
  break;
852
- case CLAUDE:
840
+ case ANTHROPIC:
853
841
  history.push(buildClaudeMessage(x.request, _user));
854
842
  history.push(buildClaudeMessage(x.response, _assistant));
855
843
  break;
@@ -861,13 +849,13 @@ const buildPrompts = async (model, input, options = {}) => {
861
849
  }
862
850
  });
863
851
  switch (options.flavor) {
864
- case CHATGPT:
852
+ case OPENAI:
865
853
  history = messages([
866
854
  systemPrompt, ...history, prompt,
867
855
  ...options.toolsResult?.length ? options.toolsResult : []
868
856
  ]);
869
857
  break;
870
- case CLAUDE:
858
+ case ANTHROPIC:
871
859
  history = messages([
872
860
  ...history, prompt,
873
861
  ...options.toolsResult?.length ? options.toolsResult : []
@@ -893,7 +881,7 @@ const buildPrompts = async (model, input, options = {}) => {
893
881
  content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
894
882
  }
895
883
  }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
896
- if ([CHATGPT].includes(options.flavor) || options.model === GEMMA_3_27B) {
884
+ if ([OPENAI].includes(options.flavor) || options.model === GEMMA_3_27B) {
897
885
  systemPrompt = null;
898
886
  }
899
887
  return { systemPrompt, history, prompt };
@@ -914,13 +902,13 @@ const handleToolsCall = async (msg, options) => {
914
902
  const calls = msg.tool_calls || msg.content || msg.parts || [];
915
903
  if (calls.length) {
916
904
  switch (options?.flavor) {
917
- case CLAUDE: preRes.push(msg); break;
905
+ case ANTHROPIC: preRes.push(msg); break;
918
906
  case GEMINI: preRes.push(msg); break;
919
- case CHATGPT: default: preRes.push(msg); break;
907
+ case OPENAI: default: preRes.push(msg); break;
920
908
  }
921
909
  for (const fn of calls) {
922
910
  switch (options?.flavor) {
923
- case CLAUDE:
911
+ case ANTHROPIC:
924
912
  input = fn.input = String.isString(fn?.input)
925
913
  ? parseJson(fn.input) : fn?.input;
926
914
  packMsg = (content, is_error) => ({
@@ -939,7 +927,7 @@ const handleToolsCall = async (msg, options) => {
939
927
  }
940
928
  });
941
929
  break;
942
- case CHATGPT: default:
930
+ case OPENAI: default:
943
931
  input = parseJson(fn?.function?.arguments);
944
932
  packMsg = (content = '', e = false) => ({
945
933
  role: TOOL, tool_call_id: fn.id,
@@ -976,7 +964,7 @@ const handleToolsCall = async (msg, options) => {
976
964
  }
977
965
  if (content.length) {
978
966
  switch (options?.flavor) {
979
- case CLAUDE: content = [{ role: user, content }]; break;
967
+ case ANTHROPIC: content = [{ role: user, content }]; break;
980
968
  case GEMINI: content = [{ role: FUNC, parts: content }]; break;
981
969
  }
982
970
  }
@@ -998,7 +986,7 @@ const promptOpenAI = async (aiId, content, options = {}) => {
998
986
  options.result ?? '', Buffer.alloc(0), null, [], false,
999
987
  provider === AZURE
1000
988
  ];
1001
- options.flavor = CHATGPT;
989
+ options.flavor = OPENAI;
1002
990
  options.model = options.model || model.name;
1003
991
  const { history }
1004
992
  = await buildPrompts(MODELS[options.model], content, options);
@@ -1077,7 +1065,7 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1077
1065
  + '46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1078
1066
  ); // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1079
1067
  const { systemPrompt: system, history }
1080
- = await buildPrompts(model, content, { ...options, flavor: CLAUDE });
1068
+ = await buildPrompts(model, content, { ...options, flavor: ANTHROPIC });
1081
1069
  const resp = await client.beta.messages.create({
1082
1070
  model: options.model, ...history, system, stream: true,
1083
1071
  max_tokens: options.extendedThinking ? 128000 : model.maxOutputTokens,
@@ -1128,7 +1116,7 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1128
1116
  ]
1129
1117
  };
1130
1118
  const { toolsResult, toolsResponse } = await handleToolsCall(
1131
- event, { ...options, result, flavor: CLAUDE },
1119
+ event, { ...options, result, flavor: ANTHROPIC },
1132
1120
  );
1133
1121
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1134
1122
  return await promptAnthropic(aiId, content, {
@@ -1139,8 +1127,8 @@ const promptAnthropic = async (aiId, content, options = {}) => {
1139
1127
  return packResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
1140
1128
  };
1141
1129
 
1142
- const uploadFile = async (input, options) => {
1143
- const { client } = await getOpenAIClient(options);
1130
+ const uploadFile = async (aiId, input, options) => {
1131
+ const { client } = await getAi(aiId);
1144
1132
  const { content: file, cleanup } = await convert(input, {
1145
1133
  input: options?.input, ...options || {}, expected: STREAM,
1146
1134
  errorMessage: INVALID_FILE, suffix: options?.suffix,
@@ -1151,20 +1139,20 @@ const uploadFile = async (input, options) => {
1151
1139
  return resp;
1152
1140
  };
1153
1141
 
1154
- const uploadFileForFineTuning = async (content, options) => await uploadFile(
1155
- content, { suffix: 'jsonl', ...options, params: { purpose: 'fine-tune' } }
1142
+ const uploadFileForFineTuning = async (aiId, content, options) => await uploadFile(
1143
+ aiId, content, { suffix: 'jsonl', ...options, params: { purpose: 'fine-tune' } }
1156
1144
  );
1157
1145
 
1158
- const listFiles = async (options) => {
1159
- const { client } = await getOpenAIClient(options);
1146
+ const listFiles = async (aiId, options) => {
1147
+ const { client } = await getAi(aiId);
1160
1148
  const files = [];
1161
1149
  const list = await client.files.list(options?.params || {});
1162
1150
  for await (const file of list) { files.push(file); }
1163
1151
  return files;
1164
1152
  };
1165
1153
 
1166
- const deleteFile = async (file_id, options) => {
1167
- const { client } = await getOpenAIClient(options);
1154
+ const deleteFile = async (aiId, file_id, options) => {
1155
+ const { client } = await getAi(aiId);
1168
1156
  return await client.files.del(file_id);
1169
1157
  };
1170
1158
 
@@ -1294,48 +1282,48 @@ const buildGptTrainingCases = (cases, opts) => cases.map(x => JSON.stringify(
1294
1282
  buildGptTrainingCase(x.prompt, x.response, { ...x.options, ...opts })
1295
1283
  )).join('\n');
1296
1284
 
1297
- const createGptFineTuningJob = async (training_file, options) => {
1298
- const { client } = await getOpenAIClient(options);
1285
+ const createGptFineTuningJob = async (aiId, training_file, options) => {
1286
+ const { client } = await getAi(aiId);
1299
1287
  return await client.fineTuning.jobs.create({
1300
1288
  training_file, model: options?.model || DEFAULT_MODELS[OPENAI_TRAINING],
1301
1289
  })
1302
1290
  };
1303
1291
 
1304
- const getGptFineTuningJob = async (job_id, options) => {
1305
- const { client } = await getOpenAIClient(options);
1292
+ const getGptFineTuningJob = async (aiId, job_id, options) => {
1293
+ const { client } = await getAi(aiId);
1306
1294
  // https://platform.openai.com/finetune/[job_id]?filter=all
1307
1295
  return await client.fineTuning.jobs.retrieve(job_id);
1308
1296
  };
1309
1297
 
1310
- const cancelGptFineTuningJob = async (job_id, options) => {
1311
- const { client } = await getOpenAIClient(options);
1298
+ const cancelGptFineTuningJob = async (aiId, job_id, options) => {
1299
+ const { client } = await getAi(aiId);
1312
1300
  return await client.fineTuning.jobs.cancel(job_id);
1313
1301
  };
1314
1302
 
1315
- const listGptFineTuningJobs = async (options) => {
1316
- const { client } = await getOpenAIClient(options);
1303
+ const listGptFineTuningJobs = async (aiId, options) => {
1304
+ const { client } = await getAi(aiId);
1317
1305
  const resp = await client.fineTuning.jobs.list({
1318
1306
  limit: GPT_QUERY_LIMIT, ...options?.params
1319
1307
  });
1320
1308
  return options?.raw ? resp : resp.data;
1321
1309
  };
1322
1310
 
1323
- const listGptFineTuningEvents = async (job_id, options) => {
1324
- const { client } = await getOpenAIClient(options);
1311
+ const listGptFineTuningEvents = async (aiId, job_id, options) => {
1312
+ const { client } = await getAi(aiId);
1325
1313
  const resp = await client.fineTuning.jobs.listEvents(job_id, {
1326
1314
  limit: GPT_QUERY_LIMIT, ...options?.params,
1327
1315
  });
1328
1316
  return options?.raw ? resp : resp.data;
1329
1317
  };
1330
1318
 
1331
- const tailGptFineTuningEvents = async (job_id, options) => {
1319
+ const tailGptFineTuningEvents = async (aiId, job_id, options) => {
1332
1320
  assert(job_id, 'Job ID is required.');
1333
- const [loopName, listOpts] = [`GPT - ${job_id} `, {
1321
+ const [loopName, listOpts] = [`GPT-${job_id}`, {
1334
1322
  ...options, params: { ...options?.params, order: 'ascending' }
1335
1323
  }];
1336
1324
  let lastEvent;
1337
1325
  return await loop(async () => {
1338
- const resp = await listGptFineTuningEvents(job_id, {
1326
+ const resp = await listGptFineTuningEvents(aiId, job_id, {
1339
1327
  ...listOpts, params: {
1340
1328
  ...listOpts?.params,
1341
1329
  ...(lastEvent ? { after: lastEvent.id } : {}),
@@ -1348,33 +1336,23 @@ const tailGptFineTuningEvents = async (job_id, options) => {
1348
1336
  }, 3, 2, 1, loopName, { silent, ...options });
1349
1337
  };
1350
1338
 
1351
- const initChat = async (options) => {
1352
- options = {
1353
- engines: options?.engines || { [DEFAULT_MODELS[CHAT]]: {} },
1354
- ...options || {},
1355
- };
1356
- if (options?.sessions) {
1339
+ const initChat = async (options = {}) => {
1340
+ if (options.sessions) {
1357
1341
  assert(
1358
1342
  options.sessions?.get && options.sessions?.set,
1359
1343
  'Invalid session storage provider.'
1360
1344
  );
1361
1345
  chatConfig.sessions = options.sessions;
1362
1346
  }
1363
- options?.instructions && (chatConfig.systemPrompt = options.instructions);
1364
- for (const i in options.engines) {
1365
- const key = ensureString(i, { case: 'UP' });
1366
- const model = DEFAULT_MODELS[key];
1367
- assert(model, `Invalid chat model: '${i}'.`);
1368
- chatConfig.engines[key] = options.engines[i];
1369
- chatConfig.engines[key].model = chatConfig.engines[key].model || model;
1370
- const mxPmpt = MODELS[chatConfig.engines[key].model].maxInputTokens / 2;
1371
- const pmptTokens = await countTokens([buildGeminiHistory(
1372
- chatConfig.systemPrompt, { role: system }
1373
- )]); // Use Gemini instead of ChatGPT because of the longer pack
1374
- assert(
1375
- pmptTokens < mxPmpt,
1376
- `System prompt is too long: ${pmptTokens} / ${mxPmpt} tokens.`
1377
- );
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.`);
1378
1356
  }
1379
1357
  return chatConfig;
1380
1358
  };
@@ -1410,53 +1388,30 @@ const resetSession = async (sessionId, options) => {
1410
1388
  return await setSession(sessionId, session);
1411
1389
  };
1412
1390
 
1413
- const packResult = resp => {
1414
- const result = {
1415
- ...resp, spoken: renderText(
1416
- resp.text, { noCode: true, noLink: true }
1417
- ).replace(/\[\^\d\^\]/ig, ''),
1418
- };
1419
- log(`Response (${result.model}): ${JSON.stringify(result.text)}`);
1420
- // log(result);
1421
- return result;
1422
- };
1423
-
1424
- const talk = async (input, options) => {
1425
- let [engine, chat, resp, sessionId] = [
1426
- unifyEngine({
1427
- engine: Object.keys(chatConfig.engines)?.[0] || DEFAULT_MODELS[CHAT],
1428
- ...options,
1429
- }), { request: input || ATTACHMENTS }, null,
1430
- options?.sessionId || newSessionId(),
1431
- ];
1432
- assert(chatConfig.engines[engine], NOT_INIT);
1433
- const session = await getSession(sessionId, { engine, ...options });
1434
- log(`Prompt (${engine}): ${JSON.stringify(input)}`);
1435
- const pmtOptions = {
1436
- messages: session.messages, model: chatConfig.engines[engine].model,
1437
- ...options,
1438
- };
1439
- switch (engine) {
1440
- case CHATGPT: resp = await promptOpenAI(input, pmtOptions); break;
1441
- case GEMINI: resp = await promptGemini(input, pmtOptions); break;
1442
- case CLAUDE: resp = await promptAnthropic(input, pmtOptions); break;
1443
- // case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1444
- case AZURE: resp = await promptAzure(input, pmtOptions); break;
1445
- default: throwError(`Invalid AI engine: '${engine}'.`);
1446
- }
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
+ });
1447
1398
  chat.response = resp.text;
1448
1399
  chat.request && chat.response && session.messages.push(chat);
1449
1400
  await setSession(sessionId, session, options);
1450
- return { sessionId, ...packResult(resp) };
1401
+ return {
1402
+ sessionId, ...resp, spoken: renderText(
1403
+ resp.text, { noCode: true, noLink: true }
1404
+ ).replace(/\[\^\d\^\]/ig, ''),
1405
+ };
1451
1406
  };
1452
1407
 
1453
- const getMaxChatPromptLimit = (options) => {
1408
+ const getMaxChatPromptLimit = async (options) => {
1454
1409
  let resp = 0;
1455
- for (const i in chatConfig.engines) {
1456
- if (options?.engine && i !== options.engine) { continue; }
1457
- 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;
1458
1413
  resp = resp ? Math.min(resp, maxInputTokens) : maxInputTokens;
1459
- }
1414
+ });
1460
1415
  assert(resp > 0, 'Chat engine has not been initialized.');
1461
1416
  return options?.raw ? resp : Math.min(resp, MAX_INPUT_TOKENS);
1462
1417
  };
@@ -1490,28 +1445,17 @@ const distillFile = async (attachments, o) => {
1490
1445
  attachments = await Promise.all(attachments);
1491
1446
  // print(attachments);
1492
1447
  return await prompt(strPmt, {
1493
- fast: true, multimodal: true, simple: true, ...o, attachments,
1448
+ simple: true, select: { vision: true, fast: true }, ...o, attachments,
1494
1449
  });
1495
1450
  };
1496
1451
 
1497
- const prompt = async (input, options) => {
1498
- let egn = options?.engine && unifyEngine(options);
1499
- const engines = PREFERRED_ENGINES.slice();
1500
- options?.multimodal && engines.sort((x, y) => x.multimodal - y.multimodal);
1501
- for (const engine of engines) {
1502
- if ((egn ? engine.client === egn : true) && clients[engine.client]) {
1503
- const extra = {};
1504
- if (engine.client === OPENAI) {
1505
- if (options?.fast) {
1506
- extra.model = DEFAULT_MODELS[CHATGPT_MINI];
1507
- } else if (options?.reasoning) {
1508
- extra.model = DEFAULT_MODELS[CHATGPT_REASONING];
1509
- }
1510
- }
1511
- return await engine.func(input, { ...extra, ...options || {} });
1512
- }
1513
- }
1514
- 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;
1515
1459
  };
1516
1460
 
1517
1461
  const trimPrompt = async (getPrompt, trimFunc, contextWindow, options) => {
@@ -1553,23 +1497,16 @@ const analyzeSessions = async (sessionIds, options) => {
1553
1497
  x, JSON.stringify(sses[x]).length,
1554
1498
  ]).sort((x, y) => y[1] - x[1])[0][0]];
1555
1499
  }
1556
- }, getMaxChatPromptLimit(options));
1500
+ }, await getMaxChatPromptLimit(options));
1557
1501
  const aiResp = Object.keys(sses) ? (await prompt(getInput(), {
1558
- jsonMode: true, fast: true, simple: true, ...options || {}
1502
+ jsonMode: true, simple: true, select: { json: true, fase: true },
1503
+ ...options || {}
1559
1504
  })) : {};
1560
1505
  assert(aiResp, 'Unable to analyze sessions.');
1561
1506
  ids.map(x => resp[x] = aiResp[x] || null);
1562
1507
  return Array.isArray(sessionIds) ? resp : resp[sessionIds[0]];
1563
1508
  };
1564
1509
 
1565
- const PREFERRED_ENGINES = [
1566
- { client: OPENAI, func: promptOpenAI, multimodal: 0 },
1567
- { client: GEMINI, func: promptGemini, multimodal: 1 },
1568
- { client: CLAUDE, func: promptAnthropic, multimodal: 2 },
1569
- // { client: AZURE, func: promptAzure, multimodal: 3 },
1570
- // { client: OLLAMA, func: promptOllama, multimodal: 99 },
1571
- ]; // keep gpt first to avoid gemini grounding by default
1572
-
1573
1510
  export default init;
1574
1511
  export {
1575
1512
  ATTACHMENT_TOKEN_COST, CLOUD_37_SONNET, CODE_INTERPRETER, DEEPSEEK_R1,