utilitas 1999.1.17 → 1999.1.18

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
@@ -65,8 +65,8 @@ const [
65
65
  name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent,
66
66
  GEMINI_EMBEDDING_M, INVALID_FILE, tokenSafeRatio, GPT_QUERY_LIMIT,
67
67
  CONTENT_IS_REQUIRED, OPENAI_HI_RES_SIZE, k, kT, m, minute, hour,
68
- gb, trimTailing, EBD, GEMINI_20_FLASH_EXP, IMAGE, JINA_DEEPSEARCH,
69
- JINA_DEEPSEARCH_M, JINA_EMBEDDING, JINA_CLIP,
68
+ gb, trimTailing, EBD, GEMINI_20_FLASH_EXP, IMAGE, JINA, JINA_DEEPSEARCH,
69
+ JINA_EMBEDDING, JINA_CLIP,
70
70
  ] = [
71
71
  'OpenAI', 'Gemini', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
72
72
  'OPENAI_TRAINING', 'Ollama', 'gpt-4o-mini', 'gpt-4o', 'o1', 'o3-mini',
@@ -84,7 +84,7 @@ const [
84
84
  'Content is required.', 2000 * 768, x => 1024 * x, x => 1000 * x,
85
85
  x => 1024 * 1024 * x, x => 60 * x, x => 60 * 60 * x,
86
86
  x => 1024 * 1024 * 1024 * x, x => x.replace(/[\.\s]*$/, ''),
87
- { embedding: true }, 'gemini-2.0-flash-exp', 'image', 'Jina Deepsearch',
87
+ { embedding: true }, 'gemini-2.0-flash-exp', 'image', 'Jina',
88
88
  'jina-deepsearch-v1', 'JINA_EMBEDDING', 'jina-clip-v2',
89
89
  ];
90
90
 
@@ -92,11 +92,9 @@ const [tool, messages, text]
92
92
  = [type => ({ type }), messages => ({ messages }), text => ({ text })];
93
93
  const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
94
94
  = ['code_interpreter', 'retrieval', FUNC].map(tool);
95
- const [sessionType, aiType]
96
- = [`${name.toUpperCase()}-SESSION`, `${name.toUpperCase()}-AI`];
97
- const [newSessionId, newAiId]
98
- = [sessionType, aiType].map(type => () => createUoid({ type }));
99
95
  const _NO_RENDER = ['INSTRUCTIONS', 'MODELS', 'DEFAULT_MODELS'];
96
+ const sessionType = `${name.toUpperCase()}-SESSION`;
97
+ const newSessionId = () => createUoid({ type: sessionType });
100
98
  const chatConfig = { sessions: new Map(), systemPrompt: INSTRUCTIONS };
101
99
  const tokenSafe = count => Math.ceil(count * tokenSafeRatio);
102
100
  const renderText = (t, o) => _renderText(t, { extraCodeBlock: 0, ...o || {} });
@@ -117,7 +115,7 @@ const OPENAI_RULES = {
117
115
  imageCostTokens: ~~(OPENAI_HI_RES_SIZE / (512 * 512) * 170 + 85),
118
116
  maxFileSize: m(20), maxImageSize: OPENAI_HI_RES_SIZE,
119
117
  supportedMimeTypes: [png, jpeg, gif, webp],
120
- json: true, tools: true, vision: true,
118
+ json: true, tools: true, vision: true, defaultProvider: OPENAI,
121
119
  };
122
120
 
123
121
  const GEMINI_RULES = {
@@ -128,7 +126,7 @@ const GEMINI_RULES = {
128
126
  maxVideoPerPrompt: 10, vision: true, supportedMimeTypes: [
129
127
  png, jpeg, mov, mpeg, mp4, mpg, avi, wmv, mpegps, flv, pdf, aac,
130
128
  flac, mp3, m4a, mpga, opus, pcm, wav, webm, tgpp,
131
- ],
129
+ ], defaultProvider: GEMINI,
132
130
  };
133
131
 
134
132
  // https://platform.openai.com/docs/models
@@ -164,13 +162,13 @@ const MODELS = {
164
162
  contextWindow: kT(128), maxOutputTokens: k(8),
165
163
  imageCostTokens: 256, maxImageSize: 896 * 896,
166
164
  supportedMimeTypes: [png, jpeg, gif],
167
- fast: true, json: true, vision: true,
165
+ fast: true, json: true, vision: true, defaultProvider: GEMINI,
168
166
  },
169
- [JINA_DEEPSEARCH_M]: {
167
+ [JINA_DEEPSEARCH]: {
170
168
  contextWindow: Infinity, maxInputTokens: Infinity,
171
169
  maxOutputTokens: Infinity, imageCostTokens: 0, maxImageSize: Infinity,
172
170
  supportedMimeTypes: [png, jpeg, MIME_TEXT, webp, pdf],
173
- reasoning: true, json: true, vision: true,
171
+ reasoning: true, json: true, vision: true, defaultProvider: JINA,
174
172
  },
175
173
  [DEEPSEEK_R1]: {
176
174
  contextWindow: kT(128), maxOutputTokens: k(32),
@@ -189,6 +187,7 @@ const MODELS = {
189
187
  maxImagePerPrompt: 100, maxImageSize: 2000 * 2000,
190
188
  supportedMimeTypes: [png, jpeg, gif, webp, pdf],
191
189
  json: true, reasoning: true, tools: true, vision: true,
190
+ defaultProvider: ANTHROPIC,
192
191
  }, // https://docs.anthropic.com/en/docs/build-with-claude/vision
193
192
  };
194
193
 
@@ -207,12 +206,15 @@ for (const n in MODELS) {
207
206
  ) : MODELS[n].imageCostTokens;
208
207
  }
209
208
  }
210
- MODELS[GEMMA327B] = MODELS[GEMMA_3_27B]; // Ollama Alias
209
+
211
210
  MODELS[GEMINI_20_FLASH].image = GEMINI_20_FLASH_EXP;
212
211
  MODELS[GEMINI_20_FLASH_EXP] = {
213
212
  ...MODELS[GEMINI_20_FLASH],
214
213
  name: GEMINI_20_FLASH_EXP, image: true, tools: false,
215
214
  };
215
+ MODELS[GEMMA327B] = { // Ollama Alias
216
+ ...MODELS[GEMMA_3_27B], name: GEMMA327B, defaultProvider: OLLAMA
217
+ };
216
218
 
217
219
  // Default models for each provider
218
220
  const DEFAULT_MODELS = {
@@ -221,7 +223,7 @@ const DEFAULT_MODELS = {
221
223
  [GEMINI]: GEMINI_20_FLASH,
222
224
  [ANTHROPIC]: CLOUD_37_SONNET,
223
225
  [VERTEX_ANTHROPIC]: CLOUD_37_SONNET,
224
- [JINA_DEEPSEARCH]: JINA_DEEPSEARCH_M,
226
+ [JINA]: JINA_DEEPSEARCH,
225
227
  [OLLAMA]: GEMMA327B,
226
228
  [OPENAI_VOICE]: NOVA,
227
229
  [OPENAI_EMBEDDING]: TEXT_EMBEDDING_3_SMALL,
@@ -247,7 +249,7 @@ let tokeniser;
247
249
  const unifyProvider = provider => {
248
250
  assert(provider = (provider || '').trim(), 'AI provider is required.');
249
251
  for (let type of [OPENAI, AZURE_OPENAI, AZURE, GEMINI, ANTHROPIC,
250
- VERTEX_ANTHROPIC, JINA_DEEPSEARCH, OLLAMA]) {
252
+ VERTEX_ANTHROPIC, JINA, OLLAMA]) {
251
253
  if (insensitiveCompare(provider, type)) { return type; }
252
254
  }
253
255
  throwError(`Invalid AI provider: ${provider}.`);
@@ -347,112 +349,153 @@ const toolsGemini = async () => (await toolsOpenAI()).map(x => ({
347
349
  }
348
350
  }));
349
351
 
352
+ const buildAiId = (provider, model) => [provider, model].map(
353
+ x => ensureString(x, { case: 'SNAKE' })
354
+ ).join('_');
355
+
350
356
  const init = async (options = {}) => {
351
- const id = options.id || newAiId();
352
357
  const provider = unifyProvider(options?.provider);
353
- const modelName = options.model || DEFAULT_MODELS[provider];
354
- assert(modelName, `Model is required for provider: ${provider}.`);
355
- let model = options.modelConfig || MODELS[modelName];
356
- assert(model, `The model has not been configured yet: ${modelName}.`);
357
- model = { name: modelName, ...model };
358
+ let models;
359
+ if (options.model === '*') { // All models
360
+ models = Object.values(MODELS).filter(x => x.defaultProvider === provider);
361
+ } else if (options.model) { // Specific model
362
+ models = Object.values(MODELS).filter(x => ensureArray(options.model).includes(x.name));
363
+ } else if (DEFAULT_MODELS[provider]) { // Default model
364
+ models = [MODELS[DEFAULT_MODELS[provider]]];
365
+ } else if (options.modelConfig) {
366
+ models = ensureArray(options.modelConfig);
367
+ }
368
+ assert(models.length,
369
+ `Model name or description is required for provider: ${provider}.`);
358
370
  switch (provider) {
359
371
  case OPENAI:
360
372
  assertApiKey(provider, options);
361
- ais.push({
362
- id, provider, model, priority: 0, initOrder: ais.length,
363
- client: await OpenAI(options),
364
- prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
365
- embedding: async (i, o) => await createOpenAIEmbedding(id, i, o),
366
- });
373
+ var client = await OpenAI(options);
374
+ for (let model of models) {
375
+ var id = buildAiId(provider, model.name);
376
+ ais.push({
377
+ id, provider, model, priority: 0, initOrder: ais.length, client,
378
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
379
+ embedding: async (i, o) => await createOpenAIEmbedding(id, i, o),
380
+ });
381
+ }
367
382
  break;
368
383
  case AZURE_OPENAI:
369
384
  assertApiKey(provider, options);
370
385
  assert(options.endpoint,
371
386
  `${provider} api endpoint and deployment are required.`);
387
+ var model = models[0];
388
+ var client = await AzureOpenAI({
389
+ apiVersion: '2025-01-01-preview',
390
+ deployment: model.name, ...options,
391
+ });
392
+ var id = buildAiId(provider, model.name);
372
393
  ais.push({
373
- id, provider, model, priority: 0, initOrder: ais.length,
374
- client: await AzureOpenAI({
375
- apiVersion: '2025-01-01-preview',
376
- deployment: model.name, ...options,
377
- }),
394
+ id, provider, model, priority: 0, initOrder: ais.length, client,
378
395
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
379
396
  });
380
397
  break;
381
398
  case AZURE:
382
399
  assertApiKey(provider, options);
383
400
  assert(options.baseURL, `${provider} api endpoint is required.`);
401
+ var model = models[0];
402
+ var client = await OpenAI(options);
403
+ var id = buildAiId(provider, model.name);
384
404
  ais.push({
385
- id, provider, model, priority: 0, initOrder: ais.length,
386
- client: await OpenAI(options),
405
+ id, provider, model, priority: 0, initOrder: ais.length, client,
387
406
  prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
388
407
  });
389
408
  break;
390
409
  case GEMINI:
391
410
  assertApiKey(provider, options);
392
411
  const { GoogleGenerativeAI } = await need('@google/generative-ai');
393
- ais.push({
394
- id, provider, model, priority: 0, initOrder: ais.length,
395
- client: new GoogleGenerativeAI(options.apiKey),
396
- prompt: async (cnt, opts) => await promptGemini(id, cnt, opts),
397
- embedding: async (i, o) => await createGeminiEmbedding(id, i, o),
398
- });
412
+ var client = new GoogleGenerativeAI(options.apiKey);
413
+ for (let model of models) {
414
+ var id = buildAiId(provider, model.name);
415
+ ais.push({
416
+ id, provider, model, priority: 0, initOrder: ais.length, client,
417
+ prompt: async (cnt, opts) => await promptGemini(id, cnt, opts),
418
+ embedding: async (i, o) => await createGeminiEmbedding(id, i, o),
419
+ });
420
+ }
399
421
  break;
400
422
  case ANTHROPIC:
401
423
  assertApiKey(provider, options);
402
- const Anthropic = (await need('@anthropic-ai/sdk')).Anthropic;
403
- ais.push({
404
- id, provider, model, priority: 0, initOrder: ais.length,
405
- client: new Anthropic(options),
406
- prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
407
- });
424
+ var client = new ((
425
+ await need('@anthropic-ai/sdk')
426
+ ).Anthropic)(options)
427
+ for (let model of models) {
428
+ var id = buildAiId(provider, model.name);
429
+ ais.push({
430
+ id, provider, model, priority: 0, initOrder: ais.length, client,
431
+ prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
432
+ });
433
+ }
408
434
  break;
409
435
  case VERTEX_ANTHROPIC:
410
436
  // https://github.com/anthropics/anthropic-sdk-typescript/tree/main/packages/vertex-sdk
411
437
  assert(options?.credentials, `${provider} api credentials are required.`);
412
- const AnthropicVertex = (await need('@anthropic-ai/vertex-sdk')).AnthropicVertex;
413
438
  process.env['GOOGLE_APPLICATION_CREDENTIALS'] = options.credentials;
414
439
  process.env['ANTHROPIC_VERTEX_PROJECT_ID'] = options.projectId;
440
+ var model = models[0];
441
+ var client = new ((
442
+ await need('@anthropic-ai/vertex-sdk')
443
+ ).AnthropicVertex)({ region: options?.region || 'us-east5' });
444
+ var id = buildAiId(provider, model.name);
415
445
  ais.push({
416
- id, provider, model, priority: 0, initOrder: ais.length,
417
- client: new AnthropicVertex({ region: options?.region || 'us-east5' }),
446
+ id, provider, model, priority: 0, initOrder: ais.length, client: client,
418
447
  prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
419
448
  });
420
449
  break;
421
- case JINA_DEEPSEARCH:
450
+ case JINA:
422
451
  assertApiKey(provider, options);
423
- ais.push({
424
- id, provider, model, priority: 0, initOrder: ais.length,
425
- client: await OpenAI({
426
- baseURL: 'https://deepsearch.jina.ai/v1/', ...options,
427
- }),
428
- prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
429
- embedding: async (i, o) => await createJinaEmbedding(await OpenAI({
430
- baseURL: 'https://api.jina.ai/v1/', ...options,
431
- }), i, o),
432
- });
452
+ var [client, ebd] = [await OpenAI({
453
+ baseURL: 'https://deepsearch.jina.ai/v1/', ...options,
454
+ }), await OpenAI({
455
+ baseURL: 'https://api.jina.ai/v1/', ...options,
456
+ })];
457
+ for (let model of models) {
458
+ var id = buildAiId(provider, model.name);
459
+ ais.push({
460
+ id, provider, model, priority: 0, initOrder: ais.length, client,
461
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
462
+ embedding: async (i, o) => await createJinaEmbedding(ebd, i, o),
463
+ });
464
+ }
433
465
  break;
434
466
  case OLLAMA:
435
467
  // https://github.com/ollama/ollama/blob/main/docs/openai.md
436
468
  const baseURL = 'http://localhost:11434/v1/';
437
- ais.push({
438
- id, provider, model, priority: 0, initOrder: ais.length,
439
- client: await OpenAI({ baseURL, apiKey: 'ollama', ...options }),
440
- prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
441
- });
442
469
  const phLog = m => log(`Ollama preheat: ${m?.message || m}`);
443
- ignoreErrFunc(async () => {
444
- phLog(await (await fetch(`${baseURL}completions`, {
445
- method: 'POST', body: JSON.stringify({
446
- model: model.name, prompt: '', keep_alive: -1
447
- })
448
- })).text());
449
- }, { log: phLog });
470
+ var client = await OpenAI({ baseURL, apiKey: 'ollama', ...options });
471
+ for (let model of models) {
472
+ var id = buildAiId(provider, model.name);
473
+ ais.push({
474
+ id, provider, model, priority: 0, initOrder: ais.length, client,
475
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
476
+ });
477
+ ignoreErrFunc(async () => {
478
+ phLog(await (await fetch(`${baseURL}completions`, {
479
+ method: 'POST', body: JSON.stringify({
480
+ model: model.name, prompt: '', keep_alive: -1
481
+ })
482
+ })).text());
483
+ }, { log: phLog });
484
+ }
450
485
  break;
451
486
  default:
452
487
  throwError(`Invalid AI provider: ${options.provider || 'null'}.`);
453
488
  }
454
489
  ais.sort((a, b) => a.priority - b.priority || a.initOrder - b.initOrder);
455
- return ais.find(x => x.id === id);
490
+ return ais;
491
+ };
492
+
493
+ const packAi = (ais, options = {}) => {
494
+ const res = options.basic ? ais.map(x => ({
495
+ id: x.id, provider: x.provider, model: x.model, priority: x.priority,
496
+ initOrder: x.initOrder, prompt: !!x.prompt, embedding: !!x.embedding,
497
+ })) : ais;
498
+ return options.all ? res : res[0];
456
499
  };
457
500
 
458
501
  const getAi = async (id, options = {}) => {
@@ -472,12 +515,12 @@ const getAi = async (id, options = {}) => {
472
515
  select && (res.push(x));
473
516
  }
474
517
  const best = options.select?.fast ? res.filter(x => x.model.fast) : res;
475
- if (best.length) { return options.all ? best : best[0]; }
518
+ if (best.length) { return packAi(best, options); }
476
519
  assert(res.length, 'AI not found.');
477
520
  log(`Best match AI not found, fallbacked: ${JSON.stringify(options.select)}.`);
478
- return options.all ? res : res[0];
521
+ return packAi(res, options);
479
522
  }
480
- const result = options.all ? ais : ais[0];
523
+ const result = packAi(ais, options);
481
524
  assert(result?.length || result?.id, 'AI not found.');
482
525
  return result;
483
526
  };
package/lib/manifest.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  const manifest = {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1999.1.17",
4
+ "version": "1999.1.18",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1999.1.17",
4
+ "version": "1999.1.18",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",