utilitas 1999.1.78 → 1999.1.80

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
@@ -1197,8 +1197,10 @@ const promptGemini = async (aiId, content, options = {}) => {
1197
1197
  const chat = client.chats.create({
1198
1198
  model: options.model, history, config: {
1199
1199
  responseMimeType: options.jsonMode ? MIME_JSON : MIME_TEXT,
1200
- thinkingConfig: model.reasoning ? { includeThoughts: true } : {},
1201
- systemInstruction, responseModalities: options.modalities || (
1200
+ ...model.reasoning ? {
1201
+ thinkingConfig: { includeThoughts: true },
1202
+ } : {}, systemInstruction,
1203
+ responseModalities: options.modalities || (
1202
1204
  options.imageMode ? [TEXT, IMAGE] : undefined
1203
1205
  ), ...options?.config || {}, ...model?.tools && !options.jsonMode
1204
1206
  && ![GEMINI_20_FLASH].includes(options.model)
package/lib/gen.mjs CHANGED
@@ -1,42 +1,40 @@
1
1
  import {
2
- ensureArray, ensureString, ignoreErrFunc, log as _log, need, throwError,
3
- tryUntil,
2
+ ensureArray, ensureString, log as _log, need, throwError,
3
+ tryUntil, timeout,
4
4
  } from './utilitas.mjs';
5
5
 
6
- import { assertExist, exec } from './shell.mjs';
7
- import { convert, MIME_PNG } from './storage.mjs';
6
+ import { convert, MIME_PNG, MIME_MP4, getTempPath } from './storage.mjs';
8
7
  import { createReadStream } from 'fs';
8
+ import { hash } from './encryption.mjs';
9
9
 
10
- const _NEED = ['OpenAI'];
10
+ const _NEED = ['OpenAI', '@google/genai'];
11
11
  const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
12
12
  const [
13
- clients, OPENAI, GEMINI, BASE64, BUFFER, ERROR_GENERATING, IMAGEN_MODEL,
14
- OPENAI_MODEL, VEO_MODEL,
13
+ clients, OPENAI, GEMINI, BASE64, FILE, BUFFER, ERROR_GENERATING,
14
+ IMAGEN_MODEL, OPENAI_MODEL, VEO_MODEL,
15
15
  ] = [
16
- {}, 'OPENAI', 'GEMINI', 'BASE64', 'BUFFER', 'Error generating image.',
17
- 'imagen-3.0-generate-002', 'gpt-image-1', 'veo-2.0-generate-001',
16
+ {}, 'OPENAI', 'GEMINI', 'BASE64', 'FILE', 'BUFFER',
17
+ 'Error generating media.', 'imagen-3.0-generate-002', 'gpt-image-1',
18
+ 'veo-2.0-generate-001',
18
19
  ];
19
20
 
20
21
  const init = async (options) => {
21
- assert(
22
- options?.apiKey || (options?.credentials && options?.projectId),
23
- 'API key or credentials are required.'
24
- );
22
+ assert(options?.apiKey, 'API key is required.');
25
23
  const provider = ensureString(options?.provider, { case: 'UP' });
26
24
  switch (provider) {
27
25
  case OPENAI:
28
26
  const OpenAI = await need('openai');
29
- const openai = new OpenAI(options);
27
+ var client = new OpenAI(options);
30
28
  clients[provider] = {
31
- image: openai.images,
29
+ image: client.images,
32
30
  toFile: OpenAI.toFile,
33
31
  };
34
32
  break;
35
33
  case GEMINI:
34
+ const { GoogleGenAI } = await need('@google/genai');
35
+ var client = new GoogleGenAI({ vertexai: false, ...options });
36
36
  clients[provider] = {
37
- apiKey: options.apiKey,
38
- projectId: options.projectId,
39
- credentials: options.credentials,
37
+ gen: client,
40
38
  };
41
39
  break;
42
40
  default:
@@ -50,7 +48,7 @@ const extractImage = async (data, options) => await convert(
50
48
  );
51
49
 
52
50
  const extractVideo = async (data, options) => await convert(
53
- data, { input: BASE64, suffix: 'mp4', ...options || {} }
51
+ data, { input: FILE, suffix: 'mp4', ...options || {} }
54
52
  );
55
53
 
56
54
  const prepareImage = async (files, repack, options) => {
@@ -68,8 +66,8 @@ const prepareImage = async (files, repack, options) => {
68
66
 
69
67
  const image = async (prompt, options) => {
70
68
  let provider = ensureString(options?.provider, { case: 'UP' });
71
- if (!provider && clients?.[GEMINI]?.apiKey) { provider = GEMINI; }
72
- if (!provider && clients?.[OPENAI]) { provider = OPENAI; }
69
+ if (!provider && clients?.[GEMINI]) { provider = GEMINI; }
70
+ else if (!provider && clients?.[OPENAI]) { provider = OPENAI; }
73
71
  const client = clients?.[provider];
74
72
  const n = options?.n || 4;
75
73
  assert(client, 'No available image generation provider.');
@@ -110,66 +108,25 @@ const image = async (prompt, options) => {
110
108
  }
111
109
  return resp?.data;
112
110
  case GEMINI:
113
- // Image editing failed with the following error: imagen-3.0-capability-001 is unavailable.
114
- // @todo: https://cloud.google.com/vertex-ai/generative-ai/docs/image/overview#feature-launch-stage
115
- // cat << EOF > request.json
116
- // {
117
- // "endpoint": "projects/backend-alpha-97077/locations/us-central1/publishers/google/models/imagen-3.0-capability-001",
118
- // "instances": [
119
- // {
120
- // "prompt": "ENTER PROMPT HERE",
121
- // "referenceImages": [
122
- // {
123
- // "referenceId": 1,
124
- // "referenceType": "REFERENCE_TYPE_SUBJECT",
125
- // "referenceImage": {
126
- // "bytesBase64Encoded":
127
- // },
128
- // "subjectImageConfig" {
129
- // "subjectDescription": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
130
- // "subjectType": "SUBJECT_TYPE_DEFAULT"
131
- // }
132
- // }
133
- // ],
134
- // }
135
- // ],
136
- // "parameters": {
137
- // "aspectRatio": "1:1",
138
- // "sampleCount": 4,
139
- // "negativePrompt": "",
140
- // "enhancePrompt": false,
141
- // "personGeneration": "",
142
- // "safetySetting": "",
143
- // "addWatermark": true,
144
- // "includeRaiReason": true,
145
- // "language": "auto",
146
- // }
147
- // }
148
- // curl \
149
- // -X POST \
150
- // -H "Content-Type: application/json" \
151
- // -H "Authorization: Bearer $(gcloud auth print-access-token)" \
152
- // "https://${API_ENDPOINT}/v1/projects/${PROJECT_ID}/locations/${LOCATION_ID}/publishers/google/models/${MODEL_ID}:predict" -d '@request.json'
153
- // ARGs: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/imagen-api?authuser=4#rest_1
154
- var resp = await (await fetch(
155
- 'https://generativelanguage.googleapis.com/v1beta/models/'
156
- + `${IMAGEN_MODEL}:predict?key=${client.apiKey}`, {
157
- method: 'POST', headers: { 'Content-Type': 'application/json' },
158
- body: JSON.stringify({
159
- instances: [{ prompt }], parameters: {
160
- // "1:1" (default), "3:4", "4:3", "9:16", and "16:9"
161
- aspectRatio: '16:9', includeRaiReason: true,
162
- personGeneration: 'allow_adult', sampleCount: n,
163
- ...options?.params || {},
164
- },
165
- })
166
- })).json();
167
- assert(!resp?.error, resp?.error?.message || ERROR_GENERATING);
111
+ var resp = await client.gen.models.generateImages({
112
+ model: IMAGEN_MODEL, prompt, config: {
113
+ numberOfImages: n, includeRaiReason: true,
114
+ // "1:1" (default), "3:4", "4:3", "9:16", and "16:9"
115
+ aspectRatio: '16:9', personGeneration: 'allow_adult',
116
+ ...options?.config || {},
117
+ },
118
+ });
119
+ const generated = resp?.generatedImages;
120
+ assert(!resp?.error && generated?.filter(
121
+ x => !x.raiFilteredReason
122
+ ).length, resp?.error?.message || generated?.find(
123
+ x => x.raiFilteredReason
124
+ )?.raiFilteredReason || ERROR_GENERATING);
168
125
  if (!options?.raw) {
169
- resp = await Promise.all((resp?.predictions || []).map(
126
+ resp = await Promise.all((resp?.generatedImages || []).map(
170
127
  async x => ({
171
128
  caption: `🎨 by ${IMAGEN_MODEL}`,
172
- data: await extractImage(x.bytesBase64Encoded, options),
129
+ data: await extractImage(x.image.imageBytes, options),
173
130
  mimeType: x.mimeType,
174
131
  })
175
132
  ));
@@ -180,46 +137,11 @@ const image = async (prompt, options) => {
180
137
  }
181
138
  };
182
139
 
183
- const getGeminiAccessToken = async (credentials) => {
184
- const bin = 'gcloud';
185
- await assertExist(bin);
186
- const actResp = await ignoreErrFunc(async () => await exec(
187
- `${bin} auth activate-service-account --key-file=${credentials}`,
188
- { acceptError: true }
189
- ), { log: true });
190
- assert(actResp?.includes?.('Activated service account credentials'),
191
- 'Failed to activate service account credentials.', 500);
192
- const tokResp = (await exec(`gcloud auth print-access-token`)).trim();
193
- assert(tokResp, 'Failed to get access token.', 500);
194
- return tokResp;
195
- };
196
-
197
- const getGeminiVideo = async (jobId, accessToken) => {
198
- const client = clients?.[GEMINI];
199
- assert(client, 'No available video generation provider.');
200
- const resp = await (await fetch(
201
- 'https://us-central1-aiplatform.googleapis.com/v1/projects/'
202
- + `${client.projectId}/locations/us-central1/publishers/google/models/`
203
- + `${VEO_MODEL}:fetchPredictOperation`, {
204
- method: 'POST', headers: {
205
- 'Content-Type': 'application/json',
206
- 'Authorization': `Bearer ${accessToken}`,
207
- }, body: JSON.stringify({ operationName: jobId })
208
- })).json();
209
- assert(resp?.response?.videos?.length,
210
- 'Waiting for Gemini video generation: '
211
- + jobId.replace(/^.*\/([^/]+)$/, '$1'));
212
- return resp?.response?.videos;
213
- };
214
-
215
140
  const video = async (prompt, options) => {
216
141
  let provider = ensureString(options?.provider, { case: 'UP' });
217
- if (!provider
218
- && clients?.[GEMINI]?.credentials
219
- && clients?.[GEMINI]?.projectId) { provider = GEMINI; }
142
+ if (!provider && clients?.[GEMINI]) { provider = GEMINI; }
220
143
  const client = clients?.[provider];
221
144
  assert(client, 'No available video generation provider.');
222
- const accessToken = await getGeminiAccessToken(client.credentials);
223
145
  prompt = ensureString(prompt);
224
146
  assert(prompt.length <= 4000,
225
147
  'Prompt must be less than 4000 characters.', 400);
@@ -229,39 +151,50 @@ const video = async (prompt, options) => {
229
151
  };
230
152
  switch (provider) {
231
153
  case GEMINI:
232
- var resp = await (await fetch(
233
- 'https://us-central1-aiplatform.googleapis.com/v1/projects/'
234
- + `${client.projectId}/locations/us-central1/publishers/google/`
235
- + `models/${VEO_MODEL}:predictLongRunning`, {
236
- method: 'POST', headers: {
237
- 'Content-Type': 'application/json',
238
- 'Authorization': `Bearer ${accessToken}`,
154
+ var resp = await client.gen.models.generateVideos({
155
+ model: VEO_MODEL, prompt, config: {
156
+ aspectRatio: '16:9', numberOfVideos: 1,
157
+ personGeneration: 'allow_adult',
158
+ enablePromptRewriting: true, addWatermark: false,
159
+ includeRaiReason: true, ...options?.config || {},
239
160
  },
240
- body: JSON.stringify({
241
- instances: [{ prompt }], parameters: {
242
- aspectRatio: '16:9', sampleCount: 4,
243
- durationSeconds: '8', fps: '24',
244
- personGeneration: 'allow_adult',
245
- enablePromptRewriting: true, addWatermark: false,
246
- includeRaiReason: true, ...options?.params || {},
247
- },
248
- })
249
- })).json();
250
- assert(
251
- !resp?.error && resp?.name,
252
- resp?.error?.message || ERROR_GENERATING
253
- );
161
+ });
162
+ assert(!resp?.error, resp?.error?.message || ERROR_GENERATING);
254
163
  if (options?.generateRaw) { return resp; }
255
- var videos = await tryUntil(async () => await getGeminiVideo(
256
- resp.name, accessToken
257
- ), { maxTry: 60 * 10, log });
258
- assert(videos?.length, 'Failed to generate Gemini video.');
259
- if (options?.videoRaw) { return videos; }
260
- return await Promise.all(videos.map(async x => ({
261
- caption: `🎥 by ${VEO_MODEL}`,
262
- data: await extractVideo(x.bytesBase64Encoded, options),
263
- mimeType: x.mimeType, jobId: resp.name,
264
- })));
164
+ await tryUntil(async () => {
165
+ resp = await client.gen.operations.getVideosOperation({
166
+ operation: resp,
167
+ });
168
+ assert(
169
+ resp?.done,
170
+ `Waiting for Gemini video generation: ${resp.name}`,
171
+ );
172
+ }, { maxTry: 60 * 10, log });
173
+ let generated = resp?.response?.generatedVideos;
174
+ assert(!resp?.error && generated?.filter(
175
+ x => !x.raiFilteredReason
176
+ ).length, resp?.error?.message || generated?.find(
177
+ x => x.raiFilteredReason
178
+ )?.raiFilteredReason || ERROR_GENERATING);
179
+ if (!options?.videoRaw) {
180
+ generated = await Promise.all(generated?.filter(
181
+ x => x?.video?.uri
182
+ ).map(async (x, i) => {
183
+ const downloadPath = `${getTempPath({
184
+ seed: x?.video?.uri
185
+ })}.mp4`;
186
+ // @todo: fix this
187
+ // https://github.com/googleapis/js-genai/compare/main...Leask:js-genai:main
188
+ await client.gen.files.download({ file: x, downloadPath });
189
+ await timeout(5000);
190
+ return {
191
+ caption: `🎥 by ${VEO_MODEL}`,
192
+ data: await extractVideo(downloadPath, options),
193
+ mimeType: MIME_MP4, jobId: resp.name,
194
+ };
195
+ }));
196
+ }
197
+ return generated;
265
198
  default:
266
199
  throw new Error('Invalid provider.');
267
200
  }
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.78",
4
+ "version": "1999.1.80",
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.78",
4
+ "version": "1999.1.80",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",