utilitas 1998.2.36 → 1998.2.38

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,7 +54,7 @@ const [
54
54
  CLAUDE_35_HAIKU, CLOUD_37_SONNET, AUDIO, WAV, CHATGPT_MINI, ATTACHMENTS,
55
55
  CHAT, OPENAI_VOICE, MEDIUM, LOW, HIGH, GPT_REASONING_EFFORT, THINK,
56
56
  THINK_STR, THINK_END, AZURE, TOOLS_STR, TOOLS_END, TOOLS, TEXT, THINKING,
57
- OK,
57
+ OK, FUNC,
58
58
  ] = [
59
59
  'OPENAI', 'GEMINI', 'CHATGPT', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
60
60
  'OPENAI_TRAINING', 'OLLAMA', 'CLAUDE', 'gpt-4o-mini', 'gpt-4o', 'o1',
@@ -66,7 +66,7 @@ const [
66
66
  'claude-3-7-sonnet@20250219', 'audio', 'wav', 'CHATGPT_MINI',
67
67
  '[ATTACHMENTS]', 'CHAT', 'OPENAI_VOICE', 'medium', 'low', 'high',
68
68
  'medium', 'think', '<think>', '</think>', 'AZURE', '<tools>',
69
- '</tools>', 'tools', 'text', 'thinking', 'OK',
69
+ '</tools>', 'tools', 'text', 'thinking', 'OK', 'function',
70
70
  ];
71
71
 
72
72
  const [
@@ -90,7 +90,7 @@ const [tool, provider, messages, text] = [
90
90
  const [name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent]
91
91
  = ['Alan', 'user', 'system', 'assistant', 'model', 'json_object', 'tool', true];
92
92
  const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
93
- = ['code_interpreter', 'retrieval', 'function'].map(tool);
93
+ = ['code_interpreter', 'retrieval', FUNC].map(tool);
94
94
  const [NOT_INIT, INVALID_FILE]
95
95
  = ['AI engine has not been initialized.', 'Invalid file data.'];
96
96
  const chatConfig
@@ -442,7 +442,10 @@ const tools = [
442
442
  parameters: {
443
443
  type: 'object',
444
444
  properties: {
445
- keyword: { type: 'string', description: 'The keyword you need to search for.' }
445
+ keyword: { type: 'string', description: 'The keyword you need to search for.' },
446
+ num: { type: 'integer', description: 'The number of search results you need, default `10`.' },
447
+ start: { type: 'integer', description: 'The start index of the search results, default `1`.' },
448
+ image: { type: 'boolean', description: 'Whether to search for images, default `false`.' },
446
449
  },
447
450
  required: ['keyword'],
448
451
  additionalProperties: false
@@ -751,8 +754,7 @@ const streamResp = async (resp, options) => {
751
754
 
752
755
  const packGptResp = async (resp, options) => {
753
756
  // simple mode is not recommended for streaming responses
754
- let text = resp.text // ChatGPT / Claude
755
- || (Function.isFunction(resp?.text) ? resp.text() : resp?.text) // Gemini
757
+ let text = resp.text // ChatGPT / Claude / Gemini
756
758
  || resp?.message?.content || ''; // Ollama
757
759
  const audio = resp?.message?.audio?.data; // ChatGPT audio mode
758
760
  if (options?.raw) { return resp; }
@@ -783,27 +785,30 @@ const handleToolsCall = async (msg, options) => {
783
785
  toolsResponse = (toolsResponse + m).trim();
784
786
  await streamResp({ text: options?.delta ? m : toolsResponse }, options);
785
787
  };
786
- const calls = msg.tool_calls || msg.content || [];
788
+ const calls = msg.tool_calls || msg.content || msg.parts || [];
787
789
  if (calls.length) {
788
790
  switch (options?.flavor) {
789
791
  case CLAUDE: preRes.push(msg); break;
790
- case GEMINI: preRes.push({ role: MODEL, parts: msg?.tool_calls.map(x => ({ functionCall: x })) }); break;
792
+ case GEMINI: preRes.push(msg); break;
791
793
  case CHATGPT: default: preRes.push(msg); break;
792
794
  }
793
795
  for (const fn of calls) {
794
796
  switch (options?.flavor) {
795
797
  case CLAUDE:
796
- input = fn.input = String.isString(fn?.input) ? parseJson(fn.input) : fn?.input;
798
+ input = fn.input = String.isString(fn?.input)
799
+ ? parseJson(fn.input) : fn?.input;
797
800
  packMsg = (content, is_error) => ({
798
- type: 'tool_result', tool_use_id: fn.id, content, is_error,
801
+ type: 'tool_result', tool_use_id: fn.id,
802
+ content, is_error,
799
803
  });
800
804
  break;
801
805
  case GEMINI:
802
- input = fn.args;
806
+ input = fn?.functionCall?.args;
803
807
  packMsg = (t, e) => ({
804
808
  functionResponse: {
805
- name: fn.name, response: {
806
- name: fn.name, content: e ? `[Error] ${t}` : t,
809
+ name: fn?.functionCall?.name, response: {
810
+ name: fn?.functionCall?.name,
811
+ content: e ? `[Error] ${t}` : t,
807
812
  }
808
813
  }
809
814
  });
@@ -816,7 +821,7 @@ const handleToolsCall = async (msg, options) => {
816
821
  });
817
822
  break;
818
823
  }
819
- const name = fn?.function?.name || fn?.name;
824
+ const name = (fn?.function || fn?.functionCall || fn)?.name;
820
825
  if (!name) { continue; }
821
826
  await resp(`\nName: ${name}`);
822
827
  const f = tools.find(x => insensitiveCompare(
@@ -843,13 +848,18 @@ const handleToolsCall = async (msg, options) => {
843
848
  log(rt);
844
849
  }
845
850
  }
846
- switch (options?.flavor) {
847
- case CLAUDE: content = content.length ? [{ role: user, content }] : []; break;
848
- case GEMINI: content = [{ role: user, parts: content }]; break;
851
+ if (content.length) {
852
+ switch (options?.flavor) {
853
+ case CLAUDE: content = [{ role: user, content }]; break;
854
+ case GEMINI: content = [{ role: FUNC, parts: content }]; break;
855
+ }
849
856
  }
850
857
  responded && await resp(`\n${TOOLS_END}`);
851
858
  }
852
- return { toolsResult: [...content.length ? preRes : [], ...content], toolsResponse };
859
+ return {
860
+ toolsResult: [...content.length ? preRes : [], ...content],
861
+ toolsResponse,
862
+ };
853
863
  };
854
864
 
855
865
  const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
@@ -1012,7 +1022,8 @@ const promptClaude = async (content, options = {}) => {
1012
1022
  );
1013
1023
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1014
1024
  return await promptClaude(content, {
1015
- ...options, toolsResult: [...options?.toolsResult || [], ...toolsResult], result: toolsResponse,
1025
+ ...options, toolsResult: [...options?.toolsResult || [],
1026
+ ...toolsResult], result: toolsResponse,
1016
1027
  });
1017
1028
  }
1018
1029
  return packGptResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
@@ -1078,54 +1089,46 @@ const promptGemini = async (content, options = {}) => {
1078
1089
  options.model = genModel;
1079
1090
  const chat = generative.startChat({
1080
1091
  history: [
1081
- ...options?.messages && !options?.attachments?.length
1082
- ? options.messages : [],
1083
- ...options?.toolsResult ? [{
1084
- role: user, parts: buildGeminiMessage(content, options)
1085
- }, options?.toolsResult[0]] : [],
1092
+ ...options?.messages && !options?.attachments?.length ? options.messages : [],
1093
+ ...options?.toolsResult ? [
1094
+ buildGeminiMessage(content, { ...options, history: true }),
1095
+ ...options.toolsResult.slice(0, options.toolsResult.length - 1)
1096
+ ] : []
1086
1097
  ], ...generationConfig(options),
1087
1098
  });
1088
- const resp = await chat[
1089
- options?.stream ? 'sendMessageStream' : 'sendMessage'
1090
- ](options?.toolsResult ?
1091
- options?.toolsResult[1].parts : buildGeminiMessage(content, options));
1092
- let [result, references, functionCalls] = [
1093
- options?.toolsResponse ? `${options?.toolsResponse}\n\n` : '', null, null
1094
- ];
1095
- if (options?.stream) {
1096
- for await (const chunk of resp.stream) {
1097
- functionCalls || (functionCalls = chunk.functionCalls);
1098
- const delta = chunk?.text?.() || '';
1099
- const rfc = packGeminiReferences(
1100
- chunk.candidates[0]?.groundingMetadata?.groundingChunks,
1101
- chunk.candidates[0]?.groundingMetadata?.groundingSupports
1102
- );
1103
- if (delta === '' && !rfc) { continue; }
1104
- result += delta;
1105
- references = rfc;
1106
- await ignoreErrFunc(async () => await options.stream(
1107
- await packGptResp({
1108
- text: () => options?.delta ? delta : result, references,
1109
- }, { ...options, processing: true })
1110
- ), LOG);
1111
- }
1099
+ const resp = await chat.sendMessageStream(
1100
+ options?.toolsResult?.[options?.toolsResult?.length]?.parts
1101
+ || buildGeminiMessage(content, options)
1102
+ );
1103
+ let [result, references, functionCalls, responded]
1104
+ = [options?.result ?? '', null, null];
1105
+ for await (const chunk of resp.stream) {
1106
+ functionCalls || (functionCalls = chunk.functionCalls);
1107
+ const rfc = packGeminiReferences(
1108
+ chunk.candidates[0]?.groundingMetadata?.groundingChunks,
1109
+ chunk.candidates[0]?.groundingMetadata?.groundingSupports
1110
+ );
1111
+ rfc && (references = rfc);
1112
+ let delta = chunk?.text?.() || '';
1113
+ delta && (responded = responded || (delta = `\n\n${delta}`));
1114
+ result += delta;
1115
+ await streamResp({ text: options?.delta ? delta : result }, options);
1112
1116
  }
1113
1117
  const _resp = await resp.response;
1114
- const { toolsResult, toolsResponse } = await handleToolsCall({
1115
- tool_calls: (functionCalls || _resp.functionCalls)()
1116
- }, { ...options, flavor: GEMINI });
1117
- options?.toolsResponse && !options?.stream
1118
- && (_resp.text = [options?.toolsResponse, _resp.text()].join('\n\n'));
1119
- return await (toolsResult.length && !options?.toolsResult ? promptGemini(
1120
- content, { ...options || {}, toolsResult, toolsResponse }
1121
- ) : packGptResp(options?.stream ? {
1122
- _resp, text: () => result, references
1123
- } : {
1124
- ..._resp, references: packGeminiReferences(
1125
- _resp.candidates[0]?.groundingMetadata?.groundingChunks,
1126
- _resp.candidates[0]?.groundingMetadata?.groundingSupports
1127
- )
1128
- }, options));
1118
+ functionCalls = (functionCalls() || _resp.functionCalls() || []).map(x => ({ functionCall: x }));
1119
+ const { toolsResult, toolsResponse } = await handleToolsCall(
1120
+ { role: MODEL, parts: functionCalls },
1121
+ { ...options, result, flavor: GEMINI }
1122
+ );
1123
+ if (toolsResult.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1124
+ return promptGemini(content, {
1125
+ ...options || {}, toolsResult: [...options?.toolsResult || [],
1126
+ ...toolsResult], result: toolsResponse,
1127
+ });
1128
+ }
1129
+ return await packGptResp({
1130
+ text: mergeMsgs(toolsResponse, toolsResult), references,
1131
+ }, options);
1129
1132
  };
1130
1133
 
1131
1134
  const checkEmbeddingInput = async (input, model) => {
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": "1998.2.36",
4
+ "version": "1998.2.38",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
package/lib/shot.mjs CHANGED
@@ -5,6 +5,7 @@ import { sha256 } from './encryption.mjs';
5
5
  import { distillHtml } from './web.mjs';
6
6
 
7
7
  import {
8
+ ensureInt,
8
9
  ensureString, extract, ignoreErrFunc, inBrowser, parseJson, parseVersion,
9
10
  throwError, which
10
11
  } from './utilitas.mjs';
@@ -160,66 +161,28 @@ const initSearch = (options) => {
160
161
  };
161
162
 
162
163
  const search = async (query, options) => {
164
+ const [key, cx, min, max]
165
+ = [options?.apiKey || googleApiKey, options?.cx || googleCx, 1, 10];
163
166
  assert(query, 'Query is required.');
164
- const [GOOGLE, DUCKDUCKGO] = ['GOOGLE', 'DUCKDUCKGO'];
165
- const provider = ensureString(
166
- options?.provider || searchProvider || DUCKDUCKGO, { case: 'UP' }
167
- );
168
- let apiKey = options?.apiKey || googleApiKey, cx = options?.cx || googleCx,
169
- url, parser;
170
- switch (provider) {
171
- case GOOGLE:
172
- assert(apiKey, 'API key is required.');
173
- assert(cx, 'CX is required.');
174
- url = 'https://www.googleapis.com/customsearch/v1'
175
- + `?key=${encodeURIComponent(apiKey)}`
176
- + `&cx=${encodeURIComponent(cx)}`
177
- + `&q=${encodeURIComponent(query)}`;
178
- break;
179
- case DUCKDUCKGO:
180
- url = 'https://api.duckduckgo.com/'
181
- + `?q=${encodeURIComponent(query)}&format=json&skip_disambig=1`;
182
- parser = x => x.FirstURL ? {
183
- title: x.FirstURL.replace(/^.*\/([^\/]*)$/, '$1').replace(/_/g, ' '),
184
- link: x.FirstURL, snippet: x.Text,
185
- image: `https://duckduckgo.com${x.Icon.URL}`, source: null,
186
- } : null;
187
- break;
188
- default:
189
- throwError(`Invalid provider: ${provider}.`);
190
- }
167
+ assert(key, 'API key is required.');
168
+ assert(cx, 'CX is required.');
169
+ const num = ensureInt(options?.num || max, { min, max });
170
+ const start = ensureInt(options?.start || min, { min });
171
+ assert(start + num <= 100, 'Reached maximum search limit.');
172
+ const url = 'https://www.googleapis.com/customsearch/v1'
173
+ + `?key=${encodeURIComponent(key)}&cx=${encodeURIComponent(cx)}`
174
+ + `&q=${encodeURIComponent(query)}&num=${num}&start=${start}`
175
+ + (options?.image ? `&searchType=image` : '');
191
176
  const resp = await get(url, { encode: _JSON, ...options || {} });
192
- let result = [];
193
- if (options?.raw) {
194
- result = resp.content;
195
- } else if (provider === GOOGLE) {
196
- result.push(...resp?.content?.items.map(x => ({
197
- title: x.title, link: x.link, snippet: x.snippet,
198
- image: x.pagemap?.cse_image?.[0]?.src || null,
199
- })));
200
- } else if (provider === DUCKDUCKGO) {
201
- const cnt = resp?.content;
202
- if (cnt?.Abstract) {
203
- result.push({
204
- title: cnt?.Heading + (cnt?.Entity ? ` (${cnt.Entity})` : ''),
205
- link: cnt?.AbstractURL, snippet: cnt?.AbstractText,
206
- image: cnt?.Image ? `https://duckduckgo.com${cnt.Image}` : null,
207
- source: cnt?.AbstractSource,
208
- });
209
- if (cnt?.Results) {
210
- result.push(...cnt.Results.map(x => ({
211
- title: x.Text, link: x.FirstURL, snippet: null,
212
- image: x.Icon ? `https://duckduckgo.com${x.Icon.URL}` : null,
213
- source: null,
214
- })));
215
- }
216
- } else if (cnt?.RelatedTopics?.[0] && !options?.no_recurse) {
217
- result = await search(cnt.RelatedTopics[0].FirstURL.replace(
218
- /^.*\/([^\/]*)$/, '$1'
219
- ).replace(/_/g, ' '), { ...options, no_recurse: true });
220
- }
221
- }
222
- return result;
177
+ return options?.raw ? resp.content : {
178
+ totalResults: resp?.content?.searchInformation?.totalResults || 0,
179
+ startIndex: resp?.content?.queries?.request?.[0]?.startIndex || 1,
180
+ items: resp?.content?.items.map(x => ({
181
+ title: x.title, link: options?.image ? null : x.link,
182
+ snippet: x.snippet,
183
+ image: (options?.image ? x.link : x.pagemap?.cse_image?.[0]?.src) || null,
184
+ })),
185
+ };
223
186
  };
224
187
 
225
188
  export default get;
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": "1998.2.36",
4
+ "version": "1998.2.38",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",