@loonylabs/tti-middleware 1.4.0 → 1.5.0
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/.env.example +1 -1
- package/README.md +21 -15
- package/dist/middleware/services/tti/providers/base-tti-provider.d.ts +2 -1
- package/dist/middleware/services/tti/providers/base-tti-provider.js +2 -1
- package/dist/middleware/services/tti/providers/google-cloud-provider.d.ts +5 -1
- package/dist/middleware/services/tti/providers/google-cloud-provider.js +117 -37
- package/dist/middleware/services/tti/utils/debug-tti.utils.d.ts +2 -0
- package/dist/middleware/services/tti/utils/debug-tti.utils.js +4 -0
- package/dist/middleware/types/index.d.ts +1 -1
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -16,7 +16,7 @@ TTI_DEFAULT_PROVIDER=google-cloud
|
|
|
16
16
|
# Google Cloud (Vertex AI) - RECOMMENDED FOR EU/GDPR
|
|
17
17
|
# ============================================================
|
|
18
18
|
# DPA: https://cloud.google.com/terms/data-processing-addendum
|
|
19
|
-
# Models: imagen-3, gemini-flash-image
|
|
19
|
+
# Models: imagen-3, imagen-4 (+ fast/ultra), gemini-flash-image, gemini-pro-image
|
|
20
20
|
|
|
21
21
|
# Project ID (required)
|
|
22
22
|
GOOGLE_CLOUD_PROJECT=your-google-cloud-project-id
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# TTI Middleware
|
|
4
4
|
|
|
5
|
-
*Provider-agnostic Text-to-Image middleware with **GDPR compliance** and **character consistency** support. Currently supports Google Cloud (Imagen 3, Gemini Flash Image), Eden AI, and IONOS. Features EU data residency via Vertex AI, automatic region fallback, retry logic, and comprehensive error handling.*
|
|
5
|
+
*Provider-agnostic Text-to-Image middleware with **GDPR compliance** and **character consistency** support. Currently supports Google Cloud (Imagen 3, Imagen 4, Gemini Flash Image, Gemini 3 Pro Image), Eden AI, and IONOS. Features EU data residency via Vertex AI, automatic region fallback, retry logic, and comprehensive error handling.*
|
|
6
6
|
|
|
7
7
|
<!-- Horizontal Badge Navigation Bar -->
|
|
8
8
|
[](https://www.npmjs.com/package/@loonylabs/tti-middleware)
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
## Features
|
|
41
41
|
|
|
42
42
|
- **Multi-Provider Architecture**: Unified API for all TTI providers
|
|
43
|
-
- **Google Cloud** (Recommended): Imagen 3
|
|
43
|
+
- **Google Cloud** (Recommended): Imagen 3, Imagen 4, Gemini Flash Image & Gemini 3 Pro Image with EU data residency
|
|
44
44
|
- **Eden AI**: Aggregator with access to OpenAI, Stability AI, Replicate (experimental)
|
|
45
45
|
- **IONOS**: German cloud provider with OpenAI-compatible API (experimental)
|
|
46
46
|
- **Character Consistency**: Generate consistent characters across multiple images (perfect for children's book illustrations)
|
|
@@ -204,12 +204,18 @@ IONOS_API_URL=https://api.ionos.cloud/ai/v1
|
|
|
204
204
|
|
|
205
205
|
### Google Cloud (Recommended)
|
|
206
206
|
|
|
207
|
-
| Model | ID | Character Consistency | EU Regions |
|
|
208
|
-
|
|
209
|
-
| **Imagen 3** | `imagen-3` | No | All EU regions |
|
|
210
|
-
| **
|
|
207
|
+
| Model | ID | Character Consistency | EU Regions | Max Images |
|
|
208
|
+
|-------|-----|----------------------|------------|------------|
|
|
209
|
+
| **Imagen 3** | `imagen-3` | No | All EU regions | 4 |
|
|
210
|
+
| **Imagen 4** | `imagen-4` | No | All EU regions | 4 |
|
|
211
|
+
| **Imagen 4 Fast** | `imagen-4-fast` | No | All EU regions | 4 |
|
|
212
|
+
| **Imagen 4 Ultra** | `imagen-4-ultra` | No | All EU regions | 4 |
|
|
213
|
+
| **Gemini Flash Image** | `gemini-flash-image` | **Yes** | europe-west1, europe-west4, europe-north1 | 1 |
|
|
214
|
+
| **Gemini 3 Pro Image** | `gemini-pro-image` | No | `global` endpoint (auto-routed) | 1 |
|
|
211
215
|
|
|
212
|
-
**Important:**
|
|
216
|
+
**Important:**
|
|
217
|
+
- `gemini-flash-image` is **NOT available** in `europe-west3` (Frankfurt) - auto-fallback to EU alternative
|
|
218
|
+
- `gemini-pro-image` requires the **`global` Vertex AI endpoint** - the middleware handles this automatically
|
|
213
219
|
|
|
214
220
|
### Eden AI (Experimental)
|
|
215
221
|
|
|
@@ -227,13 +233,13 @@ IONOS_API_URL=https://api.ionos.cloud/ai/v1
|
|
|
227
233
|
|
|
228
234
|
### Google Cloud Region Availability
|
|
229
235
|
|
|
230
|
-
| Region | Location | Imagen 3 | Gemini Flash Image |
|
|
231
|
-
|
|
232
|
-
| `europe-west1` | Belgium | Yes | Yes |
|
|
233
|
-
| `europe-west3` | Frankfurt | Yes | **No** |
|
|
234
|
-
| `europe-west4` | Netherlands | Yes | **Yes (Recommended)** |
|
|
235
|
-
| `europe-north1` | Finland | Yes | Yes |
|
|
236
|
-
| `europe-west9` | Paris | Yes | No |
|
|
236
|
+
| Region | Location | Imagen 3 | Imagen 4 | Gemini Flash Image | Gemini Pro Image |
|
|
237
|
+
|--------|----------|----------|----------|-------------------|-----------------|
|
|
238
|
+
| `europe-west1` | Belgium | Yes | Yes | Yes | `global` endpoint |
|
|
239
|
+
| `europe-west3` | Frankfurt | Yes | Yes | **No** | `global` endpoint |
|
|
240
|
+
| `europe-west4` | Netherlands | Yes | Yes | **Yes (Recommended)** | `global` endpoint |
|
|
241
|
+
| `europe-north1` | Finland | Yes | Yes | Yes | `global` endpoint |
|
|
242
|
+
| `europe-west9` | Paris | Yes | Yes | No | `global` endpoint |
|
|
237
243
|
|
|
238
244
|
## Character Consistency
|
|
239
245
|
|
|
@@ -354,7 +360,7 @@ class TTIService {
|
|
|
354
360
|
```typescript
|
|
355
361
|
interface TTIRequest {
|
|
356
362
|
prompt: string;
|
|
357
|
-
model?: string; // 'imagen-3', 'gemini-flash-image', etc.
|
|
363
|
+
model?: string; // 'imagen-3', 'imagen-4', 'gemini-flash-image', 'gemini-pro-image', etc.
|
|
358
364
|
n?: number; // Number of images (default: 1)
|
|
359
365
|
aspectRatio?: string; // '1:1', '16:9', '4:3', etc.
|
|
360
366
|
|
|
@@ -22,7 +22,8 @@ export declare class ProviderUnavailableError extends TTIError {
|
|
|
22
22
|
constructor(provider: string, message?: string, cause?: Error);
|
|
23
23
|
}
|
|
24
24
|
export declare class GenerationFailedError extends TTIError {
|
|
25
|
-
|
|
25
|
+
readonly modelResponse?: string;
|
|
26
|
+
constructor(provider: string, message: string, cause?: Error, modelResponse?: string);
|
|
26
27
|
}
|
|
27
28
|
export declare class NetworkError extends TTIError {
|
|
28
29
|
constructor(provider: string, message: string, cause?: Error);
|
|
@@ -55,9 +55,10 @@ class ProviderUnavailableError extends TTIError {
|
|
|
55
55
|
}
|
|
56
56
|
exports.ProviderUnavailableError = ProviderUnavailableError;
|
|
57
57
|
class GenerationFailedError extends TTIError {
|
|
58
|
-
constructor(provider, message, cause) {
|
|
58
|
+
constructor(provider, message, cause, modelResponse) {
|
|
59
59
|
super(provider, 'GENERATION_FAILED', message, cause);
|
|
60
60
|
this.name = 'GenerationFailedError';
|
|
61
|
+
this.modelResponse = modelResponse;
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
exports.GenerationFailedError = GenerationFailedError;
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Unified provider for Google Cloud's image generation services:
|
|
5
5
|
* - Imagen 3 (imagegeneration@006) - High quality text-to-image
|
|
6
|
+
* - Imagen 4 (imagen-4.0-generate-001) - Next-gen text-to-image with enhanced quality
|
|
7
|
+
* - Imagen 4 Fast (imagen-4.0-fast-generate-001) - Faster inference variant
|
|
8
|
+
* - Imagen 4 Ultra (imagen-4.0-ultra-generate-001) - Highest quality variant
|
|
6
9
|
* - Gemini 2.5 Flash Image - Text-to-image with character consistency
|
|
10
|
+
* - Gemini 3 Pro Image (gemini-3-pro-image-preview) - 4K, text rendering
|
|
7
11
|
*
|
|
8
12
|
* All requests go through Google Cloud (Vertex AI) with proper DPA.
|
|
9
13
|
* EU-compliant when using EU regions.
|
|
@@ -31,7 +35,7 @@ export declare class GoogleCloudTTIProvider extends BaseTTIProvider {
|
|
|
31
35
|
private config;
|
|
32
36
|
private lastUsedRegion;
|
|
33
37
|
private aiplatformClient;
|
|
34
|
-
private
|
|
38
|
+
private genaiClients;
|
|
35
39
|
constructor(config?: Partial<GoogleCloudConfig>);
|
|
36
40
|
getDisplayName(): string;
|
|
37
41
|
listModels(): ModelInfo[];
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Unified provider for Google Cloud's image generation services:
|
|
6
6
|
* - Imagen 3 (imagegeneration@006) - High quality text-to-image
|
|
7
|
+
* - Imagen 4 (imagen-4.0-generate-001) - Next-gen text-to-image with enhanced quality
|
|
8
|
+
* - Imagen 4 Fast (imagen-4.0-fast-generate-001) - Faster inference variant
|
|
9
|
+
* - Imagen 4 Ultra (imagen-4.0-ultra-generate-001) - Highest quality variant
|
|
7
10
|
* - Gemini 2.5 Flash Image - Text-to-image with character consistency
|
|
11
|
+
* - Gemini 3 Pro Image (gemini-3-pro-image-preview) - 4K, text rendering
|
|
8
12
|
*
|
|
9
13
|
* All requests go through Google Cloud (Vertex AI) with proper DPA.
|
|
10
14
|
* EU-compliant when using EU regions.
|
|
@@ -53,7 +57,23 @@ const debug_tti_utils_1 = require("../utils/debug-tti.utils");
|
|
|
53
57
|
// ============================================================
|
|
54
58
|
// MODEL DEFINITIONS
|
|
55
59
|
// ============================================================
|
|
60
|
+
// Imagen 4 is available in most EU regions
|
|
61
|
+
const IMAGEN_4_REGIONS = [
|
|
62
|
+
'europe-west1',
|
|
63
|
+
'europe-west2',
|
|
64
|
+
'europe-west3',
|
|
65
|
+
'europe-west4',
|
|
66
|
+
'europe-west6',
|
|
67
|
+
'europe-west8',
|
|
68
|
+
'europe-west9',
|
|
69
|
+
'europe-north1',
|
|
70
|
+
'europe-central2',
|
|
71
|
+
'europe-southwest1',
|
|
72
|
+
'us-central1',
|
|
73
|
+
'us-east4',
|
|
74
|
+
];
|
|
56
75
|
const GOOGLE_CLOUD_MODELS = [
|
|
76
|
+
// ── Imagen models (Vertex AI predict API) ──────────────────
|
|
57
77
|
{
|
|
58
78
|
id: 'imagen-3',
|
|
59
79
|
displayName: 'Imagen 3',
|
|
@@ -74,6 +94,43 @@ const GOOGLE_CLOUD_MODELS = [
|
|
|
74
94
|
],
|
|
75
95
|
pricingUrl: 'https://cloud.google.com/vertex-ai/generative-ai/pricing',
|
|
76
96
|
},
|
|
97
|
+
{
|
|
98
|
+
id: 'imagen-4',
|
|
99
|
+
displayName: 'Imagen 4',
|
|
100
|
+
capabilities: {
|
|
101
|
+
textToImage: true,
|
|
102
|
+
characterConsistency: false,
|
|
103
|
+
imageEditing: false,
|
|
104
|
+
maxImagesPerRequest: 4,
|
|
105
|
+
},
|
|
106
|
+
availableRegions: IMAGEN_4_REGIONS,
|
|
107
|
+
pricingUrl: 'https://cloud.google.com/vertex-ai/generative-ai/pricing',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'imagen-4-fast',
|
|
111
|
+
displayName: 'Imagen 4 Fast',
|
|
112
|
+
capabilities: {
|
|
113
|
+
textToImage: true,
|
|
114
|
+
characterConsistency: false,
|
|
115
|
+
imageEditing: false,
|
|
116
|
+
maxImagesPerRequest: 4,
|
|
117
|
+
},
|
|
118
|
+
availableRegions: IMAGEN_4_REGIONS,
|
|
119
|
+
pricingUrl: 'https://cloud.google.com/vertex-ai/generative-ai/pricing',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'imagen-4-ultra',
|
|
123
|
+
displayName: 'Imagen 4 Ultra',
|
|
124
|
+
capabilities: {
|
|
125
|
+
textToImage: true,
|
|
126
|
+
characterConsistency: false,
|
|
127
|
+
imageEditing: false,
|
|
128
|
+
maxImagesPerRequest: 4,
|
|
129
|
+
},
|
|
130
|
+
availableRegions: IMAGEN_4_REGIONS,
|
|
131
|
+
pricingUrl: 'https://cloud.google.com/vertex-ai/generative-ai/pricing',
|
|
132
|
+
},
|
|
133
|
+
// ── Gemini models (Vertex AI generateContent API) ──────────
|
|
77
134
|
{
|
|
78
135
|
id: 'gemini-flash-image',
|
|
79
136
|
displayName: 'Gemini 2.5 Flash Image',
|
|
@@ -93,12 +150,31 @@ const GOOGLE_CLOUD_MODELS = [
|
|
|
93
150
|
],
|
|
94
151
|
pricingUrl: 'https://cloud.google.com/vertex-ai/generative-ai/pricing',
|
|
95
152
|
},
|
|
153
|
+
{
|
|
154
|
+
id: 'gemini-pro-image',
|
|
155
|
+
displayName: 'Gemini 3 Pro Image',
|
|
156
|
+
capabilities: {
|
|
157
|
+
textToImage: true,
|
|
158
|
+
characterConsistency: false, // Not documented for this model
|
|
159
|
+
imageEditing: false,
|
|
160
|
+
maxImagesPerRequest: 1,
|
|
161
|
+
},
|
|
162
|
+
// Requires global endpoint - regional endpoints return 404
|
|
163
|
+
availableRegions: ['global'],
|
|
164
|
+
pricingUrl: 'https://cloud.google.com/vertex-ai/generative-ai/pricing',
|
|
165
|
+
},
|
|
96
166
|
];
|
|
97
|
-
// Internal model IDs used in API calls
|
|
167
|
+
// Internal model IDs used in Vertex AI API calls
|
|
98
168
|
const MODEL_ID_MAP = {
|
|
99
169
|
'imagen-3': 'imagegeneration@006',
|
|
170
|
+
'imagen-4': 'imagen-4.0-generate-001',
|
|
171
|
+
'imagen-4-fast': 'imagen-4.0-fast-generate-001',
|
|
172
|
+
'imagen-4-ultra': 'imagen-4.0-ultra-generate-001',
|
|
100
173
|
'gemini-flash-image': 'gemini-2.5-flash-image',
|
|
174
|
+
'gemini-pro-image': 'gemini-3-pro-image-preview',
|
|
101
175
|
};
|
|
176
|
+
// Models that use the Gemini generateContent API (vs Imagen predict API)
|
|
177
|
+
const GEMINI_API_MODELS = new Set(['gemini-flash-image', 'gemini-pro-image']);
|
|
102
178
|
// ============================================================
|
|
103
179
|
// PROVIDER IMPLEMENTATION
|
|
104
180
|
// ============================================================
|
|
@@ -109,7 +185,7 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
109
185
|
// Lazy-loaded SDK clients
|
|
110
186
|
this.aiplatformClient = null;
|
|
111
187
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
|
-
this.
|
|
188
|
+
this.genaiClients = new Map();
|
|
113
189
|
const projectId = config?.projectId ||
|
|
114
190
|
process.env.GOOGLE_CLOUD_PROJECT ||
|
|
115
191
|
process.env.GCLOUD_PROJECT ||
|
|
@@ -170,17 +246,13 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
170
246
|
hasReferenceImages: (0, base_tti_provider_1.hasReferenceImages)(request),
|
|
171
247
|
});
|
|
172
248
|
try {
|
|
173
|
-
// Route to appropriate
|
|
249
|
+
// Route to appropriate API based on model type
|
|
174
250
|
let response;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
response = await this.executeWithRetry(request, () => this.generateWithGemini(request, effectiveRegion), 'Gemini API call');
|
|
181
|
-
break;
|
|
182
|
-
default:
|
|
183
|
-
throw new base_tti_provider_1.InvalidConfigError(this.providerName, `Unknown model: ${modelId}`);
|
|
251
|
+
if (GEMINI_API_MODELS.has(modelId)) {
|
|
252
|
+
response = await this.executeWithRetry(request, () => this.generateWithGemini(request, modelId, effectiveRegion), 'Gemini API call');
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
response = await this.executeWithRetry(request, () => this.generateWithImagen(request, modelId, effectiveRegion), 'Imagen API call');
|
|
184
256
|
}
|
|
185
257
|
// Log successful response
|
|
186
258
|
if (debugInfo) {
|
|
@@ -224,6 +296,11 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
224
296
|
if (!modelInfo?.availableRegions) {
|
|
225
297
|
return this.config.region;
|
|
226
298
|
}
|
|
299
|
+
// Model requires global endpoint (e.g. preview models)
|
|
300
|
+
if (modelInfo.availableRegions.length === 1 && modelInfo.availableRegions[0] === 'global') {
|
|
301
|
+
this.log('info', `Model ${modelId} requires global endpoint`, { configuredRegion: this.config.region, effectiveRegion: 'global' });
|
|
302
|
+
return 'global';
|
|
303
|
+
}
|
|
227
304
|
// Check if configured region supports this model
|
|
228
305
|
if (modelInfo.availableRegions.includes(this.config.region)) {
|
|
229
306
|
return this.config.region;
|
|
@@ -241,11 +318,11 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
241
318
|
return fallback;
|
|
242
319
|
}
|
|
243
320
|
// ============================================================
|
|
244
|
-
// PRIVATE: IMAGEN
|
|
321
|
+
// PRIVATE: IMAGEN IMPLEMENTATION
|
|
245
322
|
// ============================================================
|
|
246
|
-
async generateWithImagen(request, region) {
|
|
323
|
+
async generateWithImagen(request, modelId, region) {
|
|
247
324
|
const startTime = Date.now();
|
|
248
|
-
const internalModelId = MODEL_ID_MAP[
|
|
325
|
+
const internalModelId = MODEL_ID_MAP[modelId];
|
|
249
326
|
this.lastUsedRegion = region;
|
|
250
327
|
try {
|
|
251
328
|
const { client, helpers } = await this.getAiplatformClient();
|
|
@@ -284,7 +361,7 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
284
361
|
if (!response.predictions || response.predictions.length === 0) {
|
|
285
362
|
throw new base_tti_provider_1.GenerationFailedError(this.providerName, 'No images returned from Imagen API');
|
|
286
363
|
}
|
|
287
|
-
return this.processImagenResponse(response.predictions, helpers, duration);
|
|
364
|
+
return this.processImagenResponse(response.predictions, helpers, modelId, duration);
|
|
288
365
|
}
|
|
289
366
|
catch (error) {
|
|
290
367
|
if (error instanceof base_tti_provider_1.InvalidConfigError || error instanceof base_tti_provider_1.GenerationFailedError) {
|
|
@@ -323,7 +400,7 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
323
400
|
helpers: helpers,
|
|
324
401
|
};
|
|
325
402
|
}
|
|
326
|
-
processImagenResponse(predictions, helpers, duration) {
|
|
403
|
+
processImagenResponse(predictions, helpers, modelId, duration) {
|
|
327
404
|
const images = [];
|
|
328
405
|
for (const prediction of predictions) {
|
|
329
406
|
const predictionObj = helpers.fromValue(prediction);
|
|
@@ -339,13 +416,13 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
339
416
|
}
|
|
340
417
|
const usage = {
|
|
341
418
|
imagesGenerated: images.length,
|
|
342
|
-
modelId
|
|
419
|
+
modelId,
|
|
343
420
|
};
|
|
344
421
|
return {
|
|
345
422
|
images,
|
|
346
423
|
metadata: {
|
|
347
424
|
provider: this.providerName,
|
|
348
|
-
model:
|
|
425
|
+
model: modelId,
|
|
349
426
|
region: this.lastUsedRegion || this.config.region,
|
|
350
427
|
duration,
|
|
351
428
|
},
|
|
@@ -353,11 +430,11 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
353
430
|
};
|
|
354
431
|
}
|
|
355
432
|
// ============================================================
|
|
356
|
-
// PRIVATE: GEMINI
|
|
433
|
+
// PRIVATE: GEMINI IMAGE IMPLEMENTATION
|
|
357
434
|
// ============================================================
|
|
358
|
-
async generateWithGemini(request, region) {
|
|
435
|
+
async generateWithGemini(request, modelId, region) {
|
|
359
436
|
const startTime = Date.now();
|
|
360
|
-
const internalModelId = MODEL_ID_MAP[
|
|
437
|
+
const internalModelId = MODEL_ID_MAP[modelId];
|
|
361
438
|
this.lastUsedRegion = region;
|
|
362
439
|
try {
|
|
363
440
|
const client = await this.getGenaiClient(region);
|
|
@@ -420,7 +497,7 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
420
497
|
duration,
|
|
421
498
|
hasCandidates: !!(response?.candidates || response?.response),
|
|
422
499
|
});
|
|
423
|
-
return this.processGeminiResponse(response, duration);
|
|
500
|
+
return this.processGeminiResponse(response, modelId, duration);
|
|
424
501
|
}
|
|
425
502
|
catch (error) {
|
|
426
503
|
if (error instanceof base_tti_provider_1.InvalidConfigError || error instanceof base_tti_provider_1.GenerationFailedError) {
|
|
@@ -431,19 +508,16 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
431
508
|
}
|
|
432
509
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
433
510
|
async getGenaiClient(region) {
|
|
434
|
-
//
|
|
435
|
-
if (!this.
|
|
511
|
+
// Vertex AI client (one per region, since region is baked into the client)
|
|
512
|
+
if (!this.genaiClients.has(region)) {
|
|
436
513
|
try {
|
|
437
514
|
const { GoogleGenAI } = await Promise.resolve().then(() => __importStar(require('@google/genai')));
|
|
438
|
-
|
|
439
|
-
process.env.GOOGLE_GENAI_USE_VERTEXAI = 'true';
|
|
440
|
-
process.env.GOOGLE_CLOUD_PROJECT = this.config.projectId;
|
|
441
|
-
process.env.GOOGLE_CLOUD_LOCATION = region;
|
|
442
|
-
this.genaiClient = new GoogleGenAI({
|
|
515
|
+
const client = new GoogleGenAI({
|
|
443
516
|
vertexai: true,
|
|
444
517
|
project: this.config.projectId,
|
|
445
518
|
location: region,
|
|
446
519
|
});
|
|
520
|
+
this.genaiClients.set(region, client);
|
|
447
521
|
this.log('debug', 'Initialized @google/genai with Vertex AI backend', {
|
|
448
522
|
project: this.config.projectId,
|
|
449
523
|
location: region,
|
|
@@ -453,10 +527,11 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
453
527
|
throw new base_tti_provider_1.InvalidConfigError(this.providerName, `Failed to load @google/genai. Install it with: npm install @google/genai`, error);
|
|
454
528
|
}
|
|
455
529
|
}
|
|
530
|
+
const client = this.genaiClients.get(region);
|
|
456
531
|
return {
|
|
457
532
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
458
533
|
generateContent: async (params) => {
|
|
459
|
-
return
|
|
534
|
+
return client.models.generateContent(params);
|
|
460
535
|
},
|
|
461
536
|
};
|
|
462
537
|
}
|
|
@@ -467,7 +542,7 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
467
542
|
IMPORTANT: Maintain exact visual consistency with the subject in the reference - same style, colors, proportions, and distinctive features. The subject should be immediately recognizable as the same one from the reference.`;
|
|
468
543
|
}
|
|
469
544
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
470
|
-
processGeminiResponse(response, duration) {
|
|
545
|
+
processGeminiResponse(response, modelId, duration) {
|
|
471
546
|
const images = [];
|
|
472
547
|
const candidates = response?.candidates || response?.response?.candidates;
|
|
473
548
|
if (!candidates || candidates.length === 0) {
|
|
@@ -486,9 +561,12 @@ IMPORTANT: Maintain exact visual consistency with the subject in the reference -
|
|
|
486
561
|
}
|
|
487
562
|
if (images.length === 0) {
|
|
488
563
|
const firstParts = candidates[0]?.content?.parts || [];
|
|
564
|
+
const fullTexts = [];
|
|
489
565
|
const partTypes = firstParts.map((p) => {
|
|
490
|
-
if (p.text)
|
|
491
|
-
|
|
566
|
+
if (p.text) {
|
|
567
|
+
fullTexts.push(p.text);
|
|
568
|
+
return `text(${p.text.substring(0, 200)}${p.text.length > 200 ? '...' : ''})`;
|
|
569
|
+
}
|
|
492
570
|
if (p.inlineData)
|
|
493
571
|
return `inlineData(${p.inlineData.mimeType})`;
|
|
494
572
|
return 'unknown';
|
|
@@ -496,19 +574,21 @@ IMPORTANT: Maintain exact visual consistency with the subject in the reference -
|
|
|
496
574
|
this.log('error', 'No images in Gemini response', {
|
|
497
575
|
candidateCount: candidates.length,
|
|
498
576
|
partTypes,
|
|
577
|
+
...(fullTexts.length > 0 && { modelResponse: fullTexts.join('\n') }),
|
|
499
578
|
});
|
|
579
|
+
const fullModelResponse = fullTexts.length > 0 ? fullTexts.join('\n') : undefined;
|
|
500
580
|
throw new base_tti_provider_1.GenerationFailedError(this.providerName, `No images in response. Model returned: ${partTypes.join(', ')}. ` +
|
|
501
|
-
'Make sure responseModalities includes IMAGE.');
|
|
581
|
+
'Make sure responseModalities includes IMAGE.', undefined, fullModelResponse);
|
|
502
582
|
}
|
|
503
583
|
const usage = {
|
|
504
584
|
imagesGenerated: images.length,
|
|
505
|
-
modelId
|
|
585
|
+
modelId,
|
|
506
586
|
};
|
|
507
587
|
return {
|
|
508
588
|
images,
|
|
509
589
|
metadata: {
|
|
510
590
|
provider: this.providerName,
|
|
511
|
-
model:
|
|
591
|
+
model: modelId,
|
|
512
592
|
region: this.lastUsedRegion || this.config.region,
|
|
513
593
|
duration,
|
|
514
594
|
},
|
|
@@ -45,6 +45,7 @@ export interface TTIDebugInfo {
|
|
|
45
45
|
message: string;
|
|
46
46
|
code?: string;
|
|
47
47
|
details?: unknown;
|
|
48
|
+
modelResponse?: string;
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
/**
|
|
@@ -145,6 +146,7 @@ export declare class TTIDebugger {
|
|
|
145
146
|
static updateWithError(debugInfo: TTIDebugInfo, error: Error & {
|
|
146
147
|
code?: string;
|
|
147
148
|
cause?: unknown;
|
|
149
|
+
modelResponse?: string;
|
|
148
150
|
}): TTIDebugInfo;
|
|
149
151
|
}
|
|
150
152
|
export default TTIDebugger;
|
|
@@ -297,6 +297,9 @@ class TTIDebugger {
|
|
|
297
297
|
if (debugInfo.error.code) {
|
|
298
298
|
sections.push(`- **Code**: ${debugInfo.error.code}`);
|
|
299
299
|
}
|
|
300
|
+
if (debugInfo.error.modelResponse) {
|
|
301
|
+
sections.push(`- **Model Response**: ${debugInfo.error.modelResponse}`);
|
|
302
|
+
}
|
|
300
303
|
if (debugInfo.error.details) {
|
|
301
304
|
sections.push('- **Details**:');
|
|
302
305
|
sections.push('```json');
|
|
@@ -401,6 +404,7 @@ class TTIDebugger {
|
|
|
401
404
|
message: error.message,
|
|
402
405
|
code: error.code,
|
|
403
406
|
details: error.cause,
|
|
407
|
+
modelResponse: error.modelResponse,
|
|
404
408
|
},
|
|
405
409
|
};
|
|
406
410
|
}
|
|
@@ -50,7 +50,7 @@ export interface ModelInfo {
|
|
|
50
50
|
* Google Cloud regions
|
|
51
51
|
* EU regions are GDPR-compliant
|
|
52
52
|
*/
|
|
53
|
-
export type GoogleCloudRegion = 'europe-west1' | 'europe-west2' | 'europe-west3' | 'europe-west4' | 'europe-west9' | 'us-central1' | 'us-east4';
|
|
53
|
+
export type GoogleCloudRegion = 'global' | 'europe-west1' | 'europe-west2' | 'europe-west3' | 'europe-west4' | 'europe-west9' | 'us-central1' | 'us-east4';
|
|
54
54
|
/**
|
|
55
55
|
* Reference image for character consistency
|
|
56
56
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loonylabs/tti-middleware",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Provider-agnostic Text-to-Image middleware with GDPR compliance. Supports Google Cloud (Imagen, Gemini), Eden AI, and IONOS.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|