@loonylabs/tti-middleware 1.0.0 → 1.1.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/README.md +90 -6
- package/dist/middleware/services/tti/index.d.ts +1 -0
- package/dist/middleware/services/tti/index.js +1 -0
- package/dist/middleware/services/tti/providers/base-tti-provider.js +8 -3
- package/dist/middleware/services/tti/providers/google-cloud-provider.js +44 -11
- package/dist/middleware/services/tti/utils/debug-tti.utils.d.ts +150 -0
- package/dist/middleware/services/tti/utils/debug-tti.utils.js +418 -0
- package/dist/middleware/services/tti/utils/index.d.ts +1 -0
- package/dist/middleware/services/tti/utils/index.js +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
- **Retry Logic**: Automatic retry for rate limits (429) with configurable backoff
|
|
49
49
|
- **TypeScript First**: Full type safety with comprehensive interfaces
|
|
50
50
|
- **Logging Control**: Configurable log levels via environment or API
|
|
51
|
+
- **Debug Logging**: Markdown file logging for debugging prompts and responses
|
|
51
52
|
- **Error Handling**: Typed error classes for precise error handling
|
|
52
53
|
|
|
53
54
|
## Quick Start
|
|
@@ -103,7 +104,7 @@ const character = await service.generate({
|
|
|
103
104
|
model: 'gemini-flash-image', // Only this model supports character consistency!
|
|
104
105
|
});
|
|
105
106
|
|
|
106
|
-
// 2. Generate new scenes with the same character
|
|
107
|
+
// 2. Generate new scenes with the same character (Structured Mode)
|
|
107
108
|
const scene = await service.generate({
|
|
108
109
|
prompt: 'dancing happily in the rain, jumping in puddles',
|
|
109
110
|
model: 'gemini-flash-image',
|
|
@@ -113,6 +114,17 @@ const scene = await service.generate({
|
|
|
113
114
|
}],
|
|
114
115
|
subjectDescription: 'cute cartoon bear with red hat and blue scarf',
|
|
115
116
|
});
|
|
117
|
+
|
|
118
|
+
// 3. Or use Index-Based Mode for multiple characters
|
|
119
|
+
const multiCharScene = await service.generate({
|
|
120
|
+
prompt: 'The FIRST reference image character meets the SECOND reference image character',
|
|
121
|
+
model: 'gemini-flash-image',
|
|
122
|
+
referenceImages: [
|
|
123
|
+
{ base64: character1.images[0].base64!, mimeType: 'image/png' },
|
|
124
|
+
{ base64: character2.images[0].base64!, mimeType: 'image/png' },
|
|
125
|
+
],
|
|
126
|
+
// subjectDescription omitted = Index-Based Mode
|
|
127
|
+
});
|
|
116
128
|
```
|
|
117
129
|
|
|
118
130
|
**Important:** Character consistency is only supported by `gemini-flash-image` model!
|
|
@@ -225,7 +237,11 @@ IONOS_API_URL=https://api.ionos.cloud/ai/v1
|
|
|
225
237
|
|
|
226
238
|
## Character Consistency
|
|
227
239
|
|
|
228
|
-
Generate consistent characters across multiple images - perfect for children's book illustrations
|
|
240
|
+
Generate consistent characters across multiple images - perfect for children's book illustrations.
|
|
241
|
+
|
|
242
|
+
### Mode 1: Structured Mode (Single Character)
|
|
243
|
+
|
|
244
|
+
Best for scenes with a single consistent character:
|
|
229
245
|
|
|
230
246
|
```typescript
|
|
231
247
|
// Step 1: Create a character
|
|
@@ -242,15 +258,47 @@ for (const scene of scenes) {
|
|
|
242
258
|
prompt: scene,
|
|
243
259
|
model: 'gemini-flash-image',
|
|
244
260
|
referenceImages: [{ base64: bear.images[0].base64!, mimeType: 'image/png' }],
|
|
245
|
-
subjectDescription: 'cute cartoon bear with red hat',
|
|
261
|
+
subjectDescription: 'cute cartoon bear with red hat', // Required in structured mode
|
|
246
262
|
});
|
|
247
263
|
// Save result...
|
|
248
264
|
}
|
|
249
265
|
```
|
|
250
266
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
267
|
+
### Mode 2: Index-Based Mode (Multiple Characters)
|
|
268
|
+
|
|
269
|
+
Best for scenes with multiple distinct characters. Reference images directly in your prompt by their position:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Load two different character references
|
|
273
|
+
const cowboy1 = await loadImage('cowboy1.png');
|
|
274
|
+
const cowboy2 = await loadImage('cowboy2.png');
|
|
275
|
+
|
|
276
|
+
// Reference each image by index in the prompt
|
|
277
|
+
const duelScene = await service.generate({
|
|
278
|
+
prompt: `Generate a cinematic wide shot of a western duel.
|
|
279
|
+
- The character on the LEFT should look exactly like the person in the FIRST reference image.
|
|
280
|
+
- The character on the RIGHT should look exactly like the person in the SECOND reference image.
|
|
281
|
+
They are standing in a dusty street at high noon.`,
|
|
282
|
+
model: 'gemini-flash-image',
|
|
283
|
+
referenceImages: [
|
|
284
|
+
{ base64: cowboy1, mimeType: 'image/png' },
|
|
285
|
+
{ base64: cowboy2, mimeType: 'image/png' },
|
|
286
|
+
],
|
|
287
|
+
// subjectDescription intentionally omitted for index-based mode
|
|
288
|
+
aspectRatio: '16:9',
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Reference keywords:** Use "FIRST reference image", "SECOND reference image" or "Image 1", "Image 2" etc.
|
|
293
|
+
|
|
294
|
+
### Requirements
|
|
295
|
+
|
|
296
|
+
| Mode | `subjectDescription` | Use Case |
|
|
297
|
+
|------|---------------------|----------|
|
|
298
|
+
| **Structured** | Required | Single character across scenes |
|
|
299
|
+
| **Index-Based** | Omitted | Multiple characters in one scene |
|
|
300
|
+
|
|
301
|
+
- Model must be `gemini-flash-image` (only model supporting character consistency)
|
|
254
302
|
|
|
255
303
|
## GDPR / Compliance
|
|
256
304
|
|
|
@@ -405,6 +453,42 @@ Available levels: `debug`, `info`, `warn`, `error`, `silent`
|
|
|
405
453
|
|
|
406
454
|
</details>
|
|
407
455
|
|
|
456
|
+
<details>
|
|
457
|
+
<summary><strong>Debug Logging (Markdown Files)</strong></summary>
|
|
458
|
+
|
|
459
|
+
Log all TTI requests and responses to markdown files for debugging:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import { TTIDebugger } from '@loonylabs/tti-middleware';
|
|
463
|
+
|
|
464
|
+
// Enable via environment variable
|
|
465
|
+
// DEBUG_TTI_REQUESTS=true
|
|
466
|
+
|
|
467
|
+
// Or programmatically
|
|
468
|
+
TTIDebugger.setEnabled(true);
|
|
469
|
+
TTIDebugger.setLogsDir('./logs/tti/requests');
|
|
470
|
+
|
|
471
|
+
// Configure all options at once
|
|
472
|
+
TTIDebugger.configure({
|
|
473
|
+
enabled: true,
|
|
474
|
+
logsDir: './logs/tti/requests',
|
|
475
|
+
consoleLog: true, // Also log to console
|
|
476
|
+
includeBase64: false, // Exclude base64 data (default)
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Log file contents:**
|
|
481
|
+
- Provider, model, and region
|
|
482
|
+
- Full prompt text
|
|
483
|
+
- Subject description (for character consistency)
|
|
484
|
+
- Reference image metadata
|
|
485
|
+
- Response data (duration, image count)
|
|
486
|
+
- Errors with full details
|
|
487
|
+
|
|
488
|
+
**Use case:** Debug why character consistency isn't working by inspecting exactly what prompt and `subjectDescription` are being sent to the API.
|
|
489
|
+
|
|
490
|
+
</details>
|
|
491
|
+
|
|
408
492
|
<details>
|
|
409
493
|
<summary><strong>Error Handling</strong></summary>
|
|
410
494
|
|
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./tti.service"), exports);
|
|
18
18
|
__exportStar(require("./providers"), exports);
|
|
19
|
+
__exportStar(require("./utils"), exports);
|
|
@@ -133,9 +133,14 @@ class BaseTTIProvider {
|
|
|
133
133
|
throw new CapabilityNotSupportedError(this.providerName, 'characterConsistency', modelId);
|
|
134
134
|
}
|
|
135
135
|
// Validate subject description is provided
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
// RELAXED: We now allow missing subjectDescription to support raw multimodal prompting
|
|
137
|
+
// where the user references images directly in the prompt (e.g., "Image 1 is X...").
|
|
138
|
+
// if (!request.subjectDescription || request.subjectDescription.trim().length === 0) {
|
|
139
|
+
// throw new InvalidConfigError(
|
|
140
|
+
// this.providerName,
|
|
141
|
+
// 'subjectDescription is required when using referenceImages'
|
|
142
|
+
// );
|
|
143
|
+
// }
|
|
139
144
|
// Validate reference images have data
|
|
140
145
|
for (let i = 0; i < request.referenceImages.length; i++) {
|
|
141
146
|
const ref = request.referenceImages[i];
|
|
@@ -49,6 +49,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
49
49
|
exports.GoogleCloudTTIProvider = void 0;
|
|
50
50
|
const types_1 = require("../../../types");
|
|
51
51
|
const base_tti_provider_1 = require("./base-tti-provider");
|
|
52
|
+
const debug_tti_utils_1 = require("../utils/debug-tti.utils");
|
|
52
53
|
// ============================================================
|
|
53
54
|
// MODEL DEFINITIONS
|
|
54
55
|
// ============================================================
|
|
@@ -157,19 +158,44 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
157
158
|
}
|
|
158
159
|
// Validate region availability
|
|
159
160
|
const effectiveRegion = this.getEffectiveRegion(modelId);
|
|
161
|
+
// Create debug info for logging
|
|
162
|
+
let debugInfo = null;
|
|
163
|
+
if (debug_tti_utils_1.TTIDebugger.isEnabled) {
|
|
164
|
+
debugInfo = debug_tti_utils_1.TTIDebugger.createDebugInfo(request, this.providerName, modelId, { region: effectiveRegion });
|
|
165
|
+
await debug_tti_utils_1.TTIDebugger.logRequest(debugInfo);
|
|
166
|
+
}
|
|
160
167
|
this.log('debug', 'Generating image', {
|
|
161
168
|
model: modelId,
|
|
162
169
|
region: effectiveRegion,
|
|
163
170
|
hasReferenceImages: (0, base_tti_provider_1.hasReferenceImages)(request),
|
|
164
171
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
try {
|
|
173
|
+
// Route to appropriate implementation with retry support
|
|
174
|
+
let response;
|
|
175
|
+
switch (modelId) {
|
|
176
|
+
case 'imagen-3':
|
|
177
|
+
response = await this.executeWithRetry(request, () => this.generateWithImagen(request, effectiveRegion), 'Imagen API call');
|
|
178
|
+
break;
|
|
179
|
+
case 'gemini-flash-image':
|
|
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}`);
|
|
184
|
+
}
|
|
185
|
+
// Log successful response
|
|
186
|
+
if (debugInfo) {
|
|
187
|
+
debugInfo = debug_tti_utils_1.TTIDebugger.updateWithResponse(debugInfo, response);
|
|
188
|
+
await debug_tti_utils_1.TTIDebugger.logResponse(debugInfo);
|
|
189
|
+
}
|
|
190
|
+
return response;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
// Log error
|
|
194
|
+
if (debugInfo) {
|
|
195
|
+
debugInfo = debug_tti_utils_1.TTIDebugger.updateWithError(debugInfo, error);
|
|
196
|
+
await debug_tti_utils_1.TTIDebugger.logError(debugInfo);
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
173
199
|
}
|
|
174
200
|
}
|
|
175
201
|
// ============================================================
|
|
@@ -347,9 +373,16 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
347
373
|
},
|
|
348
374
|
});
|
|
349
375
|
}
|
|
350
|
-
// Build character consistency prompt
|
|
351
|
-
|
|
352
|
-
|
|
376
|
+
// Build character consistency prompt if subject description is provided
|
|
377
|
+
if (request.subjectDescription) {
|
|
378
|
+
const fullPrompt = this.buildCharacterConsistencyPrompt(request.prompt, request.subjectDescription, request.referenceImages.length);
|
|
379
|
+
parts.push({ text: fullPrompt });
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// No subject description - treat as raw multimodal prompt
|
|
383
|
+
// This allows "image 1", "image 2" style prompting
|
|
384
|
+
parts.push({ text: request.prompt });
|
|
385
|
+
}
|
|
353
386
|
}
|
|
354
387
|
else {
|
|
355
388
|
parts.push({ text: request.prompt });
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTI Debugger Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides markdown-based logging for TTI requests, similar to LLM middleware.
|
|
5
|
+
* Logs prompts, subject descriptions, reference image metadata, and responses.
|
|
6
|
+
*
|
|
7
|
+
* Enable via:
|
|
8
|
+
* - Environment variable: DEBUG_TTI_REQUESTS=true
|
|
9
|
+
* - Or programmatically: TTIDebugger.setEnabled(true)
|
|
10
|
+
*
|
|
11
|
+
* Configure log directory:
|
|
12
|
+
* - Environment variable: TTI_DEBUG_LOG_DIR=/path/to/logs
|
|
13
|
+
* - Or programmatically: TTIDebugger.setLogsDir('/path/to/logs')
|
|
14
|
+
* - Default: process.cwd()/logs/tti/requests/
|
|
15
|
+
*/
|
|
16
|
+
import { TTIRequest, TTIResponse } from '../../../types';
|
|
17
|
+
/**
|
|
18
|
+
* Debug information for a TTI request
|
|
19
|
+
*/
|
|
20
|
+
export interface TTIDebugInfo {
|
|
21
|
+
requestTimestamp: Date;
|
|
22
|
+
responseTimestamp?: Date;
|
|
23
|
+
provider: string;
|
|
24
|
+
model: string;
|
|
25
|
+
region?: string;
|
|
26
|
+
prompt: string;
|
|
27
|
+
subjectDescription?: string;
|
|
28
|
+
referenceImageCount: number;
|
|
29
|
+
referenceImageMimeTypes?: string[];
|
|
30
|
+
aspectRatio?: string;
|
|
31
|
+
providerOptions?: Record<string, unknown>;
|
|
32
|
+
useCase?: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
bookId?: string;
|
|
35
|
+
characterId?: string;
|
|
36
|
+
sectionId?: string;
|
|
37
|
+
response?: {
|
|
38
|
+
imageCount: number;
|
|
39
|
+
imageMimeTypes: string[];
|
|
40
|
+
duration: number;
|
|
41
|
+
};
|
|
42
|
+
rawRequest?: TTIRequest;
|
|
43
|
+
rawResponse?: TTIResponse;
|
|
44
|
+
error?: {
|
|
45
|
+
message: string;
|
|
46
|
+
code?: string;
|
|
47
|
+
details?: unknown;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Configuration options for TTIDebugger
|
|
52
|
+
*/
|
|
53
|
+
export interface TTIDebuggerConfig {
|
|
54
|
+
/** Enable/disable logging (default: from DEBUG_TTI_REQUESTS env) */
|
|
55
|
+
enabled?: boolean;
|
|
56
|
+
/** Directory for log files (default: process.cwd()/logs/tti/requests) */
|
|
57
|
+
logsDir?: string;
|
|
58
|
+
/** Include raw base64 image data in logs (default: false - too large) */
|
|
59
|
+
includeBase64?: boolean;
|
|
60
|
+
/** Log to console as well (default: false) */
|
|
61
|
+
consoleLog?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Static debugger class for TTI request/response logging
|
|
65
|
+
*/
|
|
66
|
+
export declare class TTIDebugger {
|
|
67
|
+
private static _enabled;
|
|
68
|
+
private static _logsDir;
|
|
69
|
+
private static _includeBase64;
|
|
70
|
+
private static _consoleLog;
|
|
71
|
+
/**
|
|
72
|
+
* Check if debugging is enabled
|
|
73
|
+
*/
|
|
74
|
+
static get isEnabled(): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Enable or disable debugging
|
|
77
|
+
*/
|
|
78
|
+
static setEnabled(enabled: boolean): void;
|
|
79
|
+
/**
|
|
80
|
+
* Get the current logs directory
|
|
81
|
+
*/
|
|
82
|
+
static getLogsDir(): string;
|
|
83
|
+
/**
|
|
84
|
+
* Set the logs directory
|
|
85
|
+
*/
|
|
86
|
+
static setLogsDir(dir: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Configure the debugger
|
|
89
|
+
*/
|
|
90
|
+
static configure(config: TTIDebuggerConfig): void;
|
|
91
|
+
/**
|
|
92
|
+
* Log a TTI request (call before generation)
|
|
93
|
+
*/
|
|
94
|
+
static logRequest(debugInfo: TTIDebugInfo): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Log a TTI response (call after generation)
|
|
97
|
+
*/
|
|
98
|
+
static logResponse(debugInfo: TTIDebugInfo): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Log an error
|
|
101
|
+
*/
|
|
102
|
+
static logError(debugInfo: TTIDebugInfo): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Save debug info to a markdown file
|
|
105
|
+
*/
|
|
106
|
+
static saveToMarkdown(debugInfo: TTIDebugInfo): Promise<string>;
|
|
107
|
+
/**
|
|
108
|
+
* Generate a filename for the log file
|
|
109
|
+
*/
|
|
110
|
+
private static generateFilename;
|
|
111
|
+
/**
|
|
112
|
+
* Format debug info as markdown
|
|
113
|
+
*/
|
|
114
|
+
private static formatMarkdown;
|
|
115
|
+
/**
|
|
116
|
+
* Sanitize request by removing/truncating base64 data
|
|
117
|
+
*/
|
|
118
|
+
private static sanitizeRequest;
|
|
119
|
+
/**
|
|
120
|
+
* Sanitize response by removing/truncating base64 data
|
|
121
|
+
*/
|
|
122
|
+
private static sanitizeResponse;
|
|
123
|
+
/**
|
|
124
|
+
* Ensure the logs directory exists
|
|
125
|
+
*/
|
|
126
|
+
private static ensureLogsDirectory;
|
|
127
|
+
/**
|
|
128
|
+
* Create debug info from a request (helper for providers)
|
|
129
|
+
*/
|
|
130
|
+
static createDebugInfo(request: TTIRequest, provider: string, model: string, options?: {
|
|
131
|
+
region?: string;
|
|
132
|
+
useCase?: string;
|
|
133
|
+
sessionId?: string;
|
|
134
|
+
bookId?: string;
|
|
135
|
+
characterId?: string;
|
|
136
|
+
sectionId?: string;
|
|
137
|
+
}): TTIDebugInfo;
|
|
138
|
+
/**
|
|
139
|
+
* Update debug info with response data (helper for providers)
|
|
140
|
+
*/
|
|
141
|
+
static updateWithResponse(debugInfo: TTIDebugInfo, response: TTIResponse): TTIDebugInfo;
|
|
142
|
+
/**
|
|
143
|
+
* Update debug info with error data (helper for providers)
|
|
144
|
+
*/
|
|
145
|
+
static updateWithError(debugInfo: TTIDebugInfo, error: Error & {
|
|
146
|
+
code?: string;
|
|
147
|
+
cause?: unknown;
|
|
148
|
+
}): TTIDebugInfo;
|
|
149
|
+
}
|
|
150
|
+
export default TTIDebugger;
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TTI Debugger Utility
|
|
4
|
+
*
|
|
5
|
+
* Provides markdown-based logging for TTI requests, similar to LLM middleware.
|
|
6
|
+
* Logs prompts, subject descriptions, reference image metadata, and responses.
|
|
7
|
+
*
|
|
8
|
+
* Enable via:
|
|
9
|
+
* - Environment variable: DEBUG_TTI_REQUESTS=true
|
|
10
|
+
* - Or programmatically: TTIDebugger.setEnabled(true)
|
|
11
|
+
*
|
|
12
|
+
* Configure log directory:
|
|
13
|
+
* - Environment variable: TTI_DEBUG_LOG_DIR=/path/to/logs
|
|
14
|
+
* - Or programmatically: TTIDebugger.setLogsDir('/path/to/logs')
|
|
15
|
+
* - Default: process.cwd()/logs/tti/requests/
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.TTIDebugger = void 0;
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
// ============================================================
|
|
55
|
+
// DEBUGGER CLASS
|
|
56
|
+
// ============================================================
|
|
57
|
+
/**
|
|
58
|
+
* Static debugger class for TTI request/response logging
|
|
59
|
+
*/
|
|
60
|
+
class TTIDebugger {
|
|
61
|
+
// ============================================================
|
|
62
|
+
// CONFIGURATION
|
|
63
|
+
// ============================================================
|
|
64
|
+
/**
|
|
65
|
+
* Check if debugging is enabled
|
|
66
|
+
*/
|
|
67
|
+
static get isEnabled() {
|
|
68
|
+
return this._enabled;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Enable or disable debugging
|
|
72
|
+
*/
|
|
73
|
+
static setEnabled(enabled) {
|
|
74
|
+
this._enabled = enabled;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get the current logs directory
|
|
78
|
+
*/
|
|
79
|
+
static getLogsDir() {
|
|
80
|
+
return this._logsDir;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Set the logs directory
|
|
84
|
+
*/
|
|
85
|
+
static setLogsDir(dir) {
|
|
86
|
+
this._logsDir = dir;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Configure the debugger
|
|
90
|
+
*/
|
|
91
|
+
static configure(config) {
|
|
92
|
+
if (config.enabled !== undefined) {
|
|
93
|
+
this._enabled = config.enabled;
|
|
94
|
+
}
|
|
95
|
+
if (config.logsDir !== undefined) {
|
|
96
|
+
this._logsDir = config.logsDir;
|
|
97
|
+
}
|
|
98
|
+
if (config.includeBase64 !== undefined) {
|
|
99
|
+
this._includeBase64 = config.includeBase64;
|
|
100
|
+
}
|
|
101
|
+
if (config.consoleLog !== undefined) {
|
|
102
|
+
this._consoleLog = config.consoleLog;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ============================================================
|
|
106
|
+
// LOGGING METHODS
|
|
107
|
+
// ============================================================
|
|
108
|
+
/**
|
|
109
|
+
* Log a TTI request (call before generation)
|
|
110
|
+
*/
|
|
111
|
+
static async logRequest(debugInfo) {
|
|
112
|
+
if (!this._enabled)
|
|
113
|
+
return;
|
|
114
|
+
if (this._consoleLog) {
|
|
115
|
+
console.log('[TTI Debug] Request:', {
|
|
116
|
+
provider: debugInfo.provider,
|
|
117
|
+
model: debugInfo.model,
|
|
118
|
+
useCase: debugInfo.useCase,
|
|
119
|
+
prompt: debugInfo.prompt.substring(0, 100) + '...',
|
|
120
|
+
subjectDescription: debugInfo.subjectDescription,
|
|
121
|
+
referenceImageCount: debugInfo.referenceImageCount,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Log a TTI response (call after generation)
|
|
127
|
+
*/
|
|
128
|
+
static async logResponse(debugInfo) {
|
|
129
|
+
if (!this._enabled)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
await this.saveToMarkdown(debugInfo);
|
|
133
|
+
if (this._consoleLog) {
|
|
134
|
+
console.log('[TTI Debug] Response saved:', {
|
|
135
|
+
provider: debugInfo.provider,
|
|
136
|
+
model: debugInfo.model,
|
|
137
|
+
duration: debugInfo.response?.duration,
|
|
138
|
+
imageCount: debugInfo.response?.imageCount,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('[TTI Debug] Failed to save log:', error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Log an error
|
|
148
|
+
*/
|
|
149
|
+
static async logError(debugInfo) {
|
|
150
|
+
if (!this._enabled)
|
|
151
|
+
return;
|
|
152
|
+
try {
|
|
153
|
+
await this.saveToMarkdown(debugInfo);
|
|
154
|
+
if (this._consoleLog) {
|
|
155
|
+
console.error('[TTI Debug] Error:', debugInfo.error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error('[TTI Debug] Failed to save error log:', error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// ============================================================
|
|
163
|
+
// MARKDOWN GENERATION
|
|
164
|
+
// ============================================================
|
|
165
|
+
/**
|
|
166
|
+
* Save debug info to a markdown file
|
|
167
|
+
*/
|
|
168
|
+
static async saveToMarkdown(debugInfo) {
|
|
169
|
+
this.ensureLogsDirectory();
|
|
170
|
+
const filename = this.generateFilename(debugInfo);
|
|
171
|
+
const filepath = path.join(this._logsDir, filename);
|
|
172
|
+
const content = this.formatMarkdown(debugInfo);
|
|
173
|
+
await fs.promises.writeFile(filepath, content, 'utf-8');
|
|
174
|
+
return filepath;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Generate a filename for the log file
|
|
178
|
+
*/
|
|
179
|
+
static generateFilename(debugInfo) {
|
|
180
|
+
const timestamp = debugInfo.requestTimestamp
|
|
181
|
+
.toISOString()
|
|
182
|
+
.replace(/[:.]/g, '-');
|
|
183
|
+
const useCasePart = debugInfo.useCase
|
|
184
|
+
? `_${debugInfo.useCase.toLowerCase().replace(/[^a-z0-9]/g, '-')}`
|
|
185
|
+
: '';
|
|
186
|
+
const identifierPart = debugInfo.characterId
|
|
187
|
+
? `_char-${debugInfo.characterId.substring(0, 8)}`
|
|
188
|
+
: debugInfo.sectionId
|
|
189
|
+
? `_sec-${debugInfo.sectionId.substring(0, 8)}`
|
|
190
|
+
: '';
|
|
191
|
+
return `${timestamp}${useCasePart}${identifierPart}.md`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Format debug info as markdown
|
|
195
|
+
*/
|
|
196
|
+
static formatMarkdown(debugInfo) {
|
|
197
|
+
const sections = [];
|
|
198
|
+
// Header
|
|
199
|
+
sections.push('# TTI Request & Response Log\n');
|
|
200
|
+
// Provider Information
|
|
201
|
+
sections.push('## Provider Information\n');
|
|
202
|
+
sections.push(`- **Provider**: ${debugInfo.provider}`);
|
|
203
|
+
sections.push(`- **Model**: ${debugInfo.model}`);
|
|
204
|
+
if (debugInfo.region) {
|
|
205
|
+
sections.push(`- **Region**: ${debugInfo.region}`);
|
|
206
|
+
}
|
|
207
|
+
sections.push('');
|
|
208
|
+
// Request Information
|
|
209
|
+
sections.push('## Request Information\n');
|
|
210
|
+
sections.push(`- **Request Timestamp**: ${debugInfo.requestTimestamp.toISOString()}`);
|
|
211
|
+
if (debugInfo.responseTimestamp) {
|
|
212
|
+
sections.push(`- **Response Timestamp**: ${debugInfo.responseTimestamp.toISOString()}`);
|
|
213
|
+
}
|
|
214
|
+
if (debugInfo.useCase) {
|
|
215
|
+
sections.push(`- **Use Case**: ${debugInfo.useCase}`);
|
|
216
|
+
}
|
|
217
|
+
if (debugInfo.sessionId) {
|
|
218
|
+
sections.push(`- **Session ID**: ${debugInfo.sessionId}`);
|
|
219
|
+
}
|
|
220
|
+
if (debugInfo.bookId) {
|
|
221
|
+
sections.push(`- **Book ID**: ${debugInfo.bookId}`);
|
|
222
|
+
}
|
|
223
|
+
if (debugInfo.characterId) {
|
|
224
|
+
sections.push(`- **Character ID**: ${debugInfo.characterId}`);
|
|
225
|
+
}
|
|
226
|
+
if (debugInfo.sectionId) {
|
|
227
|
+
sections.push(`- **Section ID**: ${debugInfo.sectionId}`);
|
|
228
|
+
}
|
|
229
|
+
if (debugInfo.aspectRatio) {
|
|
230
|
+
sections.push(`- **Aspect Ratio**: ${debugInfo.aspectRatio}`);
|
|
231
|
+
}
|
|
232
|
+
sections.push('');
|
|
233
|
+
// Reference Images
|
|
234
|
+
sections.push('## Reference Images\n');
|
|
235
|
+
sections.push(`- **Count**: ${debugInfo.referenceImageCount}`);
|
|
236
|
+
if (debugInfo.referenceImageMimeTypes &&
|
|
237
|
+
debugInfo.referenceImageMimeTypes.length > 0) {
|
|
238
|
+
sections.push(`- **MIME Types**: ${debugInfo.referenceImageMimeTypes.join(', ')}`);
|
|
239
|
+
}
|
|
240
|
+
sections.push('');
|
|
241
|
+
// Subject Description (important for character consistency debugging)
|
|
242
|
+
sections.push('## Subject Description\n');
|
|
243
|
+
if (debugInfo.subjectDescription) {
|
|
244
|
+
sections.push('```');
|
|
245
|
+
sections.push(debugInfo.subjectDescription);
|
|
246
|
+
sections.push('```');
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
sections.push('*No subject description provided (raw multimodal mode)*');
|
|
250
|
+
}
|
|
251
|
+
sections.push('');
|
|
252
|
+
// Prompt (the most important part for debugging)
|
|
253
|
+
sections.push('## Prompt\n');
|
|
254
|
+
sections.push('```');
|
|
255
|
+
sections.push(debugInfo.prompt);
|
|
256
|
+
sections.push('```');
|
|
257
|
+
sections.push('');
|
|
258
|
+
// Provider Options
|
|
259
|
+
if (debugInfo.providerOptions &&
|
|
260
|
+
Object.keys(debugInfo.providerOptions).length > 0) {
|
|
261
|
+
sections.push('## Provider Options\n');
|
|
262
|
+
sections.push('```json');
|
|
263
|
+
sections.push(JSON.stringify(debugInfo.providerOptions, null, 2));
|
|
264
|
+
sections.push('```');
|
|
265
|
+
sections.push('');
|
|
266
|
+
}
|
|
267
|
+
// Response
|
|
268
|
+
if (debugInfo.response) {
|
|
269
|
+
sections.push('## Response\n');
|
|
270
|
+
sections.push(`- **Image Count**: ${debugInfo.response.imageCount}`);
|
|
271
|
+
sections.push(`- **MIME Types**: ${debugInfo.response.imageMimeTypes.join(', ')}`);
|
|
272
|
+
sections.push(`- **Duration**: ${debugInfo.response.duration}ms`);
|
|
273
|
+
sections.push('');
|
|
274
|
+
}
|
|
275
|
+
// Raw Request Data (without base64)
|
|
276
|
+
if (debugInfo.rawRequest) {
|
|
277
|
+
sections.push('## Raw Request Data\n');
|
|
278
|
+
sections.push('```json');
|
|
279
|
+
const sanitizedRequest = this.sanitizeRequest(debugInfo.rawRequest);
|
|
280
|
+
sections.push(JSON.stringify(sanitizedRequest, null, 2));
|
|
281
|
+
sections.push('```');
|
|
282
|
+
sections.push('');
|
|
283
|
+
}
|
|
284
|
+
// Raw Response Data (without base64)
|
|
285
|
+
if (debugInfo.rawResponse) {
|
|
286
|
+
sections.push('## Raw Response Data\n');
|
|
287
|
+
sections.push('```json');
|
|
288
|
+
const sanitizedResponse = this.sanitizeResponse(debugInfo.rawResponse);
|
|
289
|
+
sections.push(JSON.stringify(sanitizedResponse, null, 2));
|
|
290
|
+
sections.push('```');
|
|
291
|
+
sections.push('');
|
|
292
|
+
}
|
|
293
|
+
// Error
|
|
294
|
+
if (debugInfo.error) {
|
|
295
|
+
sections.push('## Error\n');
|
|
296
|
+
sections.push(`- **Message**: ${debugInfo.error.message}`);
|
|
297
|
+
if (debugInfo.error.code) {
|
|
298
|
+
sections.push(`- **Code**: ${debugInfo.error.code}`);
|
|
299
|
+
}
|
|
300
|
+
if (debugInfo.error.details) {
|
|
301
|
+
sections.push('- **Details**:');
|
|
302
|
+
sections.push('```json');
|
|
303
|
+
sections.push(JSON.stringify(debugInfo.error.details, null, 2));
|
|
304
|
+
sections.push('```');
|
|
305
|
+
}
|
|
306
|
+
sections.push('');
|
|
307
|
+
}
|
|
308
|
+
// Footer
|
|
309
|
+
sections.push('---');
|
|
310
|
+
sections.push(`*Generated on ${new Date().toISOString()}*`);
|
|
311
|
+
return sections.join('\n');
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Sanitize request by removing/truncating base64 data
|
|
315
|
+
*/
|
|
316
|
+
static sanitizeRequest(request) {
|
|
317
|
+
const sanitized = { ...request };
|
|
318
|
+
if (request.referenceImages && !this._includeBase64) {
|
|
319
|
+
sanitized.referenceImages = request.referenceImages.map((ref, index) => ({
|
|
320
|
+
index,
|
|
321
|
+
mimeType: ref.mimeType || 'unknown',
|
|
322
|
+
base64Length: ref.base64?.length || 0,
|
|
323
|
+
base64Preview: ref.base64 ? `${ref.base64.substring(0, 50)}...` : null,
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
return sanitized;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Sanitize response by removing/truncating base64 data
|
|
330
|
+
*/
|
|
331
|
+
static sanitizeResponse(response) {
|
|
332
|
+
const sanitized = { ...response };
|
|
333
|
+
if (response.images && !this._includeBase64) {
|
|
334
|
+
sanitized.images = response.images.map((img, index) => ({
|
|
335
|
+
index,
|
|
336
|
+
contentType: img.contentType || 'unknown',
|
|
337
|
+
hasUrl: !!img.url,
|
|
338
|
+
base64Length: img.base64?.length || 0,
|
|
339
|
+
base64Preview: img.base64 ? `${img.base64.substring(0, 50)}...` : null,
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
return sanitized;
|
|
343
|
+
}
|
|
344
|
+
// ============================================================
|
|
345
|
+
// HELPER METHODS
|
|
346
|
+
// ============================================================
|
|
347
|
+
/**
|
|
348
|
+
* Ensure the logs directory exists
|
|
349
|
+
*/
|
|
350
|
+
static ensureLogsDirectory() {
|
|
351
|
+
if (!fs.existsSync(this._logsDir)) {
|
|
352
|
+
fs.mkdirSync(this._logsDir, { recursive: true });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create debug info from a request (helper for providers)
|
|
357
|
+
*/
|
|
358
|
+
static createDebugInfo(request, provider, model, options) {
|
|
359
|
+
return {
|
|
360
|
+
requestTimestamp: new Date(),
|
|
361
|
+
provider,
|
|
362
|
+
model,
|
|
363
|
+
region: options?.region,
|
|
364
|
+
prompt: request.prompt,
|
|
365
|
+
subjectDescription: request.subjectDescription,
|
|
366
|
+
referenceImageCount: request.referenceImages?.length || 0,
|
|
367
|
+
referenceImageMimeTypes: request.referenceImages?.map((ref) => ref.mimeType || 'unknown'),
|
|
368
|
+
aspectRatio: request.aspectRatio,
|
|
369
|
+
providerOptions: request.providerOptions,
|
|
370
|
+
useCase: options?.useCase,
|
|
371
|
+
sessionId: options?.sessionId,
|
|
372
|
+
bookId: options?.bookId,
|
|
373
|
+
characterId: options?.characterId,
|
|
374
|
+
sectionId: options?.sectionId,
|
|
375
|
+
rawRequest: request,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Update debug info with response data (helper for providers)
|
|
380
|
+
*/
|
|
381
|
+
static updateWithResponse(debugInfo, response) {
|
|
382
|
+
return {
|
|
383
|
+
...debugInfo,
|
|
384
|
+
responseTimestamp: new Date(),
|
|
385
|
+
response: {
|
|
386
|
+
imageCount: response.images.length,
|
|
387
|
+
imageMimeTypes: response.images.map((img) => img.contentType || 'unknown'),
|
|
388
|
+
duration: response.metadata.duration,
|
|
389
|
+
},
|
|
390
|
+
rawResponse: response,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Update debug info with error data (helper for providers)
|
|
395
|
+
*/
|
|
396
|
+
static updateWithError(debugInfo, error) {
|
|
397
|
+
return {
|
|
398
|
+
...debugInfo,
|
|
399
|
+
responseTimestamp: new Date(),
|
|
400
|
+
error: {
|
|
401
|
+
message: error.message,
|
|
402
|
+
code: error.code,
|
|
403
|
+
details: error.cause,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
exports.TTIDebugger = TTIDebugger;
|
|
409
|
+
TTIDebugger._enabled = process.env.DEBUG_TTI_REQUESTS === 'true' ||
|
|
410
|
+
process.env.NODE_ENV === 'development';
|
|
411
|
+
TTIDebugger._logsDir = process.env.TTI_DEBUG_LOG_DIR ||
|
|
412
|
+
path.join(process.cwd(), 'logs', 'tti', 'requests');
|
|
413
|
+
TTIDebugger._includeBase64 = false;
|
|
414
|
+
TTIDebugger._consoleLog = false;
|
|
415
|
+
// ============================================================
|
|
416
|
+
// EXPORTS
|
|
417
|
+
// ============================================================
|
|
418
|
+
exports.default = TTIDebugger;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './debug-tti.utils';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./debug-tti.utils"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loonylabs/tti-middleware",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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",
|