utilitas 1999.1.97 → 1999.1.99
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/README.md +2 -2
- package/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/lib/alan.mjs +172 -244
- package/lib/gen.mjs +2 -2
- package/lib/manifest.mjs +1 -1
- package/package.json +1 -1
package/lib/alan.mjs
CHANGED
|
@@ -45,31 +45,33 @@ You may be provided with some tools(functions) to help you gather information an
|
|
|
45
45
|
const _NEED = ['js-tiktoken', 'OpenAI'];
|
|
46
46
|
|
|
47
47
|
const [
|
|
48
|
-
OPENAI, GEMINI, OLLAMA,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
OPENAI, GEMINI, OLLAMA, NOVA, DEEPSEEK_R1, MD_CODE, CLOUD_SONNET_45, AUDIO,
|
|
49
|
+
WAV, ATTACHMENTS, OPENAI_VOICE, GPT_REASONING_EFFORT, THINK, THINK_STR,
|
|
50
|
+
THINK_END, TOOLS_STR, TOOLS_END, TOOLS, TEXT, OK, FUNC, GPT_51,
|
|
51
|
+
GPT_51_CODEX, GPT_5_IMAGE, GEMMA_3_27B, ANTHROPIC, v8k, ais,
|
|
52
52
|
MAX_TOOL_RECURSION, LOG, name, user, system, assistant, MODEL, JSON_OBJECT,
|
|
53
53
|
tokenSafeRatio, CONTENT_IS_REQUIRED, OPENAI_HI_RES_SIZE, k, kT, m, minute,
|
|
54
54
|
hour, gb, trimTailing, GEMINI_25_FLASH_IMAGE, IMAGE, JINA, JINA_DEEPSEARCH,
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
SILICONFLOW, SF_DEEPSEEK_R1, MAX_TIRE, OPENROUTER_API, OPENROUTER, AUTO,
|
|
56
|
+
TOOL, S_OPENAI, S_GOOGLE, S_ANTHROPIC, ONLINE,
|
|
57
57
|
] = [
|
|
58
|
-
'OpenAI', 'Gemini', 'Ollama', '
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'gpt-5
|
|
63
|
-
|
|
64
|
-
'
|
|
65
|
-
x =>
|
|
66
|
-
x =>
|
|
67
|
-
|
|
68
|
-
'
|
|
69
|
-
'Pro/deepseek-ai/DeepSeek-R1', 768 * 768,
|
|
58
|
+
'OpenAI', 'Gemini', 'Ollama', 'nova', 'deepseek-r1', '```',
|
|
59
|
+
'claude-sonnet-4.5', 'audio', 'wav', '[ATTACHMENTS]', 'OPENAI_VOICE',
|
|
60
|
+
'medium', 'think', '<think>', '</think>', '<tools>', '</tools>',
|
|
61
|
+
'tools', 'text', 'OK', 'function', 'gpt-5.1', 'gpt-5.1-codex',
|
|
62
|
+
'gpt-5-image', 'gemma3:27b', 'Anthropic', 7680 * 4320, [], 30,
|
|
63
|
+
{ log: true }, 'Alan', 'user', { role: 'system' }, 'assistant', 'model',
|
|
64
|
+
'json_object', 1.1, 'Content is required.', 2048 * 2048, x => 1024 * x,
|
|
65
|
+
x => 1000 * x, x => 1024 * 1024 * x, x => 60 * x, x => 60 * 60 * x,
|
|
66
|
+
x => 1024 * 1024 * 1024 * x, x => x.replace(/[\.\s]*$/, ''),
|
|
67
|
+
'gemini-2.5-flash-image', 'image', 'Jina', 'jina-deepsearch-v1',
|
|
68
|
+
'SiliconFlow', 'Pro/deepseek-ai/DeepSeek-R1', 768 * 768,
|
|
70
69
|
'https://openrouter.ai/api/v1', 'OpenRouter', 'openrouter/auto', 'tool',
|
|
70
|
+
'openai', 'google', 'anthropic', ':online',
|
|
71
71
|
];
|
|
72
72
|
|
|
73
|
+
const [GEMINI_25_FLASH, GEMINI_30_PRO]
|
|
74
|
+
= [`gemini-2.5-flash${ONLINE}`, `gemini-3-pro-preview${ONLINE}`];
|
|
73
75
|
const [tool, messages, text]
|
|
74
76
|
= [type => ({ type }), messages => ({ messages }), text => ({ text })];
|
|
75
77
|
const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
|
|
@@ -88,7 +90,7 @@ const getProviderIcon = provider => PROVIDER_ICONS[provider] || '🔮';
|
|
|
88
90
|
const libOpenAi = async opts => await need('openai', { ...opts, raw: true });
|
|
89
91
|
const OpenAI = async opts => new (await libOpenAi(opts)).OpenAI(opts);
|
|
90
92
|
const OPENAI_RULES = {
|
|
91
|
-
source: '
|
|
93
|
+
source: S_OPENAI, icon: '⚛️',
|
|
92
94
|
contextWindow: kT(400), maxOutputTokens: k(128),
|
|
93
95
|
imageCostTokens: ~~(OPENAI_HI_RES_SIZE / MAX_TIRE * 140 + 70),
|
|
94
96
|
maxFileSize: m(50), maxImageSize: OPENAI_HI_RES_SIZE,
|
|
@@ -101,7 +103,7 @@ const OPENAI_RULES = {
|
|
|
101
103
|
};
|
|
102
104
|
|
|
103
105
|
const GEMINI_RULES = {
|
|
104
|
-
source: '
|
|
106
|
+
source: S_GOOGLE, icon: '♊️',
|
|
105
107
|
json: true, audioCostTokens: 1000 * 1000 * 1, // 8.4 hours => 1 million tokens
|
|
106
108
|
imageCostTokens: ~~(v8k / MAX_TIRE * 258), maxAudioLength: hour(8.4),
|
|
107
109
|
maxAudioPerPrompt: 1, maxFileSize: m(20), maxImagePerPrompt: 3000,
|
|
@@ -118,7 +120,7 @@ const GEMINI_RULES = {
|
|
|
118
120
|
};
|
|
119
121
|
|
|
120
122
|
const DEEPSEEK_R1_RULES = {
|
|
121
|
-
contextWindow: kT(128), maxOutputTokens: k(8),
|
|
123
|
+
icon: '🐬', contextWindow: kT(128), maxOutputTokens: k(8),
|
|
122
124
|
reasoning: true,
|
|
123
125
|
};
|
|
124
126
|
|
|
@@ -126,29 +128,30 @@ const DEEPSEEK_R1_RULES = {
|
|
|
126
128
|
// https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models
|
|
127
129
|
// https://openrouter.ai/docs/features/multimodal/audio (only support input audio)
|
|
128
130
|
const MODELS = {
|
|
129
|
-
|
|
130
|
-
[GPT_51_CODEX]: { ...OPENAI_RULES },
|
|
131
|
-
[GEMINI_25_FLASH_IMAGE]: {
|
|
132
|
-
...GEMINI_RULES, contextWindow: k(64), maxOutputTokens: k(32),
|
|
133
|
-
fast: true, image: true,
|
|
134
|
-
},
|
|
131
|
+
// fast and balanced models
|
|
135
132
|
[GEMINI_25_FLASH]: {
|
|
136
133
|
...GEMINI_RULES, contextWindow: m(1), maxOutputTokens: k(64),
|
|
137
134
|
fast: true, reasoning: true, tools: true,
|
|
135
|
+
json: false, // issue with json output via OpenRouter
|
|
136
|
+
// https://gemini.google.com/app/c680748b3307790b
|
|
138
137
|
},
|
|
138
|
+
// strong and fast
|
|
139
|
+
[GPT_51]: { ...OPENAI_RULES, fast: true },
|
|
140
|
+
// stronger but slow
|
|
139
141
|
[GEMINI_30_PRO]: {
|
|
140
142
|
...GEMINI_RULES, contextWindow: m(1), maxOutputTokens: k(64),
|
|
141
143
|
reasoning: true, tools: true,
|
|
142
144
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
fast: true,
|
|
148
|
-
defaultProvider: OLLAMA,
|
|
145
|
+
// models with unique capabilities
|
|
146
|
+
[GEMINI_25_FLASH_IMAGE]: {
|
|
147
|
+
...GEMINI_RULES, icon: '🍌', label: 'Nano Banana',
|
|
148
|
+
contextWindow: k(64), maxOutputTokens: k(32),
|
|
149
|
+
fast: true, image: true,
|
|
149
150
|
},
|
|
151
|
+
[GPT_51_CODEX]: { ...OPENAI_RULES },
|
|
152
|
+
[GPT_5_IMAGE]: { ...OPENAI_RULES, image: true },
|
|
150
153
|
[JINA_DEEPSEARCH]: {
|
|
151
|
-
contextWindow: Infinity, maxInputTokens: Infinity,
|
|
154
|
+
label: '✴️', contextWindow: Infinity, maxInputTokens: Infinity,
|
|
152
155
|
maxOutputTokens: Infinity, imageCostTokens: 0, maxImageSize: Infinity,
|
|
153
156
|
supportedMimeTypes: [MIME_PNG, MIME_JPEG, MIME_TEXT, MIME_WEBP, MIME_PDF],
|
|
154
157
|
reasoning: true, json: true, vision: true,
|
|
@@ -157,6 +160,7 @@ const MODELS = {
|
|
|
157
160
|
[DEEPSEEK_R1]: DEEPSEEK_R1_RULES,
|
|
158
161
|
[SF_DEEPSEEK_R1]: { ...DEEPSEEK_R1_RULES, defaultProvider: SILICONFLOW },
|
|
159
162
|
[CLOUD_SONNET_45]: {
|
|
163
|
+
source: S_ANTHROPIC, icon: '✳️',
|
|
160
164
|
contextWindow: kT(200), maxOutputTokens: kT(64),
|
|
161
165
|
documentCostTokens: 3000 * 10, maxDocumentFile: m(32),
|
|
162
166
|
maxDocumentPages: 100, imageCostTokens: ~~(v8k / 750),
|
|
@@ -165,6 +169,14 @@ const MODELS = {
|
|
|
165
169
|
json: true, reasoning: true, tools: true, vision: true,
|
|
166
170
|
defaultProvider: OPENROUTER,
|
|
167
171
|
},
|
|
172
|
+
// best local model
|
|
173
|
+
[GEMMA_3_27B]: {
|
|
174
|
+
label: '❇️', contextWindow: kT(128), maxOutputTokens: k(8),
|
|
175
|
+
imageCostTokens: 256, maxImageSize: 896 * 896,
|
|
176
|
+
supportedMimeTypes: [MIME_PNG, MIME_JPEG, MIME_GIF],
|
|
177
|
+
fast: true, json: true, vision: true,
|
|
178
|
+
defaultProvider: OLLAMA,
|
|
179
|
+
},
|
|
168
180
|
// https://docs.anthropic.com/en/docs/build-with-claude/vision
|
|
169
181
|
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/sonnet-4-5
|
|
170
182
|
};
|
|
@@ -184,46 +196,47 @@ for (const n in MODELS) {
|
|
|
184
196
|
ATTACHMENT_TOKEN_COST, MODELS[n].imageCostTokens || 0
|
|
185
197
|
) : MODELS[n].imageCostTokens;
|
|
186
198
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
199
|
+
// Auto model have some issues with tools and reasoning, so we disable them here
|
|
200
|
+
// MODELS[AUTO] = { name: AUTO, defaultProvider: OPENROUTER, };
|
|
201
|
+
// for (const n of [GPT_51, GPT_51_CODEX, GEMINI_30_PRO, GEMINI_25_FLASH]) {
|
|
202
|
+
// // get the most restrictive limits
|
|
203
|
+
// for (const key of [
|
|
204
|
+
// 'contextWindow', 'maxInputTokens', 'maxDocumentFile', 'maxAudioLength',
|
|
205
|
+
// 'maxImagePerPrompt', 'maxFileSize', 'maxImageSize', 'maxOutputTokens',
|
|
206
|
+
// 'maxAudioPerPrompt', 'maxDocumentPages', 'maxUrlSize', 'maxVideoLength',
|
|
207
|
+
// 'maxVideoPerPrompt',
|
|
208
|
+
// ]) {
|
|
209
|
+
// MODELS[AUTO][key] = Math.min(
|
|
210
|
+
// MODELS[AUTO][key] || Infinity, MODELS[n][key] || Infinity,
|
|
211
|
+
// );
|
|
212
|
+
// }
|
|
213
|
+
// // get the most permissive costs
|
|
214
|
+
// for (const key of [
|
|
215
|
+
// 'documentCostTokens', 'imageCostTokens', 'audioCostTokens',
|
|
216
|
+
// ]) {
|
|
217
|
+
// MODELS[AUTO][key] = Math.max(
|
|
218
|
+
// MODELS[AUTO][key] || 0, MODELS[n][key] || 0,
|
|
219
|
+
// );
|
|
220
|
+
// }
|
|
221
|
+
// // combine supported types
|
|
222
|
+
// for (const key of [
|
|
223
|
+
// 'supportedAudioTypes', 'supportedDocTypes', 'supportedMimeTypes',
|
|
224
|
+
// ]) {
|
|
225
|
+
// MODELS[AUTO][key] = [...new Set(
|
|
226
|
+
// [...MODELS[AUTO][key] || [], ...MODELS[n][key] || []]
|
|
227
|
+
// )];
|
|
228
|
+
// }
|
|
229
|
+
// // for other features, if any model supports it, then AUTO supports it
|
|
230
|
+
// for (const key of [
|
|
231
|
+
// 'json', 'reasoning', 'tools', 'vision', 'fast', 'deepsearch', 'image',
|
|
232
|
+
// ]) {
|
|
233
|
+
// MODELS[AUTO][key] = MODELS[AUTO][key] || MODELS[n][key];
|
|
234
|
+
// }
|
|
235
|
+
// // catch first possible support
|
|
236
|
+
// for (const key of ['audio']) {
|
|
237
|
+
// MODELS[AUTO][key] = MODELS[AUTO][key] || MODELS[n][key];
|
|
238
|
+
// }
|
|
239
|
+
// };
|
|
227
240
|
|
|
228
241
|
// Default models for each provider
|
|
229
242
|
const DEFAULT_MODELS = {
|
|
@@ -255,7 +268,7 @@ const tokenRatioByCharacters = Math.max(
|
|
|
255
268
|
);
|
|
256
269
|
|
|
257
270
|
|
|
258
|
-
let tokeniser;
|
|
271
|
+
let tokeniser, _tools;
|
|
259
272
|
|
|
260
273
|
const unifyProvider = provider => {
|
|
261
274
|
assert(provider = (provider || '').trim(), 'AI provider is required.');
|
|
@@ -300,7 +313,7 @@ const tools = [
|
|
|
300
313
|
}
|
|
301
314
|
},
|
|
302
315
|
func: async args => (await distill(args?.url))?.summary,
|
|
303
|
-
showReq: true,
|
|
316
|
+
showReq: true, replaced: ONLINE,
|
|
304
317
|
},
|
|
305
318
|
{
|
|
306
319
|
def: {
|
|
@@ -321,12 +334,11 @@ const tools = [
|
|
|
321
334
|
}
|
|
322
335
|
},
|
|
323
336
|
func: async args => await search(args?.keyword),
|
|
324
|
-
showReq: true,
|
|
325
|
-
depend: checkSearch,
|
|
337
|
+
showReq: true, replaced: ONLINE, depend: checkSearch,
|
|
326
338
|
},
|
|
327
339
|
];
|
|
328
340
|
|
|
329
|
-
const
|
|
341
|
+
const packTools = async () => {
|
|
330
342
|
const _tools = [];
|
|
331
343
|
for (const t of tools) {
|
|
332
344
|
(t.depend ? await t.depend() : true) ? _tools.push(t) : log(
|
|
@@ -342,8 +354,8 @@ const buildAiId = (provider, model) => [
|
|
|
342
354
|
].map(x => ensureString(x, { case: 'SNAKE' })).join('_');
|
|
343
355
|
|
|
344
356
|
const buildAiName = (provider, model) => [
|
|
345
|
-
getProviderIcon(provider), provider,
|
|
346
|
-
`(${isOpenrouter(provider, model) ? `${model.source}/` : ''}${model.name})`
|
|
357
|
+
model?.icon || getProviderIcon(provider), provider,
|
|
358
|
+
`(${isOpenrouter(provider, model) ? `${model.source}/` : ''}${model.label || model.name})`
|
|
347
359
|
].join(' ');
|
|
348
360
|
|
|
349
361
|
const buildAiFeatures = model => Object.entries(FEATURE_ICONS).map(
|
|
@@ -383,6 +395,7 @@ const init = async (options = {}) => {
|
|
|
383
395
|
}
|
|
384
396
|
assert(models.length,
|
|
385
397
|
`Model name or description is required for provider: ${provider}.`);
|
|
398
|
+
_tools || (_tools = await packTools());
|
|
386
399
|
switch (provider) {
|
|
387
400
|
case JINA:
|
|
388
401
|
assertApiKey(provider, options);
|
|
@@ -444,6 +457,8 @@ const packAi = (ais, options = {}) => {
|
|
|
444
457
|
};
|
|
445
458
|
|
|
446
459
|
const getAi = async (id, options = {}) => {
|
|
460
|
+
options?.select || (options.select = {});
|
|
461
|
+
options?.jsonMode && (options.select.json = true);
|
|
447
462
|
if (id) {
|
|
448
463
|
const ai = ais.find(x => x.id === id);
|
|
449
464
|
assert(ai, `AI not found: ${id}.`);
|
|
@@ -571,9 +586,11 @@ const getInfoEnd = text => Math.max(...[THINK_END, TOOLS_END].map(x => {
|
|
|
571
586
|
|
|
572
587
|
// @todo: escape ``` in think and tools
|
|
573
588
|
const packResp = async (resp, options) => {
|
|
589
|
+
// print(resp);
|
|
590
|
+
// return;
|
|
574
591
|
if (options?.raw) { return resp; }
|
|
575
592
|
let [
|
|
576
|
-
txt, audio, images,
|
|
593
|
+
txt, audio, images, annotations, simpleText, annotationsMarkdown, end,
|
|
577
594
|
json, audioMimeType,
|
|
578
595
|
] = [
|
|
579
596
|
resp.text || '', // ChatGPT / Claude / Gemini / Ollama
|
|
@@ -609,39 +626,25 @@ const packResp = async (resp, options) => {
|
|
|
609
626
|
else if (options?.simple && options?.imageMode) { return images; }
|
|
610
627
|
else if (options?.simple) { return simpleText; }
|
|
611
628
|
else if (options?.jsonMode) { txt = simpleText; }
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
// "
|
|
618
|
-
// "
|
|
619
|
-
// "
|
|
620
|
-
//
|
|
621
|
-
// ]
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
// },
|
|
632
|
-
// ]
|
|
633
|
-
// };
|
|
634
|
-
if (references?.segments?.length && references?.links?.length) {
|
|
635
|
-
for (let i = references.segments.length - 1; i >= 0; i--) {
|
|
636
|
-
let idx = txt.indexOf(references.segments[i].text);
|
|
637
|
-
if (idx < 0) { continue; }
|
|
638
|
-
idx += references.segments[i].text.length;
|
|
639
|
-
txt = txt.slice(0, idx)
|
|
640
|
-
+ references.segments[i].indices.map(y => ` (${y + 1})`).join('')
|
|
641
|
-
+ txt.slice(idx);
|
|
642
|
-
}
|
|
643
|
-
referencesMarkdown = 'References:\n\n' + references.links.map(
|
|
644
|
-
(x, i) => `${i + 1}. [${x.title}](${x.uri})`
|
|
629
|
+
// annotations debug codes:
|
|
630
|
+
// annotations = [
|
|
631
|
+
// {
|
|
632
|
+
// "type": "url_citation",
|
|
633
|
+
// "url_citation": {
|
|
634
|
+
// "end_index": 0,
|
|
635
|
+
// "start_index": 0,
|
|
636
|
+
// "title": "在線時鐘- 目前時間- 線上時鐘- 時鐘線上 - 鬧鐘",
|
|
637
|
+
// "url": "https://naozhong.tw/shijian/",
|
|
638
|
+
// "content": "- [鬧鐘](https://naozhong.tw/)\n- [計時器](https://naozhong.tw/jishiqi/)\n- [碼錶](https://naozhong.tw/miaobiao/)\n- [時間](https://naozhong.tw/shijian/)\n\n# 現在時間\n\n加入\n\n- [編輯](javascript:;)\n- [移至頂端](javascript:;)\n- [上移](javascript:;)\n- [下移](javascript:;)\n- [刪除](javascript:;)\n\n# 最常用\n\n| | |\n| --- | --- |\n| [台北](https://naozhong.tw/shijian/%E5%8F%B0%E5%8C%97/) | 10:09:14 |\n| [北京,中國](https://naozhong.tw/shijian/%E5%8C%97%E4%BA%AC-%E4%B8%AD%E5%9C%8B/) | 10:09:14 |\n| [上海,中國](https://naozhong.tw/shijian/%E4%B8%8A%E6%B5%B7-%E4%B8%AD%E5%9C%8B/) | 10:09:14 |\n| [烏魯木齊,中國](https://naozhong.tw/shijian/%E7%83%8F%E9%AD%AF%"
|
|
639
|
+
// }
|
|
640
|
+
// },
|
|
641
|
+
// ];
|
|
642
|
+
if (annotations?.length) {
|
|
643
|
+
annotations = annotations.filter(x => x?.type === 'url_citation').map(
|
|
644
|
+
x => ({ type: x.type, ...x.url_citation })
|
|
645
|
+
);
|
|
646
|
+
annotationsMarkdown = 'References:\n\n' + annotations.map(
|
|
647
|
+
(x, i) => `${i + 1}. [${x.title}](${x.url})`
|
|
645
648
|
).join('\n');
|
|
646
649
|
}
|
|
647
650
|
txt = txt.split('\n');
|
|
@@ -672,11 +675,14 @@ const packResp = async (resp, options) => {
|
|
|
672
675
|
!options?.delta && !options?.processing && (txt = txt.trim());
|
|
673
676
|
return {
|
|
674
677
|
...text(txt), ...options?.jsonMode ? { json } : {},
|
|
675
|
-
...
|
|
676
|
-
...
|
|
678
|
+
...annotations ? { annotations } : {},
|
|
679
|
+
...annotationsMarkdown ? { annotationsMarkdown } : {},
|
|
677
680
|
...audio ? { audio } : {}, ...images?.length ? { images } : {},
|
|
678
681
|
processing: !!options?.processing,
|
|
679
|
-
model:
|
|
682
|
+
model: [
|
|
683
|
+
options.provider, options?.router?.provider,
|
|
684
|
+
options?.router?.model || options?.model,
|
|
685
|
+
].filter(x => x).join('/'),
|
|
680
686
|
};
|
|
681
687
|
};
|
|
682
688
|
|
|
@@ -790,11 +796,13 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
790
796
|
let { provider, client, model } = await getAi(aiId);
|
|
791
797
|
let [
|
|
792
798
|
result, resultAudio, resultImages, resultReasoning, event, resultTools,
|
|
793
|
-
responded, modalities, source, reasoningEnd
|
|
799
|
+
responded, modalities, source, reasoningEnd, reasoning_details,
|
|
800
|
+
annotations,
|
|
794
801
|
] = [
|
|
795
802
|
options.result ?? '', Buffer.alloc(0), [], '', null, [], false,
|
|
796
|
-
options.modalities, model?.source, false
|
|
803
|
+
options.modalities, model?.source, false, [], [],
|
|
797
804
|
];
|
|
805
|
+
options.provider = provider;
|
|
798
806
|
options.model = options.model || model.name;
|
|
799
807
|
const { history }
|
|
800
808
|
= await buildPrompts(MODELS[options.model], content, options);
|
|
@@ -806,8 +814,13 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
806
814
|
} else if (!modalities && model.image) {
|
|
807
815
|
modalities = [TEXT, IMAGE];
|
|
808
816
|
}
|
|
809
|
-
const googleImageMode = source ===
|
|
810
|
-
|
|
817
|
+
const googleImageMode = source === S_GOOGLE && modalities?.has?.(IMAGE);
|
|
818
|
+
// pricy: https://openrouter.ai/docs/features/web-search
|
|
819
|
+
const ext = ''; // options.jsonMode ? '' : ONLINE;
|
|
820
|
+
const targetModel = `${isOpenrouter(provider, model) ? `${source}/` : ''}${options.model}${ext}`;
|
|
821
|
+
const packedTools = (targetModel.endsWith(ONLINE)
|
|
822
|
+
? _tools.filter(x => x?.replaced !== ONLINE)
|
|
823
|
+
: _tools).map(x => x.def);
|
|
811
824
|
const resp = await client.chat.completions.create({
|
|
812
825
|
model: targetModel, ...history,
|
|
813
826
|
...options.jsonMode ? { response_format: { type: JSON_OBJECT } } : {},
|
|
@@ -816,14 +829,15 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
816
829
|
modalities?.find?.(x => x === AUDIO)
|
|
817
830
|
&& { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
|
|
818
831
|
), ...model?.tools && !googleImageMode ? {
|
|
819
|
-
tools: options.tools ??
|
|
820
|
-
|
|
821
|
-
} : {},
|
|
822
|
-
store: true, stream: true,
|
|
832
|
+
tools: options.tools ?? packedTools, tool_choice: 'auto',
|
|
833
|
+
} : {}, store: true, stream: true,
|
|
823
834
|
reasoning_effort: options.reasoning_effort,
|
|
824
835
|
});
|
|
825
836
|
for await (event of resp) {
|
|
826
837
|
// print(JSON.stringify(event, null, 2));
|
|
838
|
+
event?.provider && event?.model && (options.router = {
|
|
839
|
+
provider: event.provider, model: event.model,
|
|
840
|
+
});
|
|
827
841
|
event = event?.choices?.[0] || {};
|
|
828
842
|
const delta = event.delta || {};
|
|
829
843
|
let [delteReasoning, deltaText] = [
|
|
@@ -836,6 +850,22 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
836
850
|
const deltaAudio = delta.audio?.data ? await convert(
|
|
837
851
|
delta.audio.data, { input: BASE64, expected: BUFFER }
|
|
838
852
|
) : Buffer.alloc(0);
|
|
853
|
+
delta?.annotations?.length && annotations.push(...delta.annotations);
|
|
854
|
+
// for anthropic reasoning details need to be merged in streaming
|
|
855
|
+
if (delta?.reasoning_details?.length) {
|
|
856
|
+
reasoning_details.length || reasoning_details.push({});
|
|
857
|
+
for (const item of delta.reasoning_details) {
|
|
858
|
+
for (const key in item) {
|
|
859
|
+
if (key === 'text') {
|
|
860
|
+
reasoning_details[0][key] = (
|
|
861
|
+
reasoning_details[0][key] || ''
|
|
862
|
+
) + item[key];
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
reasoning_details[0][key] = item[key];
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
839
869
|
for (const x of delta.tool_calls || []) {
|
|
840
870
|
let curFunc = resultTools.find(y => y.index === x.index);
|
|
841
871
|
curFunc || (resultTools.push(curFunc = {}));
|
|
@@ -851,9 +881,11 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
851
881
|
options.result && deltaText
|
|
852
882
|
&& (responded = responded || (deltaText = `\n\n${deltaText}`));
|
|
853
883
|
resultReasoning += delteReasoning;
|
|
884
|
+
// the \n\n is needed for Interleaved Thinking:
|
|
885
|
+
// tools => reasoning => tools => reasoning ...
|
|
854
886
|
delteReasoning && delteReasoning === resultReasoning
|
|
855
|
-
&& (delteReasoning = `${THINK_STR}\n${delteReasoning}`);
|
|
856
|
-
resultReasoning && deltaText && !reasoningEnd && (
|
|
887
|
+
&& (delteReasoning = `${result ? '\n\n' : ''}${THINK_STR}\n${delteReasoning}`);
|
|
888
|
+
resultReasoning && (deltaText || delta.tool_calls?.length) && !reasoningEnd && (
|
|
857
889
|
reasoningEnd = delteReasoning = `${delteReasoning}${THINK_END}\n\n`
|
|
858
890
|
);
|
|
859
891
|
deltaText = delteReasoning + deltaText;
|
|
@@ -873,7 +905,19 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
873
905
|
role: assistant, text: result, tool_calls: resultTools,
|
|
874
906
|
...resultImages.length ? { images: resultImages } : {},
|
|
875
907
|
...resultAudio.length ? { audio: { data: resultAudio } } : {},
|
|
908
|
+
...annotations.length ? { annotations } : {},
|
|
876
909
|
};
|
|
910
|
+
switch (source) {
|
|
911
|
+
case S_ANTHROPIC:
|
|
912
|
+
event.content = reasoning_details.map(x => ({
|
|
913
|
+
type: 'thinking', thinking: x.text,
|
|
914
|
+
...x.signature ? { signature: x.signature } : {},
|
|
915
|
+
}));
|
|
916
|
+
break;
|
|
917
|
+
case S_GOOGLE:
|
|
918
|
+
reasoning_details?.length
|
|
919
|
+
&& (event.reasoning_details = reasoning_details);
|
|
920
|
+
}
|
|
877
921
|
const { toolsResult, toolsResponse }
|
|
878
922
|
= await handleToolsCall(event, { ...options, result });
|
|
879
923
|
if (toolsResult.length
|
|
@@ -886,122 +930,6 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
886
930
|
return await packResp(event, options);
|
|
887
931
|
};
|
|
888
932
|
|
|
889
|
-
// const packGeminiReferences = (chunks, supports) => {
|
|
890
|
-
// let references = null;
|
|
891
|
-
// if (chunks?.length && supports?.length) {
|
|
892
|
-
// references = { segments: [], links: [] };
|
|
893
|
-
// supports.map(s => references.segments.push({
|
|
894
|
-
// ...s.segment, indices: s.groundingChunkIndices,
|
|
895
|
-
// confidence: s.confidenceScores,
|
|
896
|
-
// }));
|
|
897
|
-
// chunks.map(c => references.links.push(c.web));
|
|
898
|
-
// }
|
|
899
|
-
// return references;
|
|
900
|
-
// };
|
|
901
|
-
|
|
902
|
-
// const promptGemini = async (aiId, content, options = {}) => {
|
|
903
|
-
// let { provider, client, model } = await getAi(aiId);
|
|
904
|
-
// let [
|
|
905
|
-
// event, result, text, thinking, references, functionCalls, responded,
|
|
906
|
-
// images, thinkEnd,
|
|
907
|
-
// ] = [null, options.result ?? '', '', '', null, [], false, [], false];
|
|
908
|
-
// options.model = options.model || model.name;
|
|
909
|
-
// model?.image === true && (options.imageMode = true);
|
|
910
|
-
// assert(!(options.imageMode && !model.image), 'Image mode is not supported.');
|
|
911
|
-
// if (options.imageMode && String.isString(model.image)) {
|
|
912
|
-
// options.model = model.image;
|
|
913
|
-
// options.imageMode = true;
|
|
914
|
-
// model = MODELS[options.model];
|
|
915
|
-
// }
|
|
916
|
-
// options.flavor = GEMINI;
|
|
917
|
-
// const { systemPrompt: systemInstruction, history, prompt }
|
|
918
|
-
// = await buildPrompts(model, content, options);
|
|
919
|
-
// const responseModalities = options.modalities
|
|
920
|
-
// || (options.imageMode ? [TEXT, IMAGE] : undefined)
|
|
921
|
-
// || (options.audioMode ? [TEXT, AUDIO] : undefined);
|
|
922
|
-
// const chat = client.chats.create({
|
|
923
|
-
// model: options.model, history, config: {
|
|
924
|
-
// responseMimeType: options.jsonMode ? MIME_JSON : MIME_TEXT,
|
|
925
|
-
// ...model.reasoning ? {
|
|
926
|
-
// thinkingConfig: { includeThoughts: true },
|
|
927
|
-
// } : {}, systemInstruction, responseModalities,
|
|
928
|
-
// ...options?.config || {}, ...model?.tools && !options.jsonMode
|
|
929
|
-
// && ![GEMINI_25_FLASH_IMAGE].includes(options.model)
|
|
930
|
-
// ? (options.tools ?? {
|
|
931
|
-
// tools: [
|
|
932
|
-
// // @todo: Gemini will failed when using these tools together.
|
|
933
|
-
// // https://ai.google.dev/gemini-api/docs/function-calling
|
|
934
|
-
// // { codeExecution: {} },
|
|
935
|
-
// // { googleSearch: {} },
|
|
936
|
-
// // { urlContext: {} },
|
|
937
|
-
// // @todo: test these tools in next version 👆
|
|
938
|
-
// {
|
|
939
|
-
// functionDeclarations: (
|
|
940
|
-
// await toolsGemini({ provider })
|
|
941
|
-
// ).map(x => x.def)
|
|
942
|
-
// },
|
|
943
|
-
// ], toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
|
|
944
|
-
// }) : {},
|
|
945
|
-
// },
|
|
946
|
-
// });
|
|
947
|
-
// const resp = await chat.sendMessageStream({ message: prompt });
|
|
948
|
-
// for await (const chunk of resp) {
|
|
949
|
-
// assert(
|
|
950
|
-
// !chunk?.promptFeedback?.blockReason,
|
|
951
|
-
// chunk?.promptFeedback?.blockReason
|
|
952
|
-
// );
|
|
953
|
-
// event = chunk?.candidates?.[0];
|
|
954
|
-
// let [deltaText, deltaThink, deltaImages] = ['', '', []];
|
|
955
|
-
// event?.content?.parts?.map(x => {
|
|
956
|
-
// if (x.text && x.thought) { deltaThink = x.text; }
|
|
957
|
-
// else if (x.text) { deltaText = x.text; }
|
|
958
|
-
// else if (x.functionCall) { functionCalls.push(x); }
|
|
959
|
-
// else if (x.inlineData?.mimeType === MIME_PNG) {
|
|
960
|
-
// deltaImages.push(x.inlineData);
|
|
961
|
-
// images.push(x.inlineData);
|
|
962
|
-
// }
|
|
963
|
-
// });
|
|
964
|
-
// text += deltaText;
|
|
965
|
-
// thinking += deltaThink;
|
|
966
|
-
// deltaThink && deltaThink === thinking
|
|
967
|
-
// && (deltaThink = `${THINK_STR}\n${deltaThink}`);
|
|
968
|
-
// thinking && deltaText && !thinkEnd
|
|
969
|
-
// && (thinkEnd = deltaThink = `${deltaThink}${THINK_END}\n\n`);
|
|
970
|
-
// deltaText = deltaThink + deltaText;
|
|
971
|
-
// const rfc = packGeminiReferences(
|
|
972
|
-
// event?.groundingMetadata?.groundingChunks,
|
|
973
|
-
// event?.groundingMetadata?.groundingSupports
|
|
974
|
-
// );
|
|
975
|
-
// rfc && (references = rfc);
|
|
976
|
-
// options.result && deltaText
|
|
977
|
-
// && (responded = responded || (deltaText = `\n\n${deltaText}`));
|
|
978
|
-
// result += deltaText;
|
|
979
|
-
// (deltaText || deltaImages.length) && await streamResp({
|
|
980
|
-
// text: options.delta ? deltaText : result,
|
|
981
|
-
// images: options.delta ? deltaImages : images,
|
|
982
|
-
// }, options);
|
|
983
|
-
// }
|
|
984
|
-
// event = {
|
|
985
|
-
// role: MODEL, parts: [
|
|
986
|
-
// ...thinking ? [{ thought: true, text: thinking }] : [],
|
|
987
|
-
// ...text ? [{ text }] : [],
|
|
988
|
-
// ...functionCalls,
|
|
989
|
-
// ],
|
|
990
|
-
// };
|
|
991
|
-
// const { toolsResult, toolsResponse } = await handleToolsCall(
|
|
992
|
-
// event, { ...options, result, flavor: GEMINI }
|
|
993
|
-
// );
|
|
994
|
-
// if (toolsResult.length
|
|
995
|
-
// && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
|
|
996
|
-
// return promptGemini(aiId, content, {
|
|
997
|
-
// ...options || {}, result: toolsResponse,
|
|
998
|
-
// toolsResult: [...options?.toolsResult || [], ...toolsResult],
|
|
999
|
-
// });
|
|
1000
|
-
// }
|
|
1001
|
-
// return await packResp({
|
|
1002
|
-
// text: mergeMsgs(toolsResponse, toolsResult), images, references,
|
|
1003
|
-
// }, options);
|
|
1004
|
-
// };
|
|
1005
933
|
|
|
1006
934
|
const initChat = async (options = {}) => {
|
|
1007
935
|
if (options.sessions) {
|
package/lib/gen.mjs
CHANGED
|
@@ -10,11 +10,11 @@ const _NEED = ['OpenAI', '@google/genai'];
|
|
|
10
10
|
const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
|
|
11
11
|
const [
|
|
12
12
|
clients, OPENAI, GEMINI, BASE64, FILE, BUFFER, ERROR_GENERATING,
|
|
13
|
-
IMAGEN_MODEL, OPENAI_MODEL, VEO_MODEL,
|
|
13
|
+
IMAGEN_MODEL, OPENAI_MODEL, VEO_MODEL, IMAGEN_UPSCALE_MODEL,
|
|
14
14
|
] = [
|
|
15
15
|
{}, 'OPENAI', 'GEMINI', 'BASE64', 'FILE', 'BUFFER',
|
|
16
16
|
'Error generating media.', 'imagen-4.0-ultra-generate-001',
|
|
17
|
-
'gpt-image-1', 'veo-3.1-generate-preview',
|
|
17
|
+
'gpt-image-1', 'veo-3.1-generate-preview', 'imagen-4.0-upscale-preview',
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
const init = async (options) => {
|
package/lib/manifest.mjs
CHANGED