utilitas 1999.1.18 → 1999.1.20

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
@@ -54,38 +54,37 @@ const [
54
54
  ];
55
55
 
56
56
  const [
57
- OPENAI, GEMINI, OPENAI_EMBEDDING, GEMINI_EMEDDING, OPENAI_TRAINING, OLLAMA,
58
- GPT_4O_MINI, GPT_4O, GPT_O1, GPT_O3_MINI, GEMINI_20_FLASH,
59
- GEMINI_20_FLASH_THINKING, GEMINI_20_PRO, NOVA, DEEPSEEK_R1, MD_CODE,
60
- TEXT_EMBEDDING_3_SMALL, TEXT_EMBEDDING_3_LARGE, CLOUD_37_SONNET, AUDIO, WAV,
61
- ATTACHMENTS, CHAT, OPENAI_VOICE, MEDIUM, LOW, HIGH, GPT_REASONING_EFFORT,
62
- THINK, THINK_STR, THINK_END, AZURE, TOOLS_STR, TOOLS_END, TOOLS, TEXT,
63
- THINKING, OK, FUNC, GPT_45, REDACTED_THINKING, GEMMA_3_27B, AZURE_OPENAI,
64
- ANTHROPIC, VERTEX_ANTHROPIC, GEMMA327B, v8k, ais, MAX_TOOL_RECURSION, LOG,
65
- name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent,
66
- GEMINI_EMBEDDING_M, INVALID_FILE, tokenSafeRatio, GPT_QUERY_LIMIT,
67
- CONTENT_IS_REQUIRED, OPENAI_HI_RES_SIZE, k, kT, m, minute, hour,
68
- gb, trimTailing, EBD, GEMINI_20_FLASH_EXP, IMAGE, JINA, JINA_DEEPSEARCH,
69
- JINA_EMBEDDING, JINA_CLIP,
57
+ OPENAI, GEMINI, OPENAI_TRAINING, OLLAMA, GPT_4O_MINI, GPT_4O, GPT_O1,
58
+ GPT_O3_MINI, GEMINI_20_FLASH, GEMINI_20_FLASH_THINKING, GEMINI_20_PRO, NOVA,
59
+ DEEPSEEK_R1, MD_CODE, TEXT_EMBEDDING_3_SMALL, TEXT_EMBEDDING_3_LARGE,
60
+ CLOUD_37_SONNET, AUDIO, WAV, ATTACHMENTS, CHAT, OPENAI_VOICE, MEDIUM, LOW,
61
+ HIGH, GPT_REASONING_EFFORT, THINK, THINK_STR, THINK_END, AZURE, TOOLS_STR,
62
+ TOOLS_END, TOOLS, TEXT, THINKING, OK, FUNC, GPT_45, REDACTED_THINKING,
63
+ GEMMA_3_27B, AZURE_OPENAI, ANTHROPIC, VERTEX_ANTHROPIC, GEMMA327B, v8k, ais,
64
+ MAX_TOOL_RECURSION, LOG, name, user, system, assistant, MODEL, JSON_OBJECT,
65
+ TOOL, silent, GEMINI_EMBEDDING_M, INVALID_FILE, tokenSafeRatio,
66
+ GPT_QUERY_LIMIT, CONTENT_IS_REQUIRED, OPENAI_HI_RES_SIZE, k, kT, m, minute,
67
+ hour, gb, trimTailing, EBD, GEMINI_20_FLASH_EXP, IMAGE, JINA,
68
+ JINA_DEEPSEARCH, JINA_CLIP,
70
69
  ] = [
71
- 'OpenAI', 'Gemini', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
72
- 'OPENAI_TRAINING', 'Ollama', 'gpt-4o-mini', 'gpt-4o', 'o1', 'o3-mini',
73
- 'gemini-2.0-flash', 'gemini-2.0-flash-thinking-exp',
74
- 'gemini-2.0-pro-exp', 'nova', 'deepseek-r1', '```',
75
- 'text-embedding-3-small', 'text-embedding-3-large',
76
- 'claude-3-7-sonnet@20250219', 'audio', 'wav', '[ATTACHMENTS]', 'CHAT',
77
- 'OPENAI_VOICE', 'medium', 'low', 'high', 'medium', 'think', '<think>',
78
- '</think>', 'AZURE', '<tools>', '</tools>', 'tools', 'text', 'thinking',
79
- 'OK', 'function', 'gpt-4.5-preview', 'redacted_thinking',
80
- 'gemma-3-27b-it', 'Azure Openai', 'Anthropic', 'Vertex Anthropic',
81
- 'gemma3:27b', 7680 * 4320, [], 10, { log: true }, 'Alan', 'user',
82
- 'system', 'assistant', 'model', 'json_object', 'tool', true,
70
+ 'OpenAI', 'Gemini', 'OPENAI_TRAINING', 'Ollama', 'gpt-4o-mini',
71
+ 'gpt-4o', 'o1', 'o3-mini', 'gemini-2.0-flash',
72
+ 'gemini-2.0-flash-thinking-exp', 'gemini-2.0-pro-exp', 'nova',
73
+ 'deepseek-r1', '```', 'text-embedding-3-small',
74
+ 'text-embedding-3-large', 'claude-3-7-sonnet@20250219', 'audio', 'wav',
75
+ '[ATTACHMENTS]', 'CHAT', 'OPENAI_VOICE', 'medium', 'low', 'high',
76
+ 'medium', 'think', '<think>', '</think>', 'AZURE', '<tools>',
77
+ '</tools>', 'tools', 'text', 'thinking', 'OK', 'function',
78
+ 'gpt-4.5-preview', 'redacted_thinking', 'gemma-3-27b-it',
79
+ 'Azure Openai', 'Anthropic', 'Vertex Anthropic', 'gemma3:27b',
80
+ 7680 * 4320, [], 10, { log: true }, 'Alan', 'user', 'system',
81
+ 'assistant', 'model', 'json_object', 'tool', true,
83
82
  'gemini-embedding-exp-03-07', 'Invalid file data.', 1.1, 100,
84
83
  'Content is required.', 2000 * 768, x => 1024 * x, x => 1000 * x,
85
84
  x => 1024 * 1024 * x, x => 60 * x, x => 60 * 60 * x,
86
85
  x => 1024 * 1024 * 1024 * x, x => x.replace(/[\.\s]*$/, ''),
87
86
  { embedding: true }, 'gemini-2.0-flash-exp', 'image', 'Jina',
88
- 'jina-deepsearch-v1', 'JINA_EMBEDDING', 'jina-clip-v2',
87
+ 'jina-deepsearch-v1', 'jina-clip-v2',
89
88
  ];
90
89
 
91
90
  const [tool, messages, text]
@@ -102,6 +101,7 @@ const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
102
101
  const assertContent = content => assert(content.length, CONTENT_IS_REQUIRED);
103
102
  const countToolCalls = r => r?.split('\n').filter(x => x === TOOLS_STR).length;
104
103
  const assertApiKey = (p, o) => assert(o?.apiKey, `${p} api key is required.`);
104
+ const getProviderIcon = provider => PROVIDER_ICONS[provider] || '🔮';
105
105
  const libOpenAi = async opts => await need('openai', { ...opts, raw: true });
106
106
  const OpenAI = async opts => new (await libOpenAi(opts)).OpenAI(opts);
107
107
  const AzureOpenAI = async opts => new (await libOpenAi(opts)).AzureOpenAI(opts);
@@ -167,8 +167,8 @@ const MODELS = {
167
167
  [JINA_DEEPSEARCH]: {
168
168
  contextWindow: Infinity, maxInputTokens: Infinity,
169
169
  maxOutputTokens: Infinity, imageCostTokens: 0, maxImageSize: Infinity,
170
- supportedMimeTypes: [png, jpeg, MIME_TEXT, webp, pdf],
171
- reasoning: true, json: true, vision: true, defaultProvider: JINA,
170
+ supportedMimeTypes: [png, jpeg, MIME_TEXT, webp, pdf], reasoning: true,
171
+ json: true, vision: true, deepsearch: true, defaultProvider: JINA,
172
172
  },
173
173
  [DEEPSEEK_R1]: {
174
174
  contextWindow: kT(128), maxOutputTokens: k(32),
@@ -226,12 +226,25 @@ const DEFAULT_MODELS = {
226
226
  [JINA]: JINA_DEEPSEARCH,
227
227
  [OLLAMA]: GEMMA327B,
228
228
  [OPENAI_VOICE]: NOVA,
229
- [OPENAI_EMBEDDING]: TEXT_EMBEDDING_3_SMALL,
230
- [GEMINI_EMEDDING]: GEMINI_EMBEDDING_M,
231
- [JINA_EMBEDDING]: JINA_CLIP,
232
229
  [OPENAI_TRAINING]: GPT_4O_MINI, // https://platform.openai.com/docs/guides/fine-tuning
233
230
  };
234
- DEFAULT_MODELS[CHAT] = DEFAULT_MODELS[GEMINI];
231
+
232
+ const DEFAULT_EMBEDDING = {
233
+ [OPENAI]: TEXT_EMBEDDING_3_SMALL,
234
+ [GEMINI]: GEMINI_EMBEDDING_M,
235
+ [JINA]: JINA_CLIP,
236
+ };
237
+
238
+ const PROVIDER_ICONS = {
239
+ [OPENAI]: '⚛️', [AZURE_OPENAI]: '⚛️',
240
+ [ANTHROPIC]: '✳️', [VERTEX_ANTHROPIC]: '✳️',
241
+ [GEMINI]: '♊️', [AZURE]: '☁️', [OLLAMA]: '🦙', [JINA]: '✴️',
242
+ };
243
+
244
+ const FEATURE_ICONS = {
245
+ audio: '📣', deepsearch: '🔍', fast: '⚡️', image: '🎨',
246
+ json: '📊', reasoning: '🧠', tools: '🧰', vision: '👁️',
247
+ };
235
248
 
236
249
  const tokenRatioByWords = Math.min(
237
250
  100 / 75, // ChatGPT: https://platform.openai.com/tokenizer
@@ -353,6 +366,22 @@ const buildAiId = (provider, model) => [provider, model].map(
353
366
  x => ensureString(x, { case: 'SNAKE' })
354
367
  ).join('_');
355
368
 
369
+ const setupAi = ai => {
370
+ const id = buildAiId(ai.provider, ai.model.name);
371
+ const icon = getProviderIcon(ai.provider);
372
+ const features = Object.entries(FEATURE_ICONS).map(
373
+ x => ai.model[x[0]] ? x[1] : ''
374
+ ).join('');
375
+ ais.push({
376
+ id, name: `${icon} ${ai.provider} (${ai.model.name})`,
377
+ features, initOrder: ais.length,
378
+ priority: DEFAULT_MODELS[ai.provider] === ai.model.name ? -1 : 0,
379
+ modelEmbedding: MODELS[DEFAULT_EMBEDDING[ai.provider]], ...ai,
380
+ prompt: ai.prompt && (async (c, o) => await ai.prompt(id, c, o)),
381
+ embedding: ai.embedding && (async (c, o) => await ai.embedding(id, c, o)),
382
+ });
383
+ };
384
+
356
385
  const init = async (options = {}) => {
357
386
  const provider = unifyProvider(options?.provider);
358
387
  let models;
@@ -372,11 +401,9 @@ const init = async (options = {}) => {
372
401
  assertApiKey(provider, options);
373
402
  var client = await OpenAI(options);
374
403
  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),
404
+ setupAi({
405
+ provider, model, client,
406
+ prompt: promptOpenAI, embedding: createOpenAIEmbedding,
380
407
  });
381
408
  }
382
409
  break;
@@ -389,33 +416,23 @@ const init = async (options = {}) => {
389
416
  apiVersion: '2025-01-01-preview',
390
417
  deployment: model.name, ...options,
391
418
  });
392
- var id = buildAiId(provider, model.name);
393
- ais.push({
394
- id, provider, model, priority: 0, initOrder: ais.length, client,
395
- prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
396
- });
419
+ setupAi({ provider, model, client, prompt: promptOpenAI });
397
420
  break;
398
421
  case AZURE:
399
422
  assertApiKey(provider, options);
400
423
  assert(options.baseURL, `${provider} api endpoint is required.`);
401
424
  var model = models[0];
402
425
  var client = await OpenAI(options);
403
- var id = buildAiId(provider, model.name);
404
- ais.push({
405
- id, provider, model, priority: 0, initOrder: ais.length, client,
406
- prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
407
- });
426
+ setupAi({ provider, model, client, prompt: promptOpenAI });
408
427
  break;
409
428
  case GEMINI:
410
429
  assertApiKey(provider, options);
411
430
  const { GoogleGenerativeAI } = await need('@google/generative-ai');
412
431
  var client = new GoogleGenerativeAI(options.apiKey);
413
432
  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),
433
+ setupAi({
434
+ provider, model, client,
435
+ prompt: promptGemini, embedding: createGeminiEmbedding,
419
436
  });
420
437
  }
421
438
  break;
@@ -425,11 +442,7 @@ const init = async (options = {}) => {
425
442
  await need('@anthropic-ai/sdk')
426
443
  ).Anthropic)(options)
427
444
  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
- });
445
+ setupAi({ provider, model, client, prompt: promptAnthropic });
433
446
  }
434
447
  break;
435
448
  case VERTEX_ANTHROPIC:
@@ -441,25 +454,19 @@ const init = async (options = {}) => {
441
454
  var client = new ((
442
455
  await need('@anthropic-ai/vertex-sdk')
443
456
  ).AnthropicVertex)({ region: options?.region || 'us-east5' });
444
- var id = buildAiId(provider, model.name);
445
- ais.push({
446
- id, provider, model, priority: 0, initOrder: ais.length, client: client,
447
- prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
448
- });
457
+ setupAi({ provider, model, client, prompt: promptAnthropic });
449
458
  break;
450
459
  case JINA:
451
460
  assertApiKey(provider, options);
452
- var [client, ebd] = [await OpenAI({
461
+ var [client, clientEmbedding] = [await OpenAI({
453
462
  baseURL: 'https://deepsearch.jina.ai/v1/', ...options,
454
463
  }), await OpenAI({
455
464
  baseURL: 'https://api.jina.ai/v1/', ...options,
456
465
  })];
457
466
  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),
467
+ setupAi({
468
+ provider, model, client, clientEmbedding,
469
+ prompt: promptOpenAI, embedding: createOpenAIEmbedding,
463
470
  });
464
471
  }
465
472
  break;
@@ -469,11 +476,7 @@ const init = async (options = {}) => {
469
476
  const phLog = m => log(`Ollama preheat: ${m?.message || m}`);
470
477
  var client = await OpenAI({ baseURL, apiKey: 'ollama', ...options });
471
478
  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
- });
479
+ setupAi({ provider, model, client, prompt: promptOpenAI });
477
480
  ignoreErrFunc(async () => {
478
481
  phLog(await (await fetch(`${baseURL}completions`, {
479
482
  method: 'POST', body: JSON.stringify({
@@ -492,8 +495,10 @@ const init = async (options = {}) => {
492
495
 
493
496
  const packAi = (ais, options = {}) => {
494
497
  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,
498
+ id: x.id, name: x.name, features: x.features,
499
+ initOrder: x.initOrder, priority: x.priority,
500
+ provider: x.provider, model: x.model, modelEmbedding: x.modelEmbedding,
501
+ prompt: !!x.prompt, embedding: !!x.embedding,
497
502
  })) : ais;
498
503
  return options.all ? res : res[0];
499
504
  };
@@ -1216,7 +1221,7 @@ const checkEmbeddingInput = async (input, model) => {
1216
1221
  return getInput();
1217
1222
  };
1218
1223
 
1219
- const createOpenAIEmbedding = async (client, input, options) => {
1224
+ const createOpenAIEmbedding = async (aiId, input, options) => {
1220
1225
  // args from vertex embedding may be useful uere
1221
1226
  // https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings
1222
1227
  // task_type Description
@@ -1225,23 +1230,20 @@ const createOpenAIEmbedding = async (client, input, options) => {
1225
1230
  // SEMANTIC_SIMILARITY Specifies the given text will be used for Semantic Textual Similarity(STS).
1226
1231
  // CLASSIFICATION Specifies that the embeddings will be used for classification.
1227
1232
  // CLUSTERING Specifies that the embeddings will be used for clustering.
1228
- String.isString(client) && (client = (await getAi(client)).client);
1229
- const model = options?.model || DEFAULT_MODELS[OPENAI_EMBEDDING];
1230
- const resp = await client.embeddings.create({
1233
+ let { client, modelEmbedding, clientEmbedding } = await getAi(aiId);
1234
+ const model = options?.model || modelEmbedding.name;
1235
+ const resp = await (clientEmbedding || client).embeddings.create({
1231
1236
  model, input: await checkEmbeddingInput(input, model),
1232
1237
  });
1233
1238
  return options?.raw ? resp : resp?.data[0].embedding;
1234
1239
  };
1235
1240
 
1236
- const createJinaEmbedding = async (client, input, options) =>
1237
- await createOpenAIEmbedding(client, input, {
1238
- model: DEFAULT_MODELS[JINA_EMBEDDING], ...options || {}
1239
- });
1240
-
1241
1241
  const createGeminiEmbedding = async (aiId, input, options) => {
1242
- const { client } = await getAi(aiId);
1243
- const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
1244
- const resp = await client.getGenerativeModel({ model }).embedContent(
1242
+ const { client, modelEmbedding, clientEmbedding } = await getAi(aiId);
1243
+ const model = options?.model || modelEmbedding.name;
1244
+ const resp = await (
1245
+ clientEmbedding || client
1246
+ ).getGenerativeModel({ model }).embedContent(
1245
1247
  await checkEmbeddingInput(input, model)
1246
1248
  );
1247
1249
  return options?.raw ? resp : resp?.embedding.values;
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.18",
4
+ "version": "1999.1.20",
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.18",
4
+ "version": "1999.1.20",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",