utilitas 1999.1.98 → 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 +161 -246
- 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
|
-
|
|
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,30 +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
|
-
...GEMINI_RULES, contextWindow: m(1), maxOutputTokens: k(64),
|
|
131
|
-
reasoning: true, tools: true,
|
|
132
|
-
},
|
|
131
|
+
// fast and balanced models
|
|
133
132
|
[GEMINI_25_FLASH]: {
|
|
134
133
|
...GEMINI_RULES, contextWindow: m(1), maxOutputTokens: k(64),
|
|
135
134
|
fast: true, reasoning: true, tools: true,
|
|
135
|
+
json: false, // issue with json output via OpenRouter
|
|
136
|
+
// https://gemini.google.com/app/c680748b3307790b
|
|
137
|
+
},
|
|
138
|
+
// strong and fast
|
|
139
|
+
[GPT_51]: { ...OPENAI_RULES, fast: true },
|
|
140
|
+
// stronger but slow
|
|
141
|
+
[GEMINI_30_PRO]: {
|
|
142
|
+
...GEMINI_RULES, contextWindow: m(1), maxOutputTokens: k(64),
|
|
143
|
+
reasoning: true, tools: true,
|
|
136
144
|
},
|
|
145
|
+
// models with unique capabilities
|
|
137
146
|
[GEMINI_25_FLASH_IMAGE]: {
|
|
138
|
-
...GEMINI_RULES,
|
|
147
|
+
...GEMINI_RULES, icon: '🍌', label: 'Nano Banana',
|
|
148
|
+
contextWindow: k(64), maxOutputTokens: k(32),
|
|
139
149
|
fast: true, image: true,
|
|
140
150
|
},
|
|
141
|
-
[GPT_51]: { ...OPENAI_RULES, fast: true },
|
|
142
151
|
[GPT_51_CODEX]: { ...OPENAI_RULES },
|
|
143
152
|
[GPT_5_IMAGE]: { ...OPENAI_RULES, image: true },
|
|
144
|
-
[GEMMA_3_27B]: {
|
|
145
|
-
contextWindow: kT(128), maxOutputTokens: k(8),
|
|
146
|
-
imageCostTokens: 256, maxImageSize: 896 * 896,
|
|
147
|
-
supportedMimeTypes: [MIME_PNG, MIME_JPEG, MIME_GIF],
|
|
148
|
-
fast: true, json: true, vision: true,
|
|
149
|
-
defaultProvider: OLLAMA,
|
|
150
|
-
},
|
|
151
153
|
[JINA_DEEPSEARCH]: {
|
|
152
|
-
contextWindow: Infinity, maxInputTokens: Infinity,
|
|
154
|
+
label: '✴️', contextWindow: Infinity, maxInputTokens: Infinity,
|
|
153
155
|
maxOutputTokens: Infinity, imageCostTokens: 0, maxImageSize: Infinity,
|
|
154
156
|
supportedMimeTypes: [MIME_PNG, MIME_JPEG, MIME_TEXT, MIME_WEBP, MIME_PDF],
|
|
155
157
|
reasoning: true, json: true, vision: true,
|
|
@@ -158,6 +160,7 @@ const MODELS = {
|
|
|
158
160
|
[DEEPSEEK_R1]: DEEPSEEK_R1_RULES,
|
|
159
161
|
[SF_DEEPSEEK_R1]: { ...DEEPSEEK_R1_RULES, defaultProvider: SILICONFLOW },
|
|
160
162
|
[CLOUD_SONNET_45]: {
|
|
163
|
+
source: S_ANTHROPIC, icon: '✳️',
|
|
161
164
|
contextWindow: kT(200), maxOutputTokens: kT(64),
|
|
162
165
|
documentCostTokens: 3000 * 10, maxDocumentFile: m(32),
|
|
163
166
|
maxDocumentPages: 100, imageCostTokens: ~~(v8k / 750),
|
|
@@ -166,6 +169,14 @@ const MODELS = {
|
|
|
166
169
|
json: true, reasoning: true, tools: true, vision: true,
|
|
167
170
|
defaultProvider: OPENROUTER,
|
|
168
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
|
+
},
|
|
169
180
|
// https://docs.anthropic.com/en/docs/build-with-claude/vision
|
|
170
181
|
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/sonnet-4-5
|
|
171
182
|
};
|
|
@@ -185,46 +196,47 @@ for (const n in MODELS) {
|
|
|
185
196
|
ATTACHMENT_TOKEN_COST, MODELS[n].imageCostTokens || 0
|
|
186
197
|
) : MODELS[n].imageCostTokens;
|
|
187
198
|
}
|
|
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
|
-
|
|
227
|
-
}
|
|
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
|
+
// };
|
|
228
240
|
|
|
229
241
|
// Default models for each provider
|
|
230
242
|
const DEFAULT_MODELS = {
|
|
@@ -301,7 +313,7 @@ const tools = [
|
|
|
301
313
|
}
|
|
302
314
|
},
|
|
303
315
|
func: async args => (await distill(args?.url))?.summary,
|
|
304
|
-
showReq: true,
|
|
316
|
+
showReq: true, replaced: ONLINE,
|
|
305
317
|
},
|
|
306
318
|
{
|
|
307
319
|
def: {
|
|
@@ -322,8 +334,7 @@ const tools = [
|
|
|
322
334
|
}
|
|
323
335
|
},
|
|
324
336
|
func: async args => await search(args?.keyword),
|
|
325
|
-
showReq: true,
|
|
326
|
-
depend: checkSearch,
|
|
337
|
+
showReq: true, replaced: ONLINE, depend: checkSearch,
|
|
327
338
|
},
|
|
328
339
|
];
|
|
329
340
|
|
|
@@ -343,8 +354,8 @@ const buildAiId = (provider, model) => [
|
|
|
343
354
|
].map(x => ensureString(x, { case: 'SNAKE' })).join('_');
|
|
344
355
|
|
|
345
356
|
const buildAiName = (provider, model) => [
|
|
346
|
-
getProviderIcon(provider), provider,
|
|
347
|
-
`(${isOpenrouter(provider, model) ? `${model.source}/` : ''}${model.name})`
|
|
357
|
+
model?.icon || getProviderIcon(provider), provider,
|
|
358
|
+
`(${isOpenrouter(provider, model) ? `${model.source}/` : ''}${model.label || model.name})`
|
|
348
359
|
].join(' ');
|
|
349
360
|
|
|
350
361
|
const buildAiFeatures = model => Object.entries(FEATURE_ICONS).map(
|
|
@@ -446,6 +457,8 @@ const packAi = (ais, options = {}) => {
|
|
|
446
457
|
};
|
|
447
458
|
|
|
448
459
|
const getAi = async (id, options = {}) => {
|
|
460
|
+
options?.select || (options.select = {});
|
|
461
|
+
options?.jsonMode && (options.select.json = true);
|
|
449
462
|
if (id) {
|
|
450
463
|
const ai = ais.find(x => x.id === id);
|
|
451
464
|
assert(ai, `AI not found: ${id}.`);
|
|
@@ -573,9 +586,11 @@ const getInfoEnd = text => Math.max(...[THINK_END, TOOLS_END].map(x => {
|
|
|
573
586
|
|
|
574
587
|
// @todo: escape ``` in think and tools
|
|
575
588
|
const packResp = async (resp, options) => {
|
|
589
|
+
// print(resp);
|
|
590
|
+
// return;
|
|
576
591
|
if (options?.raw) { return resp; }
|
|
577
592
|
let [
|
|
578
|
-
txt, audio, images,
|
|
593
|
+
txt, audio, images, annotations, simpleText, annotationsMarkdown, end,
|
|
579
594
|
json, audioMimeType,
|
|
580
595
|
] = [
|
|
581
596
|
resp.text || '', // ChatGPT / Claude / Gemini / Ollama
|
|
@@ -611,39 +626,25 @@ const packResp = async (resp, options) => {
|
|
|
611
626
|
else if (options?.simple && options?.imageMode) { return images; }
|
|
612
627
|
else if (options?.simple) { return simpleText; }
|
|
613
628
|
else if (options?.jsonMode) { txt = simpleText; }
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
// "
|
|
620
|
-
// "
|
|
621
|
-
// "
|
|
622
|
-
//
|
|
623
|
-
// ]
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
// },
|
|
634
|
-
// ]
|
|
635
|
-
// };
|
|
636
|
-
if (references?.segments?.length && references?.links?.length) {
|
|
637
|
-
for (let i = references.segments.length - 1; i >= 0; i--) {
|
|
638
|
-
let idx = txt.indexOf(references.segments[i].text);
|
|
639
|
-
if (idx < 0) { continue; }
|
|
640
|
-
idx += references.segments[i].text.length;
|
|
641
|
-
txt = txt.slice(0, idx)
|
|
642
|
-
+ references.segments[i].indices.map(y => ` (${y + 1})`).join('')
|
|
643
|
-
+ txt.slice(idx);
|
|
644
|
-
}
|
|
645
|
-
referencesMarkdown = 'References:\n\n' + references.links.map(
|
|
646
|
-
(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})`
|
|
647
648
|
).join('\n');
|
|
648
649
|
}
|
|
649
650
|
txt = txt.split('\n');
|
|
@@ -672,11 +673,10 @@ const packResp = async (resp, options) => {
|
|
|
672
673
|
}
|
|
673
674
|
txt = txt.join('\n');
|
|
674
675
|
!options?.delta && !options?.processing && (txt = txt.trim());
|
|
675
|
-
print(options);
|
|
676
676
|
return {
|
|
677
677
|
...text(txt), ...options?.jsonMode ? { json } : {},
|
|
678
|
-
...
|
|
679
|
-
...
|
|
678
|
+
...annotations ? { annotations } : {},
|
|
679
|
+
...annotationsMarkdown ? { annotationsMarkdown } : {},
|
|
680
680
|
...audio ? { audio } : {}, ...images?.length ? { images } : {},
|
|
681
681
|
processing: !!options?.processing,
|
|
682
682
|
model: [
|
|
@@ -797,9 +797,10 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
797
797
|
let [
|
|
798
798
|
result, resultAudio, resultImages, resultReasoning, event, resultTools,
|
|
799
799
|
responded, modalities, source, reasoningEnd, reasoning_details,
|
|
800
|
+
annotations,
|
|
800
801
|
] = [
|
|
801
802
|
options.result ?? '', Buffer.alloc(0), [], '', null, [], false,
|
|
802
|
-
options.modalities, model?.source, false, []
|
|
803
|
+
options.modalities, model?.source, false, [], [],
|
|
803
804
|
];
|
|
804
805
|
options.provider = provider;
|
|
805
806
|
options.model = options.model || model.name;
|
|
@@ -813,8 +814,13 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
813
814
|
} else if (!modalities && model.image) {
|
|
814
815
|
modalities = [TEXT, IMAGE];
|
|
815
816
|
}
|
|
816
|
-
const googleImageMode = source ===
|
|
817
|
-
|
|
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);
|
|
818
824
|
const resp = await client.chat.completions.create({
|
|
819
825
|
model: targetModel, ...history,
|
|
820
826
|
...options.jsonMode ? { response_format: { type: JSON_OBJECT } } : {},
|
|
@@ -823,9 +829,8 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
823
829
|
modalities?.find?.(x => x === AUDIO)
|
|
824
830
|
&& { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
|
|
825
831
|
), ...model?.tools && !googleImageMode ? {
|
|
826
|
-
tools: options.tools ??
|
|
827
|
-
} : {},
|
|
828
|
-
store: true, stream: true,
|
|
832
|
+
tools: options.tools ?? packedTools, tool_choice: 'auto',
|
|
833
|
+
} : {}, store: true, stream: true,
|
|
829
834
|
reasoning_effort: options.reasoning_effort,
|
|
830
835
|
});
|
|
831
836
|
for await (event of resp) {
|
|
@@ -845,8 +850,22 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
845
850
|
const deltaAudio = delta.audio?.data ? await convert(
|
|
846
851
|
delta.audio.data, { input: BASE64, expected: BUFFER }
|
|
847
852
|
) : Buffer.alloc(0);
|
|
848
|
-
delta?.
|
|
849
|
-
|
|
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
|
+
}
|
|
850
869
|
for (const x of delta.tool_calls || []) {
|
|
851
870
|
let curFunc = resultTools.find(y => y.index === x.index);
|
|
852
871
|
curFunc || (resultTools.push(curFunc = {}));
|
|
@@ -862,9 +881,11 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
862
881
|
options.result && deltaText
|
|
863
882
|
&& (responded = responded || (deltaText = `\n\n${deltaText}`));
|
|
864
883
|
resultReasoning += delteReasoning;
|
|
884
|
+
// the \n\n is needed for Interleaved Thinking:
|
|
885
|
+
// tools => reasoning => tools => reasoning ...
|
|
865
886
|
delteReasoning && delteReasoning === resultReasoning
|
|
866
|
-
&& (delteReasoning = `${THINK_STR}\n${delteReasoning}`);
|
|
867
|
-
resultReasoning && deltaText && !reasoningEnd && (
|
|
887
|
+
&& (delteReasoning = `${result ? '\n\n' : ''}${THINK_STR}\n${delteReasoning}`);
|
|
888
|
+
resultReasoning && (deltaText || delta.tool_calls?.length) && !reasoningEnd && (
|
|
868
889
|
reasoningEnd = delteReasoning = `${delteReasoning}${THINK_END}\n\n`
|
|
869
890
|
);
|
|
870
891
|
deltaText = delteReasoning + deltaText;
|
|
@@ -881,12 +902,22 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
881
902
|
}, options);
|
|
882
903
|
}
|
|
883
904
|
event = {
|
|
884
|
-
role: assistant, text: result,
|
|
885
|
-
...reasoning_details?.length ? { reasoning_details } : {},
|
|
886
|
-
tool_calls: resultTools,
|
|
905
|
+
role: assistant, text: result, tool_calls: resultTools,
|
|
887
906
|
...resultImages.length ? { images: resultImages } : {},
|
|
888
907
|
...resultAudio.length ? { audio: { data: resultAudio } } : {},
|
|
908
|
+
...annotations.length ? { annotations } : {},
|
|
889
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
|
+
}
|
|
890
921
|
const { toolsResult, toolsResponse }
|
|
891
922
|
= await handleToolsCall(event, { ...options, result });
|
|
892
923
|
if (toolsResult.length
|
|
@@ -899,122 +930,6 @@ const promptOpenAI = async (aiId, content, options = {}) => {
|
|
|
899
930
|
return await packResp(event, options);
|
|
900
931
|
};
|
|
901
932
|
|
|
902
|
-
// const packGeminiReferences = (chunks, supports) => {
|
|
903
|
-
// let references = null;
|
|
904
|
-
// if (chunks?.length && supports?.length) {
|
|
905
|
-
// references = { segments: [], links: [] };
|
|
906
|
-
// supports.map(s => references.segments.push({
|
|
907
|
-
// ...s.segment, indices: s.groundingChunkIndices,
|
|
908
|
-
// confidence: s.confidenceScores,
|
|
909
|
-
// }));
|
|
910
|
-
// chunks.map(c => references.links.push(c.web));
|
|
911
|
-
// }
|
|
912
|
-
// return references;
|
|
913
|
-
// };
|
|
914
|
-
|
|
915
|
-
// const promptGemini = async (aiId, content, options = {}) => {
|
|
916
|
-
// let { provider, client, model } = await getAi(aiId);
|
|
917
|
-
// let [
|
|
918
|
-
// event, result, text, thinking, references, functionCalls, responded,
|
|
919
|
-
// images, thinkEnd,
|
|
920
|
-
// ] = [null, options.result ?? '', '', '', null, [], false, [], false];
|
|
921
|
-
// options.model = options.model || model.name;
|
|
922
|
-
// model?.image === true && (options.imageMode = true);
|
|
923
|
-
// assert(!(options.imageMode && !model.image), 'Image mode is not supported.');
|
|
924
|
-
// if (options.imageMode && String.isString(model.image)) {
|
|
925
|
-
// options.model = model.image;
|
|
926
|
-
// options.imageMode = true;
|
|
927
|
-
// model = MODELS[options.model];
|
|
928
|
-
// }
|
|
929
|
-
// options.flavor = GEMINI;
|
|
930
|
-
// const { systemPrompt: systemInstruction, history, prompt }
|
|
931
|
-
// = await buildPrompts(model, content, options);
|
|
932
|
-
// const responseModalities = options.modalities
|
|
933
|
-
// || (options.imageMode ? [TEXT, IMAGE] : undefined)
|
|
934
|
-
// || (options.audioMode ? [TEXT, AUDIO] : undefined);
|
|
935
|
-
// const chat = client.chats.create({
|
|
936
|
-
// model: options.model, history, config: {
|
|
937
|
-
// responseMimeType: options.jsonMode ? MIME_JSON : MIME_TEXT,
|
|
938
|
-
// ...model.reasoning ? {
|
|
939
|
-
// thinkingConfig: { includeThoughts: true },
|
|
940
|
-
// } : {}, systemInstruction, responseModalities,
|
|
941
|
-
// ...options?.config || {}, ...model?.tools && !options.jsonMode
|
|
942
|
-
// && ![GEMINI_25_FLASH_IMAGE].includes(options.model)
|
|
943
|
-
// ? (options.tools ?? {
|
|
944
|
-
// tools: [
|
|
945
|
-
// // @todo: Gemini will failed when using these tools together.
|
|
946
|
-
// // https://ai.google.dev/gemini-api/docs/function-calling
|
|
947
|
-
// // { codeExecution: {} },
|
|
948
|
-
// // { googleSearch: {} },
|
|
949
|
-
// // { urlContext: {} },
|
|
950
|
-
// // @todo: test these tools in next version 👆
|
|
951
|
-
// {
|
|
952
|
-
// functionDeclarations: (
|
|
953
|
-
// await toolsGemini({ provider })
|
|
954
|
-
// ).map(x => x.def)
|
|
955
|
-
// },
|
|
956
|
-
// ], toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
|
|
957
|
-
// }) : {},
|
|
958
|
-
// },
|
|
959
|
-
// });
|
|
960
|
-
// const resp = await chat.sendMessageStream({ message: prompt });
|
|
961
|
-
// for await (const chunk of resp) {
|
|
962
|
-
// assert(
|
|
963
|
-
// !chunk?.promptFeedback?.blockReason,
|
|
964
|
-
// chunk?.promptFeedback?.blockReason
|
|
965
|
-
// );
|
|
966
|
-
// event = chunk?.candidates?.[0];
|
|
967
|
-
// let [deltaText, deltaThink, deltaImages] = ['', '', []];
|
|
968
|
-
// event?.content?.parts?.map(x => {
|
|
969
|
-
// if (x.text && x.thought) { deltaThink = x.text; }
|
|
970
|
-
// else if (x.text) { deltaText = x.text; }
|
|
971
|
-
// else if (x.functionCall) { functionCalls.push(x); }
|
|
972
|
-
// else if (x.inlineData?.mimeType === MIME_PNG) {
|
|
973
|
-
// deltaImages.push(x.inlineData);
|
|
974
|
-
// images.push(x.inlineData);
|
|
975
|
-
// }
|
|
976
|
-
// });
|
|
977
|
-
// text += deltaText;
|
|
978
|
-
// thinking += deltaThink;
|
|
979
|
-
// deltaThink && deltaThink === thinking
|
|
980
|
-
// && (deltaThink = `${THINK_STR}\n${deltaThink}`);
|
|
981
|
-
// thinking && deltaText && !thinkEnd
|
|
982
|
-
// && (thinkEnd = deltaThink = `${deltaThink}${THINK_END}\n\n`);
|
|
983
|
-
// deltaText = deltaThink + deltaText;
|
|
984
|
-
// const rfc = packGeminiReferences(
|
|
985
|
-
// event?.groundingMetadata?.groundingChunks,
|
|
986
|
-
// event?.groundingMetadata?.groundingSupports
|
|
987
|
-
// );
|
|
988
|
-
// rfc && (references = rfc);
|
|
989
|
-
// options.result && deltaText
|
|
990
|
-
// && (responded = responded || (deltaText = `\n\n${deltaText}`));
|
|
991
|
-
// result += deltaText;
|
|
992
|
-
// (deltaText || deltaImages.length) && await streamResp({
|
|
993
|
-
// text: options.delta ? deltaText : result,
|
|
994
|
-
// images: options.delta ? deltaImages : images,
|
|
995
|
-
// }, options);
|
|
996
|
-
// }
|
|
997
|
-
// event = {
|
|
998
|
-
// role: MODEL, parts: [
|
|
999
|
-
// ...thinking ? [{ thought: true, text: thinking }] : [],
|
|
1000
|
-
// ...text ? [{ text }] : [],
|
|
1001
|
-
// ...functionCalls,
|
|
1002
|
-
// ],
|
|
1003
|
-
// };
|
|
1004
|
-
// const { toolsResult, toolsResponse } = await handleToolsCall(
|
|
1005
|
-
// event, { ...options, result, flavor: GEMINI }
|
|
1006
|
-
// );
|
|
1007
|
-
// if (toolsResult.length
|
|
1008
|
-
// && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
|
|
1009
|
-
// return promptGemini(aiId, content, {
|
|
1010
|
-
// ...options || {}, result: toolsResponse,
|
|
1011
|
-
// toolsResult: [...options?.toolsResult || [], ...toolsResult],
|
|
1012
|
-
// });
|
|
1013
|
-
// }
|
|
1014
|
-
// return await packResp({
|
|
1015
|
-
// text: mergeMsgs(toolsResponse, toolsResult), images, references,
|
|
1016
|
-
// }, options);
|
|
1017
|
-
// };
|
|
1018
933
|
|
|
1019
934
|
const initChat = async (options = {}) => {
|
|
1020
935
|
if (options.sessions) {
|
package/lib/manifest.mjs
CHANGED