@everworker/oneringai 0.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.
@@ -0,0 +1,3583 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var jose = require('jose');
5
+ var fs2 = require('fs');
6
+ var eventemitter3 = require('eventemitter3');
7
+ var path = require('path');
8
+ var OpenAI = require('openai');
9
+ var genai = require('@google/genai');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
32
+ var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
33
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
34
+ var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
35
+
36
+ // src/connectors/oauth/utils/encryption.ts
37
+ var ALGORITHM = "aes-256-gcm";
38
+ var IV_LENGTH = 16;
39
+ var SALT_LENGTH = 64;
40
+ var TAG_LENGTH = 16;
41
+ var KEY_LENGTH = 32;
42
+ function encrypt(text, password) {
43
+ const salt = crypto__namespace.randomBytes(SALT_LENGTH);
44
+ const key = crypto__namespace.pbkdf2Sync(password, salt, 1e5, KEY_LENGTH, "sha512");
45
+ const iv = crypto__namespace.randomBytes(IV_LENGTH);
46
+ const cipher = crypto__namespace.createCipheriv(ALGORITHM, key, iv);
47
+ let encrypted = cipher.update(text, "utf8", "hex");
48
+ encrypted += cipher.final("hex");
49
+ const tag = cipher.getAuthTag();
50
+ const result = Buffer.concat([salt, iv, tag, Buffer.from(encrypted, "hex")]);
51
+ return result.toString("base64");
52
+ }
53
+ function decrypt(encryptedData, password) {
54
+ const buffer = Buffer.from(encryptedData, "base64");
55
+ const salt = buffer.subarray(0, SALT_LENGTH);
56
+ const iv = buffer.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
57
+ const tag = buffer.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
58
+ const encrypted = buffer.subarray(SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
59
+ const key = crypto__namespace.pbkdf2Sync(password, salt, 1e5, KEY_LENGTH, "sha512");
60
+ const decipher = crypto__namespace.createDecipheriv(ALGORITHM, key, iv);
61
+ decipher.setAuthTag(tag);
62
+ let decrypted = decipher.update(encrypted);
63
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
64
+ return decrypted.toString("utf8");
65
+ }
66
+ function getEncryptionKey() {
67
+ if (process.env.OAUTH_ENCRYPTION_KEY) {
68
+ return process.env.OAUTH_ENCRYPTION_KEY;
69
+ }
70
+ if (!global.__oauthEncryptionKey) {
71
+ global.__oauthEncryptionKey = crypto__namespace.randomBytes(32).toString("hex");
72
+ console.warn(
73
+ "WARNING: Using auto-generated encryption key. Tokens will not persist across restarts. Set OAUTH_ENCRYPTION_KEY environment variable for production!"
74
+ );
75
+ }
76
+ return global.__oauthEncryptionKey;
77
+ }
78
+
79
+ // src/connectors/oauth/infrastructure/storage/MemoryStorage.ts
80
+ var MemoryStorage = class {
81
+ tokens = /* @__PURE__ */ new Map();
82
+ // Stores encrypted tokens
83
+ async storeToken(key, token) {
84
+ const encryptionKey = getEncryptionKey();
85
+ const plaintext = JSON.stringify(token);
86
+ const encrypted = encrypt(plaintext, encryptionKey);
87
+ this.tokens.set(key, encrypted);
88
+ }
89
+ async getToken(key) {
90
+ const encrypted = this.tokens.get(key);
91
+ if (!encrypted) {
92
+ return null;
93
+ }
94
+ try {
95
+ const encryptionKey = getEncryptionKey();
96
+ const decrypted = decrypt(encrypted, encryptionKey);
97
+ return JSON.parse(decrypted);
98
+ } catch (error) {
99
+ console.error("Failed to decrypt token from memory:", error);
100
+ this.tokens.delete(key);
101
+ return null;
102
+ }
103
+ }
104
+ async deleteToken(key) {
105
+ this.tokens.delete(key);
106
+ }
107
+ async hasToken(key) {
108
+ return this.tokens.has(key);
109
+ }
110
+ /**
111
+ * Clear all tokens (useful for testing)
112
+ */
113
+ clearAll() {
114
+ this.tokens.clear();
115
+ }
116
+ /**
117
+ * Get number of stored tokens
118
+ */
119
+ size() {
120
+ return this.tokens.size;
121
+ }
122
+ };
123
+
124
+ // src/connectors/oauth/domain/TokenStore.ts
125
+ var TokenStore = class {
126
+ storage;
127
+ baseStorageKey;
128
+ constructor(storageKey = "default", storage) {
129
+ this.baseStorageKey = storageKey;
130
+ this.storage = storage || new MemoryStorage();
131
+ }
132
+ /**
133
+ * Get user-scoped storage key
134
+ * For multi-user support, keys are scoped per user: "provider:userId"
135
+ * For single-user (backward compatible), userId is omitted or "default"
136
+ *
137
+ * @param userId - User identifier (optional, defaults to single-user mode)
138
+ * @returns Storage key scoped to user
139
+ */
140
+ getScopedKey(userId) {
141
+ if (!userId || userId === "default") {
142
+ return this.baseStorageKey;
143
+ }
144
+ return `${this.baseStorageKey}:${userId}`;
145
+ }
146
+ /**
147
+ * Store token (encrypted by storage layer)
148
+ * @param tokenResponse - Token response from OAuth provider
149
+ * @param userId - Optional user identifier for multi-user support
150
+ */
151
+ async storeToken(tokenResponse, userId) {
152
+ if (!tokenResponse.access_token) {
153
+ throw new Error("OAuth response missing required access_token field");
154
+ }
155
+ if (typeof tokenResponse.access_token !== "string") {
156
+ throw new Error("access_token must be a string");
157
+ }
158
+ if (tokenResponse.expires_in !== void 0 && tokenResponse.expires_in < 0) {
159
+ throw new Error("expires_in must be positive");
160
+ }
161
+ const token = {
162
+ access_token: tokenResponse.access_token,
163
+ refresh_token: tokenResponse.refresh_token,
164
+ expires_in: tokenResponse.expires_in || 3600,
165
+ token_type: tokenResponse.token_type || "Bearer",
166
+ scope: tokenResponse.scope,
167
+ obtained_at: Date.now()
168
+ };
169
+ const key = this.getScopedKey(userId);
170
+ await this.storage.storeToken(key, token);
171
+ }
172
+ /**
173
+ * Get access token
174
+ * @param userId - Optional user identifier for multi-user support
175
+ */
176
+ async getAccessToken(userId) {
177
+ const key = this.getScopedKey(userId);
178
+ const token = await this.storage.getToken(key);
179
+ if (!token) {
180
+ throw new Error(`No token stored for ${userId ? `user: ${userId}` : "default user"}`);
181
+ }
182
+ return token.access_token;
183
+ }
184
+ /**
185
+ * Get refresh token
186
+ * @param userId - Optional user identifier for multi-user support
187
+ */
188
+ async getRefreshToken(userId) {
189
+ const key = this.getScopedKey(userId);
190
+ const token = await this.storage.getToken(key);
191
+ if (!token?.refresh_token) {
192
+ throw new Error(`No refresh token available for ${userId ? `user: ${userId}` : "default user"}`);
193
+ }
194
+ return token.refresh_token;
195
+ }
196
+ /**
197
+ * Check if has refresh token
198
+ * @param userId - Optional user identifier for multi-user support
199
+ */
200
+ async hasRefreshToken(userId) {
201
+ const key = this.getScopedKey(userId);
202
+ const token = await this.storage.getToken(key);
203
+ return !!token?.refresh_token;
204
+ }
205
+ /**
206
+ * Check if token is valid (not expired)
207
+ *
208
+ * @param bufferSeconds - Refresh this many seconds before expiry (default: 300 = 5 min)
209
+ * @param userId - Optional user identifier for multi-user support
210
+ */
211
+ async isValid(bufferSeconds = 300, userId) {
212
+ const key = this.getScopedKey(userId);
213
+ const token = await this.storage.getToken(key);
214
+ if (!token) {
215
+ return false;
216
+ }
217
+ const expiresAt = token.obtained_at + token.expires_in * 1e3;
218
+ const bufferMs = bufferSeconds * 1e3;
219
+ return Date.now() < expiresAt - bufferMs;
220
+ }
221
+ /**
222
+ * Clear stored token
223
+ * @param userId - Optional user identifier for multi-user support
224
+ */
225
+ async clear(userId) {
226
+ const key = this.getScopedKey(userId);
227
+ await this.storage.deleteToken(key);
228
+ }
229
+ /**
230
+ * Get full token info
231
+ * @param userId - Optional user identifier for multi-user support
232
+ */
233
+ async getTokenInfo(userId) {
234
+ const key = this.getScopedKey(userId);
235
+ return this.storage.getToken(key);
236
+ }
237
+ };
238
+ function generatePKCE() {
239
+ const codeVerifier = base64URLEncode(crypto__namespace.randomBytes(32));
240
+ const hash = crypto__namespace.createHash("sha256").update(codeVerifier).digest();
241
+ const codeChallenge = base64URLEncode(hash);
242
+ return {
243
+ codeVerifier,
244
+ codeChallenge
245
+ };
246
+ }
247
+ function base64URLEncode(buffer) {
248
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
249
+ }
250
+ function generateState() {
251
+ return crypto__namespace.randomBytes(16).toString("hex");
252
+ }
253
+
254
+ // src/connectors/oauth/flows/AuthCodePKCE.ts
255
+ var AuthCodePKCEFlow = class {
256
+ constructor(config) {
257
+ this.config = config;
258
+ const storageKey = config.storageKey || `auth_code:${config.clientId}`;
259
+ this.tokenStore = new TokenStore(storageKey, config.storage);
260
+ }
261
+ tokenStore;
262
+ // Store PKCE data per user with timestamps for cleanup
263
+ codeVerifiers = /* @__PURE__ */ new Map();
264
+ states = /* @__PURE__ */ new Map();
265
+ // Store refresh locks per user to prevent concurrent refresh
266
+ refreshLocks = /* @__PURE__ */ new Map();
267
+ // PKCE data TTL: 15 minutes (auth flows should complete within this time)
268
+ PKCE_TTL = 15 * 60 * 1e3;
269
+ /**
270
+ * Generate authorization URL for user to visit
271
+ * Opens browser or redirects user to this URL
272
+ *
273
+ * @param userId - User identifier for multi-user support (optional)
274
+ */
275
+ async getAuthorizationUrl(userId) {
276
+ if (!this.config.authorizationUrl) {
277
+ throw new Error("authorizationUrl is required for authorization_code flow");
278
+ }
279
+ if (!this.config.redirectUri) {
280
+ throw new Error("redirectUri is required for authorization_code flow");
281
+ }
282
+ this.cleanupExpiredPKCE();
283
+ const userKey = userId || "default";
284
+ const { codeVerifier, codeChallenge } = generatePKCE();
285
+ this.codeVerifiers.set(userKey, { verifier: codeVerifier, timestamp: Date.now() });
286
+ const state = generateState();
287
+ this.states.set(userKey, { state, timestamp: Date.now() });
288
+ const params = new URLSearchParams({
289
+ response_type: "code",
290
+ client_id: this.config.clientId,
291
+ redirect_uri: this.config.redirectUri,
292
+ state
293
+ });
294
+ if (this.config.scope) {
295
+ params.append("scope", this.config.scope);
296
+ }
297
+ if (this.config.usePKCE !== false) {
298
+ params.append("code_challenge", codeChallenge);
299
+ params.append("code_challenge_method", "S256");
300
+ }
301
+ const stateWithUser = userId ? `${state}::${userId}` : state;
302
+ params.set("state", stateWithUser);
303
+ return `${this.config.authorizationUrl}?${params.toString()}`;
304
+ }
305
+ /**
306
+ * Exchange authorization code for access token
307
+ *
308
+ * @param code - Authorization code from callback
309
+ * @param state - State parameter from callback (for CSRF verification, may include userId)
310
+ * @param userId - User identifier (optional, can be extracted from state)
311
+ */
312
+ async exchangeCode(code, state, userId) {
313
+ let actualState = state;
314
+ let actualUserId = userId;
315
+ if (state.includes("::")) {
316
+ const parts = state.split("::");
317
+ actualState = parts[0];
318
+ actualUserId = parts[1];
319
+ }
320
+ const userKey = actualUserId || "default";
321
+ const stateData = this.states.get(userKey);
322
+ if (!stateData) {
323
+ throw new Error(`No PKCE state found for user ${actualUserId}. Authorization flow may have expired (15 min TTL).`);
324
+ }
325
+ const expectedState = stateData.state;
326
+ if (actualState !== expectedState) {
327
+ throw new Error(`State mismatch for user ${actualUserId} - possible CSRF attack. Expected: ${expectedState}, Got: ${actualState}`);
328
+ }
329
+ if (!this.config.redirectUri) {
330
+ throw new Error("redirectUri is required");
331
+ }
332
+ const params = new URLSearchParams({
333
+ grant_type: "authorization_code",
334
+ code,
335
+ redirect_uri: this.config.redirectUri,
336
+ client_id: this.config.clientId
337
+ });
338
+ if (this.config.clientSecret) {
339
+ params.append("client_secret", this.config.clientSecret);
340
+ }
341
+ const verifierData = this.codeVerifiers.get(userKey);
342
+ if (this.config.usePKCE !== false && verifierData) {
343
+ params.append("code_verifier", verifierData.verifier);
344
+ }
345
+ const response = await fetch(this.config.tokenUrl, {
346
+ method: "POST",
347
+ headers: {
348
+ "Content-Type": "application/x-www-form-urlencoded"
349
+ },
350
+ body: params
351
+ });
352
+ if (!response.ok) {
353
+ const error = await response.text();
354
+ throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${error}`);
355
+ }
356
+ const data = await response.json();
357
+ await this.tokenStore.storeToken(data, actualUserId);
358
+ this.codeVerifiers.delete(userKey);
359
+ this.states.delete(userKey);
360
+ }
361
+ /**
362
+ * Get valid token (auto-refreshes if needed)
363
+ * @param userId - User identifier for multi-user support
364
+ */
365
+ async getToken(userId) {
366
+ const key = userId || "default";
367
+ if (this.refreshLocks.has(key)) {
368
+ return this.refreshLocks.get(key);
369
+ }
370
+ if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId)) {
371
+ return this.tokenStore.getAccessToken(userId);
372
+ }
373
+ if (await this.tokenStore.hasRefreshToken(userId)) {
374
+ const refreshPromise = this.refreshToken(userId);
375
+ this.refreshLocks.set(key, refreshPromise);
376
+ try {
377
+ return await refreshPromise;
378
+ } finally {
379
+ this.refreshLocks.delete(key);
380
+ }
381
+ }
382
+ throw new Error(`No valid token available for ${userId ? `user: ${userId}` : "default user"}. User needs to authorize (call startAuthFlow).`);
383
+ }
384
+ /**
385
+ * Refresh access token using refresh token
386
+ * @param userId - User identifier for multi-user support
387
+ */
388
+ async refreshToken(userId) {
389
+ const refreshToken = await this.tokenStore.getRefreshToken(userId);
390
+ const params = new URLSearchParams({
391
+ grant_type: "refresh_token",
392
+ refresh_token: refreshToken,
393
+ client_id: this.config.clientId
394
+ });
395
+ if (this.config.clientSecret) {
396
+ params.append("client_secret", this.config.clientSecret);
397
+ }
398
+ const response = await fetch(this.config.tokenUrl, {
399
+ method: "POST",
400
+ headers: {
401
+ "Content-Type": "application/x-www-form-urlencoded"
402
+ },
403
+ body: params
404
+ });
405
+ if (!response.ok) {
406
+ const error = await response.text();
407
+ throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${error}`);
408
+ }
409
+ const data = await response.json();
410
+ await this.tokenStore.storeToken(data, userId);
411
+ return data.access_token;
412
+ }
413
+ /**
414
+ * Check if token is valid
415
+ * @param userId - User identifier for multi-user support
416
+ */
417
+ async isTokenValid(userId) {
418
+ return this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId);
419
+ }
420
+ /**
421
+ * Revoke token (if supported by provider)
422
+ * @param revocationUrl - Optional revocation endpoint
423
+ * @param userId - User identifier for multi-user support
424
+ */
425
+ async revokeToken(revocationUrl, userId) {
426
+ if (!revocationUrl) {
427
+ await this.tokenStore.clear(userId);
428
+ return;
429
+ }
430
+ try {
431
+ const token = await this.tokenStore.getAccessToken(userId);
432
+ await fetch(revocationUrl, {
433
+ method: "POST",
434
+ headers: {
435
+ "Content-Type": "application/x-www-form-urlencoded"
436
+ },
437
+ body: new URLSearchParams({
438
+ token,
439
+ client_id: this.config.clientId
440
+ })
441
+ });
442
+ } finally {
443
+ await this.tokenStore.clear(userId);
444
+ }
445
+ }
446
+ /**
447
+ * Clean up expired PKCE data to prevent memory leaks
448
+ * Removes verifiers and states older than PKCE_TTL (15 minutes)
449
+ */
450
+ cleanupExpiredPKCE() {
451
+ const now = Date.now();
452
+ for (const [key, data] of this.codeVerifiers) {
453
+ if (now - data.timestamp > this.PKCE_TTL) {
454
+ this.codeVerifiers.delete(key);
455
+ this.states.delete(key);
456
+ }
457
+ }
458
+ }
459
+ };
460
+
461
+ // src/connectors/oauth/flows/ClientCredentials.ts
462
+ var ClientCredentialsFlow = class {
463
+ constructor(config) {
464
+ this.config = config;
465
+ const storageKey = config.storageKey || `client_credentials:${config.clientId}`;
466
+ this.tokenStore = new TokenStore(storageKey, config.storage);
467
+ }
468
+ tokenStore;
469
+ /**
470
+ * Get token using client credentials
471
+ */
472
+ async getToken() {
473
+ if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry)) {
474
+ return this.tokenStore.getAccessToken();
475
+ }
476
+ return this.requestToken();
477
+ }
478
+ /**
479
+ * Request a new token from the authorization server
480
+ */
481
+ async requestToken() {
482
+ const auth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString(
483
+ "base64"
484
+ );
485
+ const params = new URLSearchParams({
486
+ grant_type: "client_credentials"
487
+ });
488
+ if (this.config.scope) {
489
+ params.append("scope", this.config.scope);
490
+ }
491
+ const response = await fetch(this.config.tokenUrl, {
492
+ method: "POST",
493
+ headers: {
494
+ Authorization: `Basic ${auth}`,
495
+ "Content-Type": "application/x-www-form-urlencoded"
496
+ },
497
+ body: params
498
+ });
499
+ if (!response.ok) {
500
+ const error = await response.text();
501
+ throw new Error(`Token request failed: ${response.status} ${response.statusText} - ${error}`);
502
+ }
503
+ const data = await response.json();
504
+ await this.tokenStore.storeToken(data);
505
+ return data.access_token;
506
+ }
507
+ /**
508
+ * Refresh token (client credentials don't use refresh tokens)
509
+ * Just requests a new token
510
+ */
511
+ async refreshToken() {
512
+ await this.tokenStore.clear();
513
+ return this.requestToken();
514
+ }
515
+ /**
516
+ * Check if token is valid
517
+ */
518
+ async isTokenValid() {
519
+ return this.tokenStore.isValid(this.config.refreshBeforeExpiry);
520
+ }
521
+ };
522
+ var JWTBearerFlow = class {
523
+ constructor(config) {
524
+ this.config = config;
525
+ const storageKey = config.storageKey || `jwt_bearer:${config.clientId}`;
526
+ this.tokenStore = new TokenStore(storageKey, config.storage);
527
+ if (config.privateKey) {
528
+ this.privateKey = config.privateKey;
529
+ } else if (config.privateKeyPath) {
530
+ try {
531
+ this.privateKey = fs2__namespace.readFileSync(config.privateKeyPath, "utf8");
532
+ } catch (error) {
533
+ throw new Error(`Failed to read private key from ${config.privateKeyPath}: ${error.message}`);
534
+ }
535
+ } else {
536
+ throw new Error("JWT Bearer flow requires privateKey or privateKeyPath");
537
+ }
538
+ }
539
+ tokenStore;
540
+ privateKey;
541
+ /**
542
+ * Generate signed JWT assertion
543
+ */
544
+ async generateJWT() {
545
+ const now = Math.floor(Date.now() / 1e3);
546
+ const alg = this.config.tokenSigningAlg || "RS256";
547
+ const key = await jose.importPKCS8(this.privateKey, alg);
548
+ const jwt = await new jose.SignJWT({
549
+ scope: this.config.scope || ""
550
+ }).setProtectedHeader({ alg }).setIssuer(this.config.clientId).setSubject(this.config.clientId).setAudience(this.config.audience || this.config.tokenUrl).setIssuedAt(now).setExpirationTime(now + 3600).sign(key);
551
+ return jwt;
552
+ }
553
+ /**
554
+ * Get token using JWT Bearer assertion
555
+ */
556
+ async getToken() {
557
+ if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry)) {
558
+ return this.tokenStore.getAccessToken();
559
+ }
560
+ return this.requestToken();
561
+ }
562
+ /**
563
+ * Request token using JWT assertion
564
+ */
565
+ async requestToken() {
566
+ const assertion = await this.generateJWT();
567
+ const params = new URLSearchParams({
568
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
569
+ assertion
570
+ });
571
+ const response = await fetch(this.config.tokenUrl, {
572
+ method: "POST",
573
+ headers: {
574
+ "Content-Type": "application/x-www-form-urlencoded"
575
+ },
576
+ body: params
577
+ });
578
+ if (!response.ok) {
579
+ const error = await response.text();
580
+ throw new Error(`JWT Bearer token request failed: ${response.status} ${response.statusText} - ${error}`);
581
+ }
582
+ const data = await response.json();
583
+ await this.tokenStore.storeToken(data);
584
+ return data.access_token;
585
+ }
586
+ /**
587
+ * Refresh token (generate new JWT and request new token)
588
+ */
589
+ async refreshToken() {
590
+ await this.tokenStore.clear();
591
+ return this.requestToken();
592
+ }
593
+ /**
594
+ * Check if token is valid
595
+ */
596
+ async isTokenValid() {
597
+ return this.tokenStore.isValid(this.config.refreshBeforeExpiry);
598
+ }
599
+ };
600
+
601
+ // src/connectors/oauth/flows/StaticToken.ts
602
+ var StaticTokenFlow = class {
603
+ token;
604
+ constructor(config) {
605
+ if (!config.staticToken) {
606
+ throw new Error("Static token flow requires staticToken in config");
607
+ }
608
+ this.token = config.staticToken;
609
+ }
610
+ /**
611
+ * Get token (always returns the static token)
612
+ */
613
+ async getToken() {
614
+ return this.token;
615
+ }
616
+ /**
617
+ * Refresh token (no-op for static tokens)
618
+ */
619
+ async refreshToken() {
620
+ return this.token;
621
+ }
622
+ /**
623
+ * Token is always valid for static tokens
624
+ */
625
+ async isTokenValid() {
626
+ return true;
627
+ }
628
+ /**
629
+ * Update the static token
630
+ */
631
+ updateToken(newToken) {
632
+ this.token = newToken;
633
+ }
634
+ };
635
+
636
+ // src/connectors/oauth/OAuthManager.ts
637
+ var OAuthManager = class {
638
+ flow;
639
+ constructor(config) {
640
+ this.validateConfig(config);
641
+ switch (config.flow) {
642
+ case "authorization_code":
643
+ this.flow = new AuthCodePKCEFlow(config);
644
+ break;
645
+ case "client_credentials":
646
+ this.flow = new ClientCredentialsFlow(config);
647
+ break;
648
+ case "jwt_bearer":
649
+ this.flow = new JWTBearerFlow(config);
650
+ break;
651
+ case "static_token":
652
+ this.flow = new StaticTokenFlow(config);
653
+ break;
654
+ default:
655
+ throw new Error(`Unknown OAuth flow: ${config.flow}`);
656
+ }
657
+ }
658
+ /**
659
+ * Get valid access token
660
+ * Automatically refreshes if expired
661
+ *
662
+ * @param userId - User identifier for multi-user support (optional)
663
+ */
664
+ async getToken(userId) {
665
+ return this.flow.getToken(userId);
666
+ }
667
+ /**
668
+ * Force refresh the token
669
+ *
670
+ * @param userId - User identifier for multi-user support (optional)
671
+ */
672
+ async refreshToken(userId) {
673
+ return this.flow.refreshToken(userId);
674
+ }
675
+ /**
676
+ * Check if current token is valid
677
+ *
678
+ * @param userId - User identifier for multi-user support (optional)
679
+ */
680
+ async isTokenValid(userId) {
681
+ return this.flow.isTokenValid(userId);
682
+ }
683
+ // ==================== Authorization Code Flow Methods ====================
684
+ /**
685
+ * Start authorization flow (Authorization Code only)
686
+ * Returns URL for user to visit
687
+ *
688
+ * @param userId - User identifier for multi-user support (optional)
689
+ * @returns Authorization URL for the user to visit
690
+ */
691
+ async startAuthFlow(userId) {
692
+ if (!(this.flow instanceof AuthCodePKCEFlow)) {
693
+ throw new Error("startAuthFlow() is only available for authorization_code flow");
694
+ }
695
+ return this.flow.getAuthorizationUrl(userId);
696
+ }
697
+ /**
698
+ * Handle OAuth callback (Authorization Code only)
699
+ * Call this with the callback URL after user authorizes
700
+ *
701
+ * @param callbackUrl - Full callback URL with code and state parameters
702
+ * @param userId - Optional user identifier (can be extracted from state if embedded)
703
+ */
704
+ async handleCallback(callbackUrl, userId) {
705
+ if (!(this.flow instanceof AuthCodePKCEFlow)) {
706
+ throw new Error("handleCallback() is only available for authorization_code flow");
707
+ }
708
+ const url = new URL(callbackUrl);
709
+ const code = url.searchParams.get("code");
710
+ const state = url.searchParams.get("state");
711
+ if (!code) {
712
+ throw new Error("Missing authorization code in callback URL");
713
+ }
714
+ if (!state) {
715
+ throw new Error("Missing state parameter in callback URL");
716
+ }
717
+ await this.flow.exchangeCode(code, state, userId);
718
+ }
719
+ /**
720
+ * Revoke token (if supported by provider)
721
+ *
722
+ * @param revocationUrl - Optional revocation endpoint URL
723
+ * @param userId - User identifier for multi-user support (optional)
724
+ */
725
+ async revokeToken(revocationUrl, userId) {
726
+ if (this.flow instanceof AuthCodePKCEFlow) {
727
+ await this.flow.revokeToken(revocationUrl, userId);
728
+ } else {
729
+ throw new Error("Token revocation not implemented for this flow");
730
+ }
731
+ }
732
+ // ==================== Validation ====================
733
+ validateConfig(config) {
734
+ if (!config.flow) {
735
+ throw new Error("OAuth flow is required (authorization_code, client_credentials, jwt_bearer, or static_token)");
736
+ }
737
+ if (config.flow !== "static_token") {
738
+ if (!config.tokenUrl) {
739
+ throw new Error("tokenUrl is required");
740
+ }
741
+ if (!config.clientId) {
742
+ throw new Error("clientId is required");
743
+ }
744
+ }
745
+ switch (config.flow) {
746
+ case "authorization_code":
747
+ if (!config.authorizationUrl) {
748
+ throw new Error("authorizationUrl is required for authorization_code flow");
749
+ }
750
+ if (!config.redirectUri) {
751
+ throw new Error("redirectUri is required for authorization_code flow");
752
+ }
753
+ break;
754
+ case "client_credentials":
755
+ if (!config.clientSecret) {
756
+ throw new Error("clientSecret is required for client_credentials flow");
757
+ }
758
+ break;
759
+ case "jwt_bearer":
760
+ if (!config.privateKey && !config.privateKeyPath) {
761
+ throw new Error(
762
+ "privateKey or privateKeyPath is required for jwt_bearer flow"
763
+ );
764
+ }
765
+ break;
766
+ case "static_token":
767
+ if (!config.staticToken) {
768
+ throw new Error("staticToken is required for static_token flow");
769
+ }
770
+ break;
771
+ }
772
+ if (config.storage && !process.env.OAUTH_ENCRYPTION_KEY) {
773
+ console.warn(
774
+ "WARNING: Using persistent storage without OAUTH_ENCRYPTION_KEY environment variable. Tokens will be encrypted with auto-generated key that changes on restart!"
775
+ );
776
+ }
777
+ }
778
+ };
779
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
780
+ failureThreshold: 5,
781
+ successThreshold: 2,
782
+ resetTimeoutMs: 3e4,
783
+ // 30 seconds
784
+ windowMs: 6e4,
785
+ // 1 minute
786
+ isRetryable: () => true
787
+ // All errors count by default
788
+ };
789
+ var CircuitOpenError = class extends Error {
790
+ constructor(breakerName, nextRetryTime, failureCount, lastError) {
791
+ const retryInSeconds = Math.ceil((nextRetryTime - Date.now()) / 1e3);
792
+ super(
793
+ `Circuit breaker '${breakerName}' is OPEN. Retry in ${retryInSeconds}s. (${failureCount} recent failures, last: ${lastError})`
794
+ );
795
+ this.breakerName = breakerName;
796
+ this.nextRetryTime = nextRetryTime;
797
+ this.failureCount = failureCount;
798
+ this.lastError = lastError;
799
+ this.name = "CircuitOpenError";
800
+ }
801
+ };
802
+ var CircuitBreaker = class extends eventemitter3.EventEmitter {
803
+ constructor(name, config = {}) {
804
+ super();
805
+ this.name = name;
806
+ this.config = { ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config };
807
+ this.lastStateChange = Date.now();
808
+ }
809
+ state = "closed";
810
+ config;
811
+ // Failure tracking
812
+ failures = [];
813
+ lastError = "";
814
+ // Success tracking
815
+ consecutiveSuccesses = 0;
816
+ // Timing
817
+ openedAt;
818
+ lastStateChange;
819
+ // Metrics
820
+ totalRequests = 0;
821
+ successCount = 0;
822
+ failureCount = 0;
823
+ rejectedCount = 0;
824
+ lastFailureTime;
825
+ lastSuccessTime;
826
+ /**
827
+ * Execute function with circuit breaker protection
828
+ */
829
+ async execute(fn) {
830
+ this.totalRequests++;
831
+ const now = Date.now();
832
+ switch (this.state) {
833
+ case "open":
834
+ if (this.openedAt && now - this.openedAt >= this.config.resetTimeoutMs) {
835
+ this.transitionTo("half-open");
836
+ } else {
837
+ this.rejectedCount++;
838
+ const nextRetry = (this.openedAt || now) + this.config.resetTimeoutMs;
839
+ throw new CircuitOpenError(this.name, nextRetry, this.failures.length, this.lastError);
840
+ }
841
+ break;
842
+ }
843
+ try {
844
+ const result = await fn();
845
+ this.recordSuccess();
846
+ return result;
847
+ } catch (error) {
848
+ this.recordFailure(error);
849
+ throw error;
850
+ }
851
+ }
852
+ /**
853
+ * Record successful execution
854
+ */
855
+ recordSuccess() {
856
+ this.successCount++;
857
+ this.lastSuccessTime = Date.now();
858
+ this.consecutiveSuccesses++;
859
+ if (this.state === "half-open") {
860
+ if (this.consecutiveSuccesses >= this.config.successThreshold) {
861
+ this.transitionTo("closed");
862
+ }
863
+ } else if (this.state === "closed") {
864
+ this.pruneOldFailures();
865
+ }
866
+ }
867
+ /**
868
+ * Record failed execution
869
+ */
870
+ recordFailure(error) {
871
+ if (this.config.isRetryable && !this.config.isRetryable(error)) {
872
+ return;
873
+ }
874
+ this.failureCount++;
875
+ this.lastFailureTime = Date.now();
876
+ this.lastError = error.message;
877
+ this.consecutiveSuccesses = 0;
878
+ this.failures.push({
879
+ timestamp: Date.now(),
880
+ error: error.message
881
+ });
882
+ this.pruneOldFailures();
883
+ if (this.state === "half-open") {
884
+ this.transitionTo("open");
885
+ } else if (this.state === "closed") {
886
+ if (this.failures.length >= this.config.failureThreshold) {
887
+ this.transitionTo("open");
888
+ }
889
+ }
890
+ }
891
+ /**
892
+ * Transition to new state
893
+ */
894
+ transitionTo(newState) {
895
+ this.state = newState;
896
+ this.lastStateChange = Date.now();
897
+ switch (newState) {
898
+ case "open":
899
+ this.openedAt = Date.now();
900
+ this.emit("opened", {
901
+ name: this.name,
902
+ failureCount: this.failures.length,
903
+ lastError: this.lastError,
904
+ nextRetryTime: this.openedAt + this.config.resetTimeoutMs
905
+ });
906
+ break;
907
+ case "half-open":
908
+ this.emit("half-open", {
909
+ name: this.name,
910
+ timestamp: Date.now()
911
+ });
912
+ break;
913
+ case "closed":
914
+ this.failures = [];
915
+ this.consecutiveSuccesses = 0;
916
+ this.openedAt = void 0;
917
+ this.emit("closed", {
918
+ name: this.name,
919
+ successCount: this.consecutiveSuccesses,
920
+ timestamp: Date.now()
921
+ });
922
+ break;
923
+ }
924
+ }
925
+ /**
926
+ * Remove failures outside the time window
927
+ */
928
+ pruneOldFailures() {
929
+ const now = Date.now();
930
+ const cutoff = now - this.config.windowMs;
931
+ this.failures = this.failures.filter((f) => f.timestamp > cutoff);
932
+ }
933
+ /**
934
+ * Get current state
935
+ */
936
+ getState() {
937
+ return this.state;
938
+ }
939
+ /**
940
+ * Get current metrics
941
+ */
942
+ getMetrics() {
943
+ this.pruneOldFailures();
944
+ const total = this.successCount + this.failureCount;
945
+ const failureRate = total > 0 ? this.failureCount / total : 0;
946
+ const successRate = total > 0 ? this.successCount / total : 0;
947
+ return {
948
+ name: this.name,
949
+ state: this.state,
950
+ totalRequests: this.totalRequests,
951
+ successCount: this.successCount,
952
+ failureCount: this.failureCount,
953
+ rejectedCount: this.rejectedCount,
954
+ recentFailures: this.failures.length,
955
+ consecutiveSuccesses: this.consecutiveSuccesses,
956
+ lastFailureTime: this.lastFailureTime,
957
+ lastSuccessTime: this.lastSuccessTime,
958
+ lastStateChange: this.lastStateChange,
959
+ nextRetryTime: this.openedAt ? this.openedAt + this.config.resetTimeoutMs : void 0,
960
+ failureRate,
961
+ successRate
962
+ };
963
+ }
964
+ /**
965
+ * Manually reset circuit breaker (force close)
966
+ */
967
+ reset() {
968
+ this.transitionTo("closed");
969
+ this.totalRequests = 0;
970
+ this.successCount = 0;
971
+ this.failureCount = 0;
972
+ this.rejectedCount = 0;
973
+ this.lastFailureTime = void 0;
974
+ this.lastSuccessTime = void 0;
975
+ }
976
+ /**
977
+ * Check if circuit is allowing requests
978
+ */
979
+ isOpen() {
980
+ if (this.state === "open" && this.openedAt) {
981
+ const now = Date.now();
982
+ if (now - this.openedAt >= this.config.resetTimeoutMs) {
983
+ this.transitionTo("half-open");
984
+ return false;
985
+ }
986
+ return true;
987
+ }
988
+ return false;
989
+ }
990
+ /**
991
+ * Get configuration
992
+ */
993
+ getConfig() {
994
+ return { ...this.config };
995
+ }
996
+ };
997
+
998
+ // src/infrastructure/resilience/BackoffStrategy.ts
999
+ var DEFAULT_BACKOFF_CONFIG = {
1000
+ strategy: "exponential",
1001
+ initialDelayMs: 1e3,
1002
+ // 1 second
1003
+ maxDelayMs: 3e4,
1004
+ // 30 seconds
1005
+ multiplier: 2,
1006
+ jitter: true,
1007
+ jitterFactor: 0.1
1008
+ };
1009
+ function calculateBackoff(attempt, config = DEFAULT_BACKOFF_CONFIG) {
1010
+ let delay;
1011
+ switch (config.strategy) {
1012
+ case "exponential":
1013
+ delay = config.initialDelayMs * Math.pow(config.multiplier || 2, attempt - 1);
1014
+ break;
1015
+ case "linear":
1016
+ delay = config.initialDelayMs + (config.incrementMs || 1e3) * (attempt - 1);
1017
+ break;
1018
+ case "constant":
1019
+ delay = config.initialDelayMs;
1020
+ break;
1021
+ default:
1022
+ delay = config.initialDelayMs;
1023
+ }
1024
+ delay = Math.min(delay, config.maxDelayMs);
1025
+ if (config.jitter) {
1026
+ delay = addJitter(delay, config.jitterFactor || 0.1);
1027
+ }
1028
+ return Math.floor(delay);
1029
+ }
1030
+ function addJitter(delay, factor = 0.1) {
1031
+ const jitterRange = delay * factor;
1032
+ const jitter = (Math.random() * 2 - 1) * jitterRange;
1033
+ return delay + jitter;
1034
+ }
1035
+ var LOG_LEVEL_VALUES = {
1036
+ trace: 10,
1037
+ debug: 20,
1038
+ info: 30,
1039
+ warn: 40,
1040
+ error: 50,
1041
+ silent: 100
1042
+ };
1043
+ function safeStringify(obj, indent) {
1044
+ const seen = /* @__PURE__ */ new WeakSet();
1045
+ const replacer = (_key, value) => {
1046
+ if (value === null || value === void 0) {
1047
+ return value;
1048
+ }
1049
+ if (typeof value !== "object") {
1050
+ if (typeof value === "function") {
1051
+ return "[Function]";
1052
+ }
1053
+ if (typeof value === "bigint") {
1054
+ return value.toString();
1055
+ }
1056
+ return value;
1057
+ }
1058
+ const objValue = value;
1059
+ const constructor = objValue.constructor?.name || "";
1060
+ if (constructor === "Timeout" || constructor === "TimersList" || constructor === "Socket" || constructor === "Server" || constructor === "IncomingMessage" || constructor === "ServerResponse" || constructor === "WriteStream" || constructor === "ReadStream" || constructor === "EventEmitter") {
1061
+ return `[${constructor}]`;
1062
+ }
1063
+ if (seen.has(objValue)) {
1064
+ return "[Circular]";
1065
+ }
1066
+ if (objValue instanceof Error) {
1067
+ return {
1068
+ name: objValue.name,
1069
+ message: objValue.message,
1070
+ stack: objValue.stack
1071
+ };
1072
+ }
1073
+ if (objValue instanceof Date) {
1074
+ return objValue.toISOString();
1075
+ }
1076
+ if (objValue instanceof Map) {
1077
+ return Object.fromEntries(objValue);
1078
+ }
1079
+ if (objValue instanceof Set) {
1080
+ return Array.from(objValue);
1081
+ }
1082
+ if (Buffer.isBuffer(objValue)) {
1083
+ return `[Buffer(${objValue.length})]`;
1084
+ }
1085
+ seen.add(objValue);
1086
+ return value;
1087
+ };
1088
+ try {
1089
+ return JSON.stringify(obj, replacer, indent);
1090
+ } catch {
1091
+ return "[Unserializable]";
1092
+ }
1093
+ }
1094
+ var FrameworkLogger = class _FrameworkLogger {
1095
+ config;
1096
+ context;
1097
+ levelValue;
1098
+ fileStream;
1099
+ constructor(config = {}) {
1100
+ this.config = {
1101
+ level: config.level || process.env.LOG_LEVEL || "info",
1102
+ pretty: config.pretty ?? (process.env.LOG_PRETTY === "true" || process.env.NODE_ENV === "development"),
1103
+ destination: config.destination || "console",
1104
+ context: config.context || {},
1105
+ filePath: config.filePath || process.env.LOG_FILE
1106
+ };
1107
+ this.context = this.config.context || {};
1108
+ this.levelValue = LOG_LEVEL_VALUES[this.config.level || "info"];
1109
+ if (this.config.filePath) {
1110
+ this.initFileStream(this.config.filePath);
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Initialize file stream for logging
1115
+ */
1116
+ initFileStream(filePath) {
1117
+ try {
1118
+ const dir = path__namespace.dirname(filePath);
1119
+ if (!fs2__namespace.existsSync(dir)) {
1120
+ fs2__namespace.mkdirSync(dir, { recursive: true });
1121
+ }
1122
+ this.fileStream = fs2__namespace.createWriteStream(filePath, {
1123
+ flags: "a",
1124
+ // append mode
1125
+ encoding: "utf8"
1126
+ });
1127
+ this.fileStream.on("error", (err) => {
1128
+ console.error(`[Logger] File stream error: ${err.message}`);
1129
+ this.fileStream = void 0;
1130
+ });
1131
+ } catch (err) {
1132
+ console.error(`[Logger] Failed to initialize log file: ${err instanceof Error ? err.message : err}`);
1133
+ }
1134
+ }
1135
+ /**
1136
+ * Create child logger with additional context
1137
+ */
1138
+ child(context) {
1139
+ return new _FrameworkLogger({
1140
+ ...this.config,
1141
+ context: { ...this.context, ...context }
1142
+ });
1143
+ }
1144
+ /**
1145
+ * Trace log
1146
+ */
1147
+ trace(obj, msg) {
1148
+ this.log("trace", obj, msg);
1149
+ }
1150
+ /**
1151
+ * Debug log
1152
+ */
1153
+ debug(obj, msg) {
1154
+ this.log("debug", obj, msg);
1155
+ }
1156
+ /**
1157
+ * Info log
1158
+ */
1159
+ info(obj, msg) {
1160
+ this.log("info", obj, msg);
1161
+ }
1162
+ /**
1163
+ * Warn log
1164
+ */
1165
+ warn(obj, msg) {
1166
+ this.log("warn", obj, msg);
1167
+ }
1168
+ /**
1169
+ * Error log
1170
+ */
1171
+ error(obj, msg) {
1172
+ this.log("error", obj, msg);
1173
+ }
1174
+ /**
1175
+ * Internal log method
1176
+ */
1177
+ log(level, obj, msg) {
1178
+ if (LOG_LEVEL_VALUES[level] < this.levelValue) {
1179
+ return;
1180
+ }
1181
+ let data;
1182
+ let message;
1183
+ if (typeof obj === "string") {
1184
+ message = obj;
1185
+ data = {};
1186
+ } else {
1187
+ message = msg || "";
1188
+ data = obj;
1189
+ }
1190
+ const entry = {
1191
+ level,
1192
+ time: Date.now(),
1193
+ ...this.context,
1194
+ ...data,
1195
+ msg: message
1196
+ };
1197
+ this.output(entry);
1198
+ }
1199
+ /**
1200
+ * Output log entry
1201
+ */
1202
+ output(entry) {
1203
+ if (this.config.pretty) {
1204
+ this.prettyPrint(entry);
1205
+ } else {
1206
+ this.jsonPrint(entry);
1207
+ }
1208
+ }
1209
+ /**
1210
+ * Pretty print for development
1211
+ */
1212
+ prettyPrint(entry) {
1213
+ const levelColors = {
1214
+ trace: "\x1B[90m",
1215
+ // Gray
1216
+ debug: "\x1B[36m",
1217
+ // Cyan
1218
+ info: "\x1B[32m",
1219
+ // Green
1220
+ warn: "\x1B[33m",
1221
+ // Yellow
1222
+ error: "\x1B[31m",
1223
+ // Red
1224
+ silent: ""
1225
+ };
1226
+ const reset = "\x1B[0m";
1227
+ const color = this.fileStream ? "" : levelColors[entry.level] || "";
1228
+ const time = new Date(entry.time).toISOString().substring(11, 23);
1229
+ const levelStr = entry.level.toUpperCase().padEnd(5);
1230
+ const contextParts = [];
1231
+ for (const [key, value] of Object.entries(entry)) {
1232
+ if (key !== "level" && key !== "time" && key !== "msg") {
1233
+ contextParts.push(`${key}=${safeStringify(value)}`);
1234
+ }
1235
+ }
1236
+ const context = contextParts.length > 0 ? ` ${contextParts.join(" ")}` : "";
1237
+ const output = `${color}[${time}] ${levelStr}${reset} ${entry.msg}${context}`;
1238
+ if (this.fileStream) {
1239
+ const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, "");
1240
+ this.fileStream.write(cleanOutput + "\n");
1241
+ return;
1242
+ }
1243
+ switch (entry.level) {
1244
+ case "error":
1245
+ case "warn":
1246
+ console.error(output);
1247
+ break;
1248
+ default:
1249
+ console.log(output);
1250
+ }
1251
+ }
1252
+ /**
1253
+ * JSON print for production
1254
+ */
1255
+ jsonPrint(entry) {
1256
+ const json = safeStringify(entry);
1257
+ if (this.fileStream) {
1258
+ this.fileStream.write(json + "\n");
1259
+ return;
1260
+ }
1261
+ switch (this.config.destination) {
1262
+ case "stderr":
1263
+ console.error(json);
1264
+ break;
1265
+ default:
1266
+ console.log(json);
1267
+ }
1268
+ }
1269
+ /**
1270
+ * Update configuration
1271
+ */
1272
+ updateConfig(config) {
1273
+ this.config = { ...this.config, ...config };
1274
+ if (config.level) {
1275
+ this.levelValue = LOG_LEVEL_VALUES[config.level];
1276
+ }
1277
+ if (config.context) {
1278
+ this.context = { ...this.context, ...config.context };
1279
+ }
1280
+ if (config.filePath !== void 0) {
1281
+ this.closeFileStream();
1282
+ if (config.filePath) {
1283
+ this.initFileStream(config.filePath);
1284
+ }
1285
+ }
1286
+ }
1287
+ /**
1288
+ * Close file stream
1289
+ */
1290
+ closeFileStream() {
1291
+ if (this.fileStream) {
1292
+ this.fileStream.end();
1293
+ this.fileStream = void 0;
1294
+ }
1295
+ }
1296
+ /**
1297
+ * Cleanup resources (call before process exit)
1298
+ */
1299
+ close() {
1300
+ this.closeFileStream();
1301
+ }
1302
+ /**
1303
+ * Get current log level
1304
+ */
1305
+ getLevel() {
1306
+ return this.config.level || "info";
1307
+ }
1308
+ /**
1309
+ * Check if level is enabled
1310
+ */
1311
+ isLevelEnabled(level) {
1312
+ return LOG_LEVEL_VALUES[level] >= this.levelValue;
1313
+ }
1314
+ };
1315
+ var logger = new FrameworkLogger({
1316
+ level: process.env.LOG_LEVEL || "info",
1317
+ pretty: process.env.LOG_PRETTY === "true" || process.env.NODE_ENV === "development",
1318
+ filePath: process.env.LOG_FILE
1319
+ });
1320
+ process.on("exit", () => {
1321
+ logger.close();
1322
+ });
1323
+ process.on("SIGINT", () => {
1324
+ logger.close();
1325
+ process.exit(0);
1326
+ });
1327
+ process.on("SIGTERM", () => {
1328
+ logger.close();
1329
+ process.exit(0);
1330
+ });
1331
+
1332
+ // src/infrastructure/observability/Metrics.ts
1333
+ var NoOpMetrics = class {
1334
+ increment() {
1335
+ }
1336
+ gauge() {
1337
+ }
1338
+ timing() {
1339
+ }
1340
+ histogram() {
1341
+ }
1342
+ };
1343
+ var ConsoleMetrics = class {
1344
+ prefix;
1345
+ constructor(prefix = "oneringai") {
1346
+ this.prefix = prefix;
1347
+ }
1348
+ increment(metric, value = 1, tags) {
1349
+ this.log("COUNTER", metric, value, tags);
1350
+ }
1351
+ gauge(metric, value, tags) {
1352
+ this.log("GAUGE", metric, value, tags);
1353
+ }
1354
+ timing(metric, duration, tags) {
1355
+ this.log("TIMING", metric, `${duration}ms`, tags);
1356
+ }
1357
+ histogram(metric, value, tags) {
1358
+ this.log("HISTOGRAM", metric, value, tags);
1359
+ }
1360
+ log(type, metric, value, tags) {
1361
+ const fullMetric = `${this.prefix}.${metric}`;
1362
+ const tagsStr = tags ? ` ${JSON.stringify(tags)}` : "";
1363
+ console.log(`[METRIC:${type}] ${fullMetric}=${value}${tagsStr}`);
1364
+ }
1365
+ };
1366
+ var InMemoryMetrics = class {
1367
+ counters = /* @__PURE__ */ new Map();
1368
+ gauges = /* @__PURE__ */ new Map();
1369
+ timings = /* @__PURE__ */ new Map();
1370
+ histograms = /* @__PURE__ */ new Map();
1371
+ increment(metric, value = 1, tags) {
1372
+ const key = this.makeKey(metric, tags);
1373
+ this.counters.set(key, (this.counters.get(key) || 0) + value);
1374
+ }
1375
+ gauge(metric, value, tags) {
1376
+ const key = this.makeKey(metric, tags);
1377
+ this.gauges.set(key, value);
1378
+ }
1379
+ timing(metric, duration, tags) {
1380
+ const key = this.makeKey(metric, tags);
1381
+ const timings = this.timings.get(key) || [];
1382
+ timings.push(duration);
1383
+ this.timings.set(key, timings);
1384
+ }
1385
+ histogram(metric, value, tags) {
1386
+ const key = this.makeKey(metric, tags);
1387
+ const values = this.histograms.get(key) || [];
1388
+ values.push(value);
1389
+ this.histograms.set(key, values);
1390
+ }
1391
+ makeKey(metric, tags) {
1392
+ if (!tags) return metric;
1393
+ const tagStr = Object.entries(tags).map(([k, v]) => `${k}:${v}`).sort().join(",");
1394
+ return `${metric}{${tagStr}}`;
1395
+ }
1396
+ /**
1397
+ * Get all metrics (for testing)
1398
+ */
1399
+ getMetrics() {
1400
+ return {
1401
+ counters: new Map(this.counters),
1402
+ gauges: new Map(this.gauges),
1403
+ timings: new Map(this.timings),
1404
+ histograms: new Map(this.histograms)
1405
+ };
1406
+ }
1407
+ /**
1408
+ * Clear all metrics
1409
+ */
1410
+ clear() {
1411
+ this.counters.clear();
1412
+ this.gauges.clear();
1413
+ this.timings.clear();
1414
+ this.histograms.clear();
1415
+ }
1416
+ /**
1417
+ * Get summary statistics for timings
1418
+ */
1419
+ getTimingStats(metric, tags) {
1420
+ const key = this.makeKey(metric, tags);
1421
+ const timings = this.timings.get(key);
1422
+ if (!timings || timings.length === 0) {
1423
+ return null;
1424
+ }
1425
+ const sorted = [...timings].sort((a, b) => a - b);
1426
+ const count = sorted.length;
1427
+ const sum = sorted.reduce((a, b) => a + b, 0);
1428
+ return {
1429
+ count,
1430
+ min: sorted[0] ?? 0,
1431
+ max: sorted[count - 1] ?? 0,
1432
+ mean: sum / count,
1433
+ p50: sorted[Math.floor(count * 0.5)] ?? 0,
1434
+ p95: sorted[Math.floor(count * 0.95)] ?? 0,
1435
+ p99: sorted[Math.floor(count * 0.99)] ?? 0
1436
+ };
1437
+ }
1438
+ };
1439
+ function createMetricsCollector(type, prefix) {
1440
+ const collectorType = process.env.METRICS_COLLECTOR || "noop";
1441
+ switch (collectorType) {
1442
+ case "console":
1443
+ return new ConsoleMetrics(prefix);
1444
+ case "inmemory":
1445
+ return new InMemoryMetrics();
1446
+ default:
1447
+ return new NoOpMetrics();
1448
+ }
1449
+ }
1450
+ var metrics = createMetricsCollector(
1451
+ void 0,
1452
+ process.env.METRICS_PREFIX || "oneringai"
1453
+ );
1454
+
1455
+ // src/core/Connector.ts
1456
+ var DEFAULT_CONNECTOR_TIMEOUT = 3e4;
1457
+ var DEFAULT_MAX_RETRIES = 3;
1458
+ var DEFAULT_RETRYABLE_STATUSES = [429, 500, 502, 503, 504];
1459
+ var DEFAULT_BASE_DELAY_MS = 1e3;
1460
+ var DEFAULT_MAX_DELAY_MS = 3e4;
1461
+ var Connector = class _Connector {
1462
+ // ============ Static Registry ============
1463
+ static registry = /* @__PURE__ */ new Map();
1464
+ static defaultStorage = new MemoryStorage();
1465
+ /**
1466
+ * Create and register a new connector
1467
+ * @param config - Must include `name` field
1468
+ */
1469
+ static create(config) {
1470
+ if (!config.name || config.name.trim().length === 0) {
1471
+ throw new Error("Connector name is required");
1472
+ }
1473
+ if (_Connector.registry.has(config.name)) {
1474
+ throw new Error(`Connector '${config.name}' already exists. Use Connector.get() or choose a different name.`);
1475
+ }
1476
+ const connector = new _Connector(config);
1477
+ _Connector.registry.set(config.name, connector);
1478
+ return connector;
1479
+ }
1480
+ /**
1481
+ * Get a connector by name
1482
+ */
1483
+ static get(name) {
1484
+ const connector = _Connector.registry.get(name);
1485
+ if (!connector) {
1486
+ const available = _Connector.list().join(", ") || "none";
1487
+ throw new Error(`Connector '${name}' not found. Available: ${available}`);
1488
+ }
1489
+ return connector;
1490
+ }
1491
+ /**
1492
+ * Check if a connector exists
1493
+ */
1494
+ static has(name) {
1495
+ return _Connector.registry.has(name);
1496
+ }
1497
+ /**
1498
+ * List all registered connector names
1499
+ */
1500
+ static list() {
1501
+ return Array.from(_Connector.registry.keys());
1502
+ }
1503
+ /**
1504
+ * Remove a connector
1505
+ */
1506
+ static remove(name) {
1507
+ const connector = _Connector.registry.get(name);
1508
+ if (connector) {
1509
+ connector.dispose();
1510
+ }
1511
+ return _Connector.registry.delete(name);
1512
+ }
1513
+ /**
1514
+ * Clear all connectors (useful for testing)
1515
+ */
1516
+ static clear() {
1517
+ for (const connector of _Connector.registry.values()) {
1518
+ connector.dispose();
1519
+ }
1520
+ _Connector.registry.clear();
1521
+ }
1522
+ /**
1523
+ * Set default token storage for OAuth connectors
1524
+ */
1525
+ static setDefaultStorage(storage) {
1526
+ _Connector.defaultStorage = storage;
1527
+ }
1528
+ /**
1529
+ * Get all registered connectors
1530
+ */
1531
+ static listAll() {
1532
+ return Array.from(_Connector.registry.values());
1533
+ }
1534
+ /**
1535
+ * Get number of registered connectors
1536
+ */
1537
+ static size() {
1538
+ return _Connector.registry.size;
1539
+ }
1540
+ /**
1541
+ * Get connector descriptions formatted for tool parameters
1542
+ * Useful for generating dynamic tool descriptions
1543
+ */
1544
+ static getDescriptionsForTools() {
1545
+ const connectors = _Connector.listAll();
1546
+ if (connectors.length === 0) {
1547
+ return "No connectors registered yet.";
1548
+ }
1549
+ return connectors.map((c) => ` - "${c.name}": ${c.displayName} - ${c.config.description || "No description"}`).join("\n");
1550
+ }
1551
+ /**
1552
+ * Get connector info (for tools and documentation)
1553
+ */
1554
+ static getInfo() {
1555
+ const info = {};
1556
+ for (const connector of _Connector.registry.values()) {
1557
+ info[connector.name] = {
1558
+ displayName: connector.displayName,
1559
+ description: connector.config.description || "",
1560
+ baseURL: connector.baseURL
1561
+ };
1562
+ }
1563
+ return info;
1564
+ }
1565
+ // ============ Instance ============
1566
+ name;
1567
+ vendor;
1568
+ config;
1569
+ oauthManager;
1570
+ circuitBreaker;
1571
+ disposed = false;
1572
+ // Metrics
1573
+ requestCount = 0;
1574
+ successCount = 0;
1575
+ failureCount = 0;
1576
+ totalLatencyMs = 0;
1577
+ constructor(config) {
1578
+ this.name = config.name;
1579
+ this.vendor = config.vendor;
1580
+ this.config = config;
1581
+ if (config.auth.type === "oauth") {
1582
+ this.initOAuthManager(config.auth);
1583
+ } else if (config.auth.type === "jwt") {
1584
+ this.initJWTManager(config.auth);
1585
+ }
1586
+ this.initCircuitBreaker();
1587
+ }
1588
+ /**
1589
+ * Initialize circuit breaker with config or defaults
1590
+ */
1591
+ initCircuitBreaker() {
1592
+ const cbConfig = this.config.circuitBreaker;
1593
+ const enabled = cbConfig?.enabled ?? true;
1594
+ if (enabled) {
1595
+ this.circuitBreaker = new CircuitBreaker(`connector:${this.name}`, {
1596
+ failureThreshold: cbConfig?.failureThreshold ?? 5,
1597
+ successThreshold: cbConfig?.successThreshold ?? 2,
1598
+ resetTimeoutMs: cbConfig?.resetTimeoutMs ?? 3e4,
1599
+ windowMs: 6e4,
1600
+ // 1 minute window
1601
+ isRetryable: (error) => {
1602
+ if (error.message.includes("HTTP 4") && !error.message.includes("HTTP 429")) {
1603
+ return false;
1604
+ }
1605
+ return true;
1606
+ }
1607
+ });
1608
+ this.circuitBreaker.on("opened", ({ name, failureCount, lastError }) => {
1609
+ logger.warn(`Circuit breaker opened for ${name}: ${failureCount} failures, last error: ${lastError}`);
1610
+ metrics.increment("connector.circuit_breaker.opened", 1, { connector: this.name });
1611
+ });
1612
+ this.circuitBreaker.on("closed", ({ name }) => {
1613
+ logger.info(`Circuit breaker closed for ${name}`);
1614
+ metrics.increment("connector.circuit_breaker.closed", 1, { connector: this.name });
1615
+ });
1616
+ }
1617
+ }
1618
+ /**
1619
+ * Human-readable display name
1620
+ */
1621
+ get displayName() {
1622
+ return this.config.displayName || this.name;
1623
+ }
1624
+ /**
1625
+ * API base URL for this connector
1626
+ */
1627
+ get baseURL() {
1628
+ return this.config.baseURL || "";
1629
+ }
1630
+ /**
1631
+ * Get the API key (for api_key auth type)
1632
+ */
1633
+ getApiKey() {
1634
+ if (this.config.auth.type !== "api_key") {
1635
+ throw new Error(`Connector '${this.name}' does not use API key auth. Type: ${this.config.auth.type}`);
1636
+ }
1637
+ return this.config.auth.apiKey;
1638
+ }
1639
+ /**
1640
+ * Get the current access token (for OAuth, JWT, or API key)
1641
+ * Handles automatic refresh if needed
1642
+ */
1643
+ async getToken(userId) {
1644
+ if (this.config.auth.type === "api_key") {
1645
+ return this.config.auth.apiKey;
1646
+ }
1647
+ if (!this.oauthManager) {
1648
+ throw new Error(`OAuth manager not initialized for connector '${this.name}'`);
1649
+ }
1650
+ return this.oauthManager.getToken(userId);
1651
+ }
1652
+ /**
1653
+ * Start OAuth authorization flow
1654
+ * Returns the URL to redirect the user to
1655
+ */
1656
+ async startAuth(userId) {
1657
+ if (!this.oauthManager) {
1658
+ throw new Error(`Connector '${this.name}' is not an OAuth connector`);
1659
+ }
1660
+ return this.oauthManager.startAuthFlow(userId);
1661
+ }
1662
+ /**
1663
+ * Handle OAuth callback
1664
+ * Call this after user is redirected back from OAuth provider
1665
+ */
1666
+ async handleCallback(callbackUrl, userId) {
1667
+ if (!this.oauthManager) {
1668
+ throw new Error(`Connector '${this.name}' is not an OAuth connector`);
1669
+ }
1670
+ await this.oauthManager.handleCallback(callbackUrl, userId);
1671
+ }
1672
+ /**
1673
+ * Check if the connector has a valid token
1674
+ */
1675
+ async hasValidToken(userId) {
1676
+ try {
1677
+ if (this.config.auth.type === "api_key") {
1678
+ return true;
1679
+ }
1680
+ if (this.oauthManager) {
1681
+ const token = await this.oauthManager.getToken(userId);
1682
+ return !!token;
1683
+ }
1684
+ return false;
1685
+ } catch {
1686
+ return false;
1687
+ }
1688
+ }
1689
+ /**
1690
+ * Get vendor-specific options from config
1691
+ */
1692
+ getOptions() {
1693
+ return this.config.options ?? {};
1694
+ }
1695
+ /**
1696
+ * Get the service type (explicit or undefined)
1697
+ */
1698
+ get serviceType() {
1699
+ return this.config.serviceType;
1700
+ }
1701
+ /**
1702
+ * Get connector metrics
1703
+ */
1704
+ getMetrics() {
1705
+ return {
1706
+ requestCount: this.requestCount,
1707
+ successCount: this.successCount,
1708
+ failureCount: this.failureCount,
1709
+ avgLatencyMs: this.requestCount > 0 ? this.totalLatencyMs / this.requestCount : 0,
1710
+ circuitBreakerState: this.circuitBreaker?.getState()
1711
+ };
1712
+ }
1713
+ /**
1714
+ * Reset circuit breaker (force close)
1715
+ */
1716
+ resetCircuitBreaker() {
1717
+ this.circuitBreaker?.reset();
1718
+ }
1719
+ /**
1720
+ * Make an authenticated fetch request using this connector
1721
+ * This is the foundation for all vendor-dependent tools
1722
+ *
1723
+ * Features:
1724
+ * - Timeout with AbortController
1725
+ * - Circuit breaker protection
1726
+ * - Retry with exponential backoff
1727
+ * - Request/response logging
1728
+ *
1729
+ * @param endpoint - API endpoint (relative to baseURL) or full URL
1730
+ * @param options - Fetch options with connector-specific settings
1731
+ * @param userId - Optional user ID for multi-user OAuth
1732
+ * @returns Fetch Response
1733
+ */
1734
+ async fetch(endpoint, options, userId) {
1735
+ if (this.disposed) {
1736
+ throw new Error(`Connector '${this.name}' has been disposed`);
1737
+ }
1738
+ const startTime = Date.now();
1739
+ this.requestCount++;
1740
+ const url = endpoint.startsWith("http") ? endpoint : `${this.baseURL}${endpoint}`;
1741
+ const timeout = options?.timeout ?? this.config.timeout ?? DEFAULT_CONNECTOR_TIMEOUT;
1742
+ if (this.config.logging?.enabled) {
1743
+ this.logRequest(url, options);
1744
+ }
1745
+ const doFetch = async () => {
1746
+ const token = await this.getToken(userId);
1747
+ const auth = this.config.auth;
1748
+ let headerName = "Authorization";
1749
+ let headerValue = `Bearer ${token}`;
1750
+ if (auth.type === "api_key") {
1751
+ headerName = auth.headerName || "Authorization";
1752
+ const prefix = auth.headerPrefix ?? "Bearer";
1753
+ headerValue = prefix ? `${prefix} ${token}` : token;
1754
+ }
1755
+ const controller = new AbortController();
1756
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1757
+ try {
1758
+ const response = await fetch(url, {
1759
+ ...options,
1760
+ signal: controller.signal,
1761
+ headers: {
1762
+ ...options?.headers,
1763
+ [headerName]: headerValue
1764
+ }
1765
+ });
1766
+ return response;
1767
+ } finally {
1768
+ clearTimeout(timeoutId);
1769
+ }
1770
+ };
1771
+ const doFetchWithRetry = async () => {
1772
+ const retryConfig = this.config.retry;
1773
+ const maxRetries = retryConfig?.maxRetries ?? DEFAULT_MAX_RETRIES;
1774
+ const retryableStatuses = retryConfig?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES;
1775
+ const baseDelayMs = retryConfig?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
1776
+ const maxDelayMs = retryConfig?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
1777
+ const backoffConfig = {
1778
+ strategy: "exponential",
1779
+ initialDelayMs: baseDelayMs,
1780
+ maxDelayMs,
1781
+ multiplier: 2,
1782
+ jitter: true,
1783
+ jitterFactor: 0.1
1784
+ };
1785
+ let lastError;
1786
+ let lastResponse;
1787
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
1788
+ try {
1789
+ const response = await doFetch();
1790
+ if (!response.ok && retryableStatuses.includes(response.status) && attempt <= maxRetries) {
1791
+ lastResponse = response;
1792
+ const delay = calculateBackoff(attempt, backoffConfig);
1793
+ if (this.config.logging?.enabled) {
1794
+ logger.debug(`Connector ${this.name}: Retry ${attempt}/${maxRetries} after ${delay}ms (status ${response.status})`);
1795
+ }
1796
+ await this.sleep(delay);
1797
+ continue;
1798
+ }
1799
+ return response;
1800
+ } catch (error) {
1801
+ lastError = error;
1802
+ if (lastError.name === "AbortError") {
1803
+ throw new Error(`Request timeout after ${timeout}ms: ${url}`);
1804
+ }
1805
+ if (attempt <= maxRetries && !options?.skipRetry) {
1806
+ const delay = calculateBackoff(attempt, backoffConfig);
1807
+ if (this.config.logging?.enabled) {
1808
+ logger.debug(`Connector ${this.name}: Retry ${attempt}/${maxRetries} after ${delay}ms (error: ${lastError.message})`);
1809
+ }
1810
+ await this.sleep(delay);
1811
+ continue;
1812
+ }
1813
+ throw lastError;
1814
+ }
1815
+ }
1816
+ if (lastResponse) {
1817
+ return lastResponse;
1818
+ }
1819
+ throw lastError ?? new Error("Unknown error during fetch");
1820
+ };
1821
+ try {
1822
+ let response;
1823
+ if (this.circuitBreaker && !options?.skipCircuitBreaker) {
1824
+ response = await this.circuitBreaker.execute(doFetchWithRetry);
1825
+ } else {
1826
+ response = await doFetchWithRetry();
1827
+ }
1828
+ const latency = Date.now() - startTime;
1829
+ this.successCount++;
1830
+ this.totalLatencyMs += latency;
1831
+ metrics.timing("connector.latency", latency, { connector: this.name });
1832
+ metrics.increment("connector.success", 1, { connector: this.name });
1833
+ if (this.config.logging?.enabled) {
1834
+ this.logResponse(url, response, latency);
1835
+ }
1836
+ return response;
1837
+ } catch (error) {
1838
+ const latency = Date.now() - startTime;
1839
+ this.failureCount++;
1840
+ this.totalLatencyMs += latency;
1841
+ metrics.increment("connector.failure", 1, { connector: this.name, error: error.name });
1842
+ if (this.config.logging?.enabled) {
1843
+ logger.error(
1844
+ { connector: this.name, url, latency, error: error.message },
1845
+ `Connector ${this.name} fetch failed: ${error.message}`
1846
+ );
1847
+ }
1848
+ throw error;
1849
+ }
1850
+ }
1851
+ /**
1852
+ * Make an authenticated fetch request and parse JSON response
1853
+ * Throws on non-OK responses
1854
+ *
1855
+ * @param endpoint - API endpoint (relative to baseURL) or full URL
1856
+ * @param options - Fetch options with connector-specific settings
1857
+ * @param userId - Optional user ID for multi-user OAuth
1858
+ * @returns Parsed JSON response
1859
+ */
1860
+ async fetchJSON(endpoint, options, userId) {
1861
+ const response = await this.fetch(endpoint, options, userId);
1862
+ const text = await response.text();
1863
+ let data;
1864
+ try {
1865
+ data = JSON.parse(text);
1866
+ } catch {
1867
+ if (!response.ok) {
1868
+ throw new Error(`HTTP ${response.status}: ${text}`);
1869
+ }
1870
+ throw new Error(`Invalid JSON response: ${text.slice(0, 100)}`);
1871
+ }
1872
+ if (!response.ok) {
1873
+ const errorMsg = typeof data === "object" && data !== null ? JSON.stringify(data) : text;
1874
+ throw new Error(`HTTP ${response.status}: ${errorMsg}`);
1875
+ }
1876
+ return data;
1877
+ }
1878
+ // ============ Private Helpers ============
1879
+ sleep(ms) {
1880
+ return new Promise((resolve) => setTimeout(resolve, ms));
1881
+ }
1882
+ logRequest(url, options) {
1883
+ const logData = {
1884
+ connector: this.name,
1885
+ method: options?.method ?? "GET",
1886
+ url
1887
+ };
1888
+ if (this.config.logging?.logHeaders && options?.headers) {
1889
+ const headers = { ...options.headers };
1890
+ if (headers["Authorization"]) {
1891
+ headers["Authorization"] = "[REDACTED]";
1892
+ }
1893
+ if (headers["authorization"]) {
1894
+ headers["authorization"] = "[REDACTED]";
1895
+ }
1896
+ logData.headers = headers;
1897
+ }
1898
+ if (this.config.logging?.logBody && options?.body) {
1899
+ logData.body = typeof options.body === "string" ? options.body.slice(0, 1e3) : "[non-string body]";
1900
+ }
1901
+ logger.debug(logData, `Connector ${this.name} request`);
1902
+ }
1903
+ logResponse(url, response, latency) {
1904
+ logger.debug(
1905
+ { connector: this.name, url, status: response.status, latency },
1906
+ `Connector ${this.name} response`
1907
+ );
1908
+ }
1909
+ /**
1910
+ * Dispose of resources
1911
+ */
1912
+ dispose() {
1913
+ if (this.disposed) return;
1914
+ this.disposed = true;
1915
+ this.oauthManager = void 0;
1916
+ this.circuitBreaker = void 0;
1917
+ }
1918
+ /**
1919
+ * Check if connector is disposed
1920
+ */
1921
+ isDisposed() {
1922
+ return this.disposed;
1923
+ }
1924
+ // ============ Private ============
1925
+ initOAuthManager(auth) {
1926
+ const oauthConfig = {
1927
+ flow: auth.flow,
1928
+ clientId: auth.clientId,
1929
+ clientSecret: auth.clientSecret,
1930
+ tokenUrl: auth.tokenUrl,
1931
+ authorizationUrl: auth.authorizationUrl,
1932
+ redirectUri: auth.redirectUri,
1933
+ scope: auth.scope,
1934
+ usePKCE: auth.usePKCE,
1935
+ privateKey: auth.privateKey,
1936
+ privateKeyPath: auth.privateKeyPath,
1937
+ audience: auth.audience,
1938
+ refreshBeforeExpiry: auth.refreshBeforeExpiry,
1939
+ storage: _Connector.defaultStorage,
1940
+ storageKey: auth.storageKey ?? this.name
1941
+ };
1942
+ this.oauthManager = new OAuthManager(oauthConfig);
1943
+ }
1944
+ initJWTManager(auth) {
1945
+ this.oauthManager = new OAuthManager({
1946
+ flow: "jwt_bearer",
1947
+ clientId: auth.clientId,
1948
+ tokenUrl: auth.tokenUrl,
1949
+ privateKey: auth.privateKey,
1950
+ privateKeyPath: auth.privateKeyPath,
1951
+ scope: auth.scope,
1952
+ audience: auth.audience,
1953
+ storage: _Connector.defaultStorage,
1954
+ storageKey: this.name
1955
+ });
1956
+ }
1957
+ };
1958
+
1959
+ // src/core/Vendor.ts
1960
+ var Vendor = {
1961
+ OpenAI: "openai",
1962
+ Google: "google",
1963
+ Grok: "grok"};
1964
+
1965
+ // src/domain/errors/AIErrors.ts
1966
+ var AIError = class _AIError extends Error {
1967
+ constructor(message, code, statusCode, originalError) {
1968
+ super(message);
1969
+ this.code = code;
1970
+ this.statusCode = statusCode;
1971
+ this.originalError = originalError;
1972
+ this.name = "AIError";
1973
+ Object.setPrototypeOf(this, _AIError.prototype);
1974
+ }
1975
+ };
1976
+ var ProviderAuthError = class _ProviderAuthError extends AIError {
1977
+ constructor(providerName, message = "Authentication failed") {
1978
+ super(
1979
+ `${providerName}: ${message}`,
1980
+ "PROVIDER_AUTH_ERROR",
1981
+ 401
1982
+ );
1983
+ this.name = "ProviderAuthError";
1984
+ Object.setPrototypeOf(this, _ProviderAuthError.prototype);
1985
+ }
1986
+ };
1987
+ var ProviderRateLimitError = class _ProviderRateLimitError extends AIError {
1988
+ constructor(providerName, retryAfter) {
1989
+ super(
1990
+ `${providerName}: Rate limit exceeded${retryAfter ? `. Retry after ${retryAfter}ms` : ""}`,
1991
+ "PROVIDER_RATE_LIMIT",
1992
+ 429
1993
+ );
1994
+ this.retryAfter = retryAfter;
1995
+ this.name = "ProviderRateLimitError";
1996
+ Object.setPrototypeOf(this, _ProviderRateLimitError.prototype);
1997
+ }
1998
+ };
1999
+ var InvalidConfigError = class _InvalidConfigError extends AIError {
2000
+ constructor(message) {
2001
+ super(message, "INVALID_CONFIG", 400);
2002
+ this.name = "InvalidConfigError";
2003
+ Object.setPrototypeOf(this, _InvalidConfigError.prototype);
2004
+ }
2005
+ };
2006
+ var ProviderError = class _ProviderError extends AIError {
2007
+ constructor(providerName, message, statusCode, originalError) {
2008
+ super(
2009
+ `${providerName}: ${message}`,
2010
+ "PROVIDER_ERROR",
2011
+ statusCode,
2012
+ originalError
2013
+ );
2014
+ this.providerName = providerName;
2015
+ this.name = "ProviderError";
2016
+ Object.setPrototypeOf(this, _ProviderError.prototype);
2017
+ }
2018
+ };
2019
+
2020
+ // src/infrastructure/providers/base/BaseProvider.ts
2021
+ var BaseProvider = class {
2022
+ constructor(config) {
2023
+ this.config = config;
2024
+ }
2025
+ /**
2026
+ * Validate provider configuration
2027
+ * Returns validation result with details
2028
+ */
2029
+ async validateConfig() {
2030
+ const validation = this.validateApiKey();
2031
+ return validation.isValid;
2032
+ }
2033
+ /**
2034
+ * Validate API key format and presence
2035
+ * Can be overridden by providers with specific key formats
2036
+ */
2037
+ validateApiKey() {
2038
+ const apiKey = this.config.apiKey;
2039
+ if (!apiKey || apiKey.trim().length === 0) {
2040
+ return { isValid: false };
2041
+ }
2042
+ const placeholders = [
2043
+ "your-api-key",
2044
+ "YOUR_API_KEY",
2045
+ "sk-xxx",
2046
+ "api-key-here",
2047
+ "REPLACE_ME",
2048
+ "<your-key>"
2049
+ ];
2050
+ if (placeholders.some((p) => apiKey.includes(p))) {
2051
+ return {
2052
+ isValid: false,
2053
+ warning: `API key appears to be a placeholder value`
2054
+ };
2055
+ }
2056
+ return this.validateProviderSpecificKeyFormat(apiKey);
2057
+ }
2058
+ /**
2059
+ * Override this method in provider implementations for specific key format validation
2060
+ */
2061
+ validateProviderSpecificKeyFormat(_apiKey) {
2062
+ return { isValid: true };
2063
+ }
2064
+ /**
2065
+ * Validate config and throw if invalid
2066
+ */
2067
+ assertValidConfig() {
2068
+ const validation = this.validateApiKey();
2069
+ if (!validation.isValid) {
2070
+ throw new InvalidConfigError(
2071
+ `Invalid API key for provider '${this.name}'${validation.warning ? `: ${validation.warning}` : ""}`
2072
+ );
2073
+ }
2074
+ }
2075
+ /**
2076
+ * Get API key from config
2077
+ */
2078
+ getApiKey() {
2079
+ return this.config.apiKey;
2080
+ }
2081
+ /**
2082
+ * Get base URL if configured
2083
+ */
2084
+ getBaseURL() {
2085
+ return this.config.baseURL;
2086
+ }
2087
+ /**
2088
+ * Get timeout configuration
2089
+ */
2090
+ getTimeout() {
2091
+ return this.config.timeout || 6e4;
2092
+ }
2093
+ /**
2094
+ * Get max retries configuration
2095
+ */
2096
+ getMaxRetries() {
2097
+ return this.config.maxRetries || 3;
2098
+ }
2099
+ };
2100
+
2101
+ // src/infrastructure/providers/base/BaseMediaProvider.ts
2102
+ var BaseMediaProvider = class extends BaseProvider {
2103
+ circuitBreaker;
2104
+ logger;
2105
+ _isObservabilityInitialized = false;
2106
+ constructor(config) {
2107
+ super(config);
2108
+ this.logger = logger.child({
2109
+ component: "MediaProvider",
2110
+ provider: "unknown"
2111
+ });
2112
+ }
2113
+ /**
2114
+ * Auto-initialize observability on first use (lazy initialization)
2115
+ * This is called automatically by executeWithCircuitBreaker()
2116
+ * @internal
2117
+ */
2118
+ ensureObservabilityInitialized() {
2119
+ if (this._isObservabilityInitialized) {
2120
+ return;
2121
+ }
2122
+ const providerName = this.name || "unknown";
2123
+ const cbConfig = this.config.circuitBreaker || DEFAULT_CIRCUIT_BREAKER_CONFIG;
2124
+ this.circuitBreaker = new CircuitBreaker(
2125
+ `media-provider:${providerName}`,
2126
+ cbConfig
2127
+ );
2128
+ this.logger = logger.child({
2129
+ component: "MediaProvider",
2130
+ provider: providerName
2131
+ });
2132
+ this.circuitBreaker.on("opened", (data) => {
2133
+ this.logger.warn(data, "Circuit breaker opened");
2134
+ metrics.increment("circuit_breaker.opened", 1, {
2135
+ breaker: data.name,
2136
+ provider: providerName
2137
+ });
2138
+ });
2139
+ this.circuitBreaker.on("closed", (data) => {
2140
+ this.logger.info(data, "Circuit breaker closed");
2141
+ metrics.increment("circuit_breaker.closed", 1, {
2142
+ breaker: data.name,
2143
+ provider: providerName
2144
+ });
2145
+ });
2146
+ this._isObservabilityInitialized = true;
2147
+ }
2148
+ /**
2149
+ * Execute operation with circuit breaker protection
2150
+ * Automatically records metrics and handles errors
2151
+ *
2152
+ * @param operation - The async operation to execute
2153
+ * @param operationName - Name of the operation for metrics (e.g., 'image.generate', 'audio.synthesize')
2154
+ * @param metadata - Additional metadata to log/record
2155
+ */
2156
+ async executeWithCircuitBreaker(operation, operationName, metadata) {
2157
+ this.ensureObservabilityInitialized();
2158
+ const startTime = Date.now();
2159
+ const metricLabels = {
2160
+ provider: this.name,
2161
+ operation: operationName,
2162
+ ...metadata
2163
+ };
2164
+ try {
2165
+ const result = await this.circuitBreaker.execute(operation);
2166
+ const duration = Date.now() - startTime;
2167
+ metrics.histogram(`${operationName}.duration`, duration, metricLabels);
2168
+ metrics.increment(`${operationName}.success`, 1, metricLabels);
2169
+ this.logger.debug(
2170
+ { operation: operationName, duration, ...metadata },
2171
+ "Operation completed successfully"
2172
+ );
2173
+ return result;
2174
+ } catch (error) {
2175
+ const duration = Date.now() - startTime;
2176
+ metrics.increment(`${operationName}.error`, 1, {
2177
+ ...metricLabels,
2178
+ error: error instanceof Error ? error.name : "unknown"
2179
+ });
2180
+ this.logger.error(
2181
+ {
2182
+ operation: operationName,
2183
+ duration,
2184
+ error: error instanceof Error ? error.message : String(error),
2185
+ ...metadata
2186
+ },
2187
+ "Operation failed"
2188
+ );
2189
+ throw error;
2190
+ }
2191
+ }
2192
+ /**
2193
+ * Log operation start with context
2194
+ * Useful for logging before async operations
2195
+ */
2196
+ logOperationStart(operation, context) {
2197
+ this.ensureObservabilityInitialized();
2198
+ this.logger.info({ operation, ...context }, `${operation} started`);
2199
+ }
2200
+ /**
2201
+ * Log operation completion with context
2202
+ */
2203
+ logOperationComplete(operation, context) {
2204
+ this.ensureObservabilityInitialized();
2205
+ this.logger.info({ operation, ...context }, `${operation} completed`);
2206
+ }
2207
+ };
2208
+
2209
+ // src/infrastructure/providers/openai/OpenAIImageProvider.ts
2210
+ var OpenAIImageProvider = class extends BaseMediaProvider {
2211
+ name = "openai-image";
2212
+ vendor = "openai";
2213
+ capabilities = {
2214
+ text: false,
2215
+ images: true,
2216
+ videos: false,
2217
+ audio: false,
2218
+ features: {
2219
+ imageGeneration: true,
2220
+ imageEditing: true
2221
+ }
2222
+ };
2223
+ client;
2224
+ constructor(config) {
2225
+ super({ apiKey: config.auth.apiKey, ...config });
2226
+ this.client = new OpenAI__default.default({
2227
+ apiKey: config.auth.apiKey,
2228
+ baseURL: config.baseURL,
2229
+ organization: config.organization,
2230
+ timeout: config.timeout,
2231
+ maxRetries: config.maxRetries
2232
+ });
2233
+ }
2234
+ /**
2235
+ * Generate images from a text prompt
2236
+ */
2237
+ async generateImage(options) {
2238
+ return this.executeWithCircuitBreaker(
2239
+ async () => {
2240
+ try {
2241
+ this.logOperationStart("image.generate", {
2242
+ model: options.model,
2243
+ size: options.size,
2244
+ quality: options.quality,
2245
+ n: options.n
2246
+ });
2247
+ const isGptImage = options.model === "gpt-image-1";
2248
+ const params = {
2249
+ model: options.model,
2250
+ prompt: options.prompt,
2251
+ size: options.size,
2252
+ quality: options.quality,
2253
+ style: options.style,
2254
+ n: options.n || 1
2255
+ };
2256
+ if (!isGptImage) {
2257
+ params.response_format = options.response_format || "b64_json";
2258
+ }
2259
+ const response = await this.client.images.generate(params);
2260
+ const data = response.data || [];
2261
+ this.logOperationComplete("image.generate", {
2262
+ model: options.model,
2263
+ imagesGenerated: data.length
2264
+ });
2265
+ return {
2266
+ created: response.created,
2267
+ data: data.map((img) => ({
2268
+ url: img.url,
2269
+ b64_json: img.b64_json,
2270
+ revised_prompt: img.revised_prompt
2271
+ }))
2272
+ };
2273
+ } catch (error) {
2274
+ this.handleError(error);
2275
+ throw error;
2276
+ }
2277
+ },
2278
+ "image.generate",
2279
+ { model: options.model }
2280
+ );
2281
+ }
2282
+ /**
2283
+ * Edit an existing image with a prompt
2284
+ * Supported by: gpt-image-1, dall-e-2
2285
+ */
2286
+ async editImage(options) {
2287
+ return this.executeWithCircuitBreaker(
2288
+ async () => {
2289
+ try {
2290
+ this.logOperationStart("image.edit", {
2291
+ model: options.model,
2292
+ size: options.size,
2293
+ n: options.n
2294
+ });
2295
+ const image = this.prepareImageInput(options.image);
2296
+ const mask = options.mask ? this.prepareImageInput(options.mask) : void 0;
2297
+ const isGptImage = options.model === "gpt-image-1";
2298
+ const params = {
2299
+ model: options.model,
2300
+ image,
2301
+ prompt: options.prompt,
2302
+ mask,
2303
+ size: options.size,
2304
+ n: options.n || 1
2305
+ };
2306
+ if (!isGptImage) {
2307
+ params.response_format = options.response_format || "b64_json";
2308
+ }
2309
+ const response = await this.client.images.edit(params);
2310
+ const data = response.data || [];
2311
+ this.logOperationComplete("image.edit", {
2312
+ model: options.model,
2313
+ imagesGenerated: data.length
2314
+ });
2315
+ return {
2316
+ created: response.created,
2317
+ data: data.map((img) => ({
2318
+ url: img.url,
2319
+ b64_json: img.b64_json,
2320
+ revised_prompt: img.revised_prompt
2321
+ }))
2322
+ };
2323
+ } catch (error) {
2324
+ this.handleError(error);
2325
+ throw error;
2326
+ }
2327
+ },
2328
+ "image.edit",
2329
+ { model: options.model }
2330
+ );
2331
+ }
2332
+ /**
2333
+ * Create variations of an existing image
2334
+ * Supported by: dall-e-2 only
2335
+ */
2336
+ async createVariation(options) {
2337
+ return this.executeWithCircuitBreaker(
2338
+ async () => {
2339
+ try {
2340
+ this.logOperationStart("image.variation", {
2341
+ model: options.model,
2342
+ size: options.size,
2343
+ n: options.n
2344
+ });
2345
+ const image = this.prepareImageInput(options.image);
2346
+ const response = await this.client.images.createVariation({
2347
+ model: options.model,
2348
+ image,
2349
+ size: options.size,
2350
+ n: options.n || 1,
2351
+ response_format: options.response_format || "b64_json"
2352
+ });
2353
+ const data = response.data || [];
2354
+ this.logOperationComplete("image.variation", {
2355
+ model: options.model,
2356
+ imagesGenerated: data.length
2357
+ });
2358
+ return {
2359
+ created: response.created,
2360
+ data: data.map((img) => ({
2361
+ url: img.url,
2362
+ b64_json: img.b64_json,
2363
+ revised_prompt: img.revised_prompt
2364
+ }))
2365
+ };
2366
+ } catch (error) {
2367
+ this.handleError(error);
2368
+ throw error;
2369
+ }
2370
+ },
2371
+ "image.variation",
2372
+ { model: options.model }
2373
+ );
2374
+ }
2375
+ /**
2376
+ * List available image models
2377
+ */
2378
+ async listModels() {
2379
+ return ["gpt-image-1", "dall-e-3", "dall-e-2"];
2380
+ }
2381
+ /**
2382
+ * Prepare image input (Buffer or file path) for OpenAI API
2383
+ */
2384
+ prepareImageInput(image) {
2385
+ if (Buffer.isBuffer(image)) {
2386
+ return new File([new Uint8Array(image)], "image.png", { type: "image/png" });
2387
+ }
2388
+ return fs2__namespace.createReadStream(image);
2389
+ }
2390
+ /**
2391
+ * Handle OpenAI API errors
2392
+ */
2393
+ handleError(error) {
2394
+ const message = error.message || "Unknown OpenAI API error";
2395
+ const status = error.status;
2396
+ if (status === 401) {
2397
+ throw new ProviderAuthError("openai", "Invalid API key");
2398
+ }
2399
+ if (status === 429) {
2400
+ throw new ProviderRateLimitError("openai", message);
2401
+ }
2402
+ if (status === 400) {
2403
+ if (message.includes("safety system")) {
2404
+ throw new ProviderError("openai", `Content policy violation: ${message}`);
2405
+ }
2406
+ throw new ProviderError("openai", `Bad request: ${message}`);
2407
+ }
2408
+ throw new ProviderError("openai", message);
2409
+ }
2410
+ };
2411
+ var GoogleImageProvider = class extends BaseMediaProvider {
2412
+ name = "google-image";
2413
+ vendor = "google";
2414
+ capabilities = {
2415
+ text: false,
2416
+ images: true,
2417
+ videos: false,
2418
+ audio: false,
2419
+ features: {
2420
+ imageGeneration: true,
2421
+ imageEditing: true
2422
+ }
2423
+ };
2424
+ client;
2425
+ constructor(config) {
2426
+ super(config);
2427
+ this.client = new genai.GoogleGenAI({
2428
+ apiKey: config.apiKey
2429
+ });
2430
+ }
2431
+ /**
2432
+ * Generate images from a text prompt using Google Imagen
2433
+ */
2434
+ async generateImage(options) {
2435
+ return this.executeWithCircuitBreaker(
2436
+ async () => {
2437
+ try {
2438
+ this.logOperationStart("image.generate", {
2439
+ model: options.model,
2440
+ n: options.n
2441
+ });
2442
+ const googleOptions = options;
2443
+ const response = await this.client.models.generateImages({
2444
+ model: options.model,
2445
+ prompt: options.prompt,
2446
+ config: {
2447
+ numberOfImages: options.n || 1,
2448
+ negativePrompt: googleOptions.negativePrompt,
2449
+ aspectRatio: googleOptions.aspectRatio,
2450
+ seed: googleOptions.seed,
2451
+ outputMimeType: googleOptions.outputMimeType,
2452
+ includeRaiReason: googleOptions.includeRaiReason
2453
+ }
2454
+ });
2455
+ const images = response.generatedImages || [];
2456
+ this.logOperationComplete("image.generate", {
2457
+ model: options.model,
2458
+ imagesGenerated: images.length
2459
+ });
2460
+ return {
2461
+ created: Math.floor(Date.now() / 1e3),
2462
+ data: images.map((img) => ({
2463
+ b64_json: img.image?.imageBytes
2464
+ // Google doesn't provide URLs, only base64
2465
+ }))
2466
+ };
2467
+ } catch (error) {
2468
+ this.handleError(error);
2469
+ throw error;
2470
+ }
2471
+ },
2472
+ "image.generate",
2473
+ { model: options.model }
2474
+ );
2475
+ }
2476
+ /**
2477
+ * Edit an existing image using Imagen capability model
2478
+ * Uses imagen-3.0-capability-001
2479
+ */
2480
+ async editImage(options) {
2481
+ return this.executeWithCircuitBreaker(
2482
+ async () => {
2483
+ try {
2484
+ this.logOperationStart("image.edit", {
2485
+ model: options.model,
2486
+ n: options.n
2487
+ });
2488
+ const referenceImage = await this.prepareReferenceImage(options.image);
2489
+ const response = await this.client.models.editImage({
2490
+ model: options.model || "imagen-3.0-capability-001",
2491
+ prompt: options.prompt,
2492
+ referenceImages: [referenceImage],
2493
+ config: {
2494
+ numberOfImages: options.n || 1
2495
+ }
2496
+ });
2497
+ const images = response.generatedImages || [];
2498
+ this.logOperationComplete("image.edit", {
2499
+ model: options.model,
2500
+ imagesGenerated: images.length
2501
+ });
2502
+ return {
2503
+ created: Math.floor(Date.now() / 1e3),
2504
+ data: images.map((img) => ({
2505
+ b64_json: img.image?.imageBytes
2506
+ }))
2507
+ };
2508
+ } catch (error) {
2509
+ this.handleError(error);
2510
+ throw error;
2511
+ }
2512
+ },
2513
+ "image.edit",
2514
+ { model: options.model }
2515
+ );
2516
+ }
2517
+ /**
2518
+ * List available image models
2519
+ */
2520
+ async listModels() {
2521
+ return [
2522
+ "imagen-4.0-generate-001",
2523
+ "imagen-4.0-ultra-generate-001",
2524
+ "imagen-4.0-fast-generate-001"
2525
+ ];
2526
+ }
2527
+ /**
2528
+ * Prepare a reference image for Google's editImage API
2529
+ */
2530
+ async prepareReferenceImage(image) {
2531
+ let imageBytes;
2532
+ if (Buffer.isBuffer(image)) {
2533
+ imageBytes = image.toString("base64");
2534
+ } else {
2535
+ const fs5 = await import('fs');
2536
+ const buffer = fs5.readFileSync(image);
2537
+ imageBytes = buffer.toString("base64");
2538
+ }
2539
+ return {
2540
+ referenceImage: {
2541
+ image: {
2542
+ imageBytes
2543
+ }
2544
+ },
2545
+ referenceType: "REFERENCE_TYPE_SUBJECT"
2546
+ };
2547
+ }
2548
+ /**
2549
+ * Handle Google API errors
2550
+ */
2551
+ handleError(error) {
2552
+ const message = error.message || "Unknown Google API error";
2553
+ const status = error.status || error.code;
2554
+ if (status === 401 || message.includes("API key not valid")) {
2555
+ throw new ProviderAuthError("google", "Invalid API key");
2556
+ }
2557
+ if (status === 429 || message.includes("Resource exhausted")) {
2558
+ throw new ProviderRateLimitError("google", message);
2559
+ }
2560
+ if (status === 400) {
2561
+ if (message.includes("SAFETY") || message.includes("blocked") || message.includes("Responsible AI")) {
2562
+ throw new ProviderError("google", `Content policy violation: ${message}`);
2563
+ }
2564
+ throw new ProviderError("google", `Bad request: ${message}`);
2565
+ }
2566
+ throw new ProviderError("google", message);
2567
+ }
2568
+ };
2569
+ var GROK_API_BASE_URL = "https://api.x.ai/v1";
2570
+ var GrokImageProvider = class extends BaseMediaProvider {
2571
+ name = "grok-image";
2572
+ vendor = "grok";
2573
+ capabilities = {
2574
+ text: false,
2575
+ images: true,
2576
+ videos: false,
2577
+ audio: false,
2578
+ features: {
2579
+ imageGeneration: true,
2580
+ imageEditing: true
2581
+ }
2582
+ };
2583
+ client;
2584
+ constructor(config) {
2585
+ super({ apiKey: config.auth.apiKey, ...config });
2586
+ this.client = new OpenAI__default.default({
2587
+ apiKey: config.auth.apiKey,
2588
+ baseURL: config.baseURL || GROK_API_BASE_URL,
2589
+ timeout: config.timeout,
2590
+ maxRetries: config.maxRetries
2591
+ });
2592
+ }
2593
+ /**
2594
+ * Generate images from a text prompt
2595
+ */
2596
+ async generateImage(options) {
2597
+ return this.executeWithCircuitBreaker(
2598
+ async () => {
2599
+ try {
2600
+ this.logOperationStart("image.generate", {
2601
+ model: options.model,
2602
+ size: options.size,
2603
+ quality: options.quality,
2604
+ n: options.n
2605
+ });
2606
+ const params = {
2607
+ model: options.model || "grok-imagine-image",
2608
+ prompt: options.prompt,
2609
+ n: options.n || 1,
2610
+ response_format: options.response_format || "b64_json"
2611
+ };
2612
+ if (options.aspectRatio) {
2613
+ params.aspect_ratio = options.aspectRatio;
2614
+ }
2615
+ const response = await this.client.images.generate(params);
2616
+ const data = response.data || [];
2617
+ this.logOperationComplete("image.generate", {
2618
+ model: options.model,
2619
+ imagesGenerated: data.length
2620
+ });
2621
+ return {
2622
+ created: response.created,
2623
+ data: data.map((img) => ({
2624
+ url: img.url,
2625
+ b64_json: img.b64_json,
2626
+ revised_prompt: img.revised_prompt
2627
+ }))
2628
+ };
2629
+ } catch (error) {
2630
+ this.handleError(error);
2631
+ throw error;
2632
+ }
2633
+ },
2634
+ "image.generate",
2635
+ { model: options.model }
2636
+ );
2637
+ }
2638
+ /**
2639
+ * Edit an existing image with a prompt
2640
+ */
2641
+ async editImage(options) {
2642
+ return this.executeWithCircuitBreaker(
2643
+ async () => {
2644
+ try {
2645
+ this.logOperationStart("image.edit", {
2646
+ model: options.model,
2647
+ size: options.size,
2648
+ n: options.n
2649
+ });
2650
+ const image = this.prepareImageInput(options.image);
2651
+ const mask = options.mask ? this.prepareImageInput(options.mask) : void 0;
2652
+ const params = {
2653
+ model: options.model || "grok-imagine-image",
2654
+ image,
2655
+ prompt: options.prompt,
2656
+ mask,
2657
+ size: options.size,
2658
+ n: options.n || 1,
2659
+ response_format: options.response_format || "b64_json"
2660
+ };
2661
+ const response = await this.client.images.edit(params);
2662
+ const data = response.data || [];
2663
+ this.logOperationComplete("image.edit", {
2664
+ model: options.model,
2665
+ imagesGenerated: data.length
2666
+ });
2667
+ return {
2668
+ created: response.created,
2669
+ data: data.map((img) => ({
2670
+ url: img.url,
2671
+ b64_json: img.b64_json,
2672
+ revised_prompt: img.revised_prompt
2673
+ }))
2674
+ };
2675
+ } catch (error) {
2676
+ this.handleError(error);
2677
+ throw error;
2678
+ }
2679
+ },
2680
+ "image.edit",
2681
+ { model: options.model }
2682
+ );
2683
+ }
2684
+ /**
2685
+ * List available image models
2686
+ */
2687
+ async listModels() {
2688
+ return ["grok-imagine-image"];
2689
+ }
2690
+ /**
2691
+ * Prepare image input (Buffer or file path) for API
2692
+ */
2693
+ prepareImageInput(image) {
2694
+ if (Buffer.isBuffer(image)) {
2695
+ return new File([new Uint8Array(image)], "image.png", { type: "image/png" });
2696
+ }
2697
+ return fs2__namespace.createReadStream(image);
2698
+ }
2699
+ /**
2700
+ * Handle API errors
2701
+ */
2702
+ handleError(error) {
2703
+ const message = error.message || "Unknown Grok API error";
2704
+ const status = error.status;
2705
+ if (status === 401) {
2706
+ throw new ProviderAuthError("grok", "Invalid API key");
2707
+ }
2708
+ if (status === 429) {
2709
+ throw new ProviderRateLimitError("grok", message);
2710
+ }
2711
+ if (status === 400) {
2712
+ if (message.includes("safety") || message.includes("policy")) {
2713
+ throw new ProviderError("grok", `Content policy violation: ${message}`);
2714
+ }
2715
+ throw new ProviderError("grok", `Bad request: ${message}`);
2716
+ }
2717
+ throw new ProviderError("grok", message);
2718
+ }
2719
+ };
2720
+
2721
+ // src/core/createImageProvider.ts
2722
+ function createImageProvider(connector) {
2723
+ const vendor = connector.vendor;
2724
+ switch (vendor) {
2725
+ case Vendor.OpenAI:
2726
+ return new OpenAIImageProvider(extractOpenAIConfig(connector));
2727
+ case Vendor.Google:
2728
+ return new GoogleImageProvider(extractGoogleConfig(connector));
2729
+ case Vendor.Grok:
2730
+ return new GrokImageProvider(extractGrokConfig(connector));
2731
+ default:
2732
+ throw new Error(
2733
+ `No Image provider available for vendor: ${vendor}. Supported vendors: ${Vendor.OpenAI}, ${Vendor.Google}, ${Vendor.Grok}`
2734
+ );
2735
+ }
2736
+ }
2737
+ function extractOpenAIConfig(connector) {
2738
+ const auth = connector.config.auth;
2739
+ if (auth.type !== "api_key") {
2740
+ throw new Error("OpenAI requires API key authentication");
2741
+ }
2742
+ const options = connector.getOptions();
2743
+ return {
2744
+ auth: {
2745
+ type: "api_key",
2746
+ apiKey: auth.apiKey
2747
+ },
2748
+ baseURL: connector.baseURL,
2749
+ organization: options.organization,
2750
+ timeout: options.timeout,
2751
+ maxRetries: options.maxRetries
2752
+ };
2753
+ }
2754
+ function extractGoogleConfig(connector) {
2755
+ const auth = connector.config.auth;
2756
+ if (auth.type !== "api_key") {
2757
+ throw new Error("Google requires API key authentication");
2758
+ }
2759
+ return {
2760
+ apiKey: auth.apiKey
2761
+ };
2762
+ }
2763
+ function extractGrokConfig(connector) {
2764
+ const auth = connector.config.auth;
2765
+ if (auth.type !== "api_key") {
2766
+ throw new Error("Grok requires API key authentication");
2767
+ }
2768
+ const options = connector.getOptions();
2769
+ return {
2770
+ auth: {
2771
+ type: "api_key",
2772
+ apiKey: auth.apiKey
2773
+ },
2774
+ baseURL: connector.baseURL,
2775
+ timeout: options.timeout,
2776
+ maxRetries: options.maxRetries
2777
+ };
2778
+ }
2779
+
2780
+ // src/domain/entities/RegistryUtils.ts
2781
+ function createRegistryHelpers(registry) {
2782
+ return {
2783
+ /**
2784
+ * Get model information by name
2785
+ */
2786
+ getInfo: (modelName) => {
2787
+ return registry[modelName];
2788
+ },
2789
+ /**
2790
+ * Get all active models for a specific vendor
2791
+ */
2792
+ getByVendor: (vendor) => {
2793
+ return Object.values(registry).filter(
2794
+ (model) => model.provider === vendor && model.isActive
2795
+ );
2796
+ },
2797
+ /**
2798
+ * Get all currently active models (across all vendors)
2799
+ */
2800
+ getActive: () => {
2801
+ return Object.values(registry).filter((model) => model.isActive);
2802
+ },
2803
+ /**
2804
+ * Get all models (including inactive/deprecated)
2805
+ */
2806
+ getAll: () => {
2807
+ return Object.values(registry);
2808
+ },
2809
+ /**
2810
+ * Check if model exists in registry
2811
+ */
2812
+ has: (modelName) => {
2813
+ return modelName in registry;
2814
+ }
2815
+ };
2816
+ }
2817
+
2818
+ // src/domain/entities/ImageModel.ts
2819
+ var IMAGE_MODELS = {
2820
+ [Vendor.OpenAI]: {
2821
+ /** GPT-Image-1: Latest OpenAI image model with best quality */
2822
+ GPT_IMAGE_1: "gpt-image-1",
2823
+ /** DALL-E 3: High quality image generation */
2824
+ DALL_E_3: "dall-e-3",
2825
+ /** DALL-E 2: Fast, supports editing and variations */
2826
+ DALL_E_2: "dall-e-2"
2827
+ },
2828
+ [Vendor.Google]: {
2829
+ /** Imagen 4.0: Latest Google image generation model */
2830
+ IMAGEN_4_GENERATE: "imagen-4.0-generate-001",
2831
+ /** Imagen 4.0 Ultra: Highest quality */
2832
+ IMAGEN_4_ULTRA: "imagen-4.0-ultra-generate-001",
2833
+ /** Imagen 4.0 Fast: Optimized for speed */
2834
+ IMAGEN_4_FAST: "imagen-4.0-fast-generate-001"
2835
+ },
2836
+ [Vendor.Grok]: {
2837
+ /** Grok Imagine Image: xAI image generation with editing support */
2838
+ GROK_IMAGINE_IMAGE: "grok-imagine-image",
2839
+ /** Grok 2 Image: xAI image generation (text-only input) */
2840
+ GROK_2_IMAGE_1212: "grok-2-image-1212"
2841
+ }
2842
+ };
2843
+ var IMAGE_MODEL_REGISTRY = {
2844
+ // ======================== OpenAI ========================
2845
+ "gpt-image-1": {
2846
+ name: "gpt-image-1",
2847
+ displayName: "GPT-Image-1",
2848
+ provider: Vendor.OpenAI,
2849
+ description: "OpenAI latest image generation model with best quality and features",
2850
+ isActive: true,
2851
+ releaseDate: "2025-04-01",
2852
+ sources: {
2853
+ documentation: "https://platform.openai.com/docs/guides/images",
2854
+ pricing: "https://openai.com/pricing",
2855
+ lastVerified: "2026-01-25"
2856
+ },
2857
+ capabilities: {
2858
+ sizes: ["1024x1024", "1024x1536", "1536x1024", "auto"],
2859
+ maxImagesPerRequest: 10,
2860
+ outputFormats: ["png", "webp", "jpeg"],
2861
+ features: {
2862
+ generation: true,
2863
+ editing: true,
2864
+ variations: false,
2865
+ styleControl: false,
2866
+ qualityControl: true,
2867
+ transparency: true,
2868
+ promptRevision: false
2869
+ },
2870
+ limits: { maxPromptLength: 32e3 },
2871
+ vendorOptions: {
2872
+ quality: {
2873
+ type: "enum",
2874
+ label: "Quality",
2875
+ description: "Image quality level",
2876
+ enum: ["auto", "low", "medium", "high"],
2877
+ default: "auto",
2878
+ controlType: "select"
2879
+ },
2880
+ background: {
2881
+ type: "enum",
2882
+ label: "Background",
2883
+ description: "Background transparency",
2884
+ enum: ["auto", "transparent", "opaque"],
2885
+ default: "auto",
2886
+ controlType: "select"
2887
+ },
2888
+ output_format: {
2889
+ type: "enum",
2890
+ label: "Output Format",
2891
+ description: "Image file format",
2892
+ enum: ["png", "jpeg", "webp"],
2893
+ default: "png",
2894
+ controlType: "select"
2895
+ },
2896
+ output_compression: {
2897
+ type: "number",
2898
+ label: "Compression",
2899
+ description: "Compression level for JPEG/WebP (0-100)",
2900
+ min: 0,
2901
+ max: 100,
2902
+ default: 75,
2903
+ controlType: "slider"
2904
+ },
2905
+ moderation: {
2906
+ type: "enum",
2907
+ label: "Moderation",
2908
+ description: "Content moderation strictness",
2909
+ enum: ["auto", "low"],
2910
+ default: "auto",
2911
+ controlType: "radio"
2912
+ }
2913
+ }
2914
+ },
2915
+ pricing: {
2916
+ perImageStandard: 0.011,
2917
+ perImageHD: 0.042,
2918
+ currency: "USD"
2919
+ }
2920
+ },
2921
+ "dall-e-3": {
2922
+ name: "dall-e-3",
2923
+ displayName: "DALL-E 3",
2924
+ provider: Vendor.OpenAI,
2925
+ description: "High quality image generation with prompt revision",
2926
+ isActive: true,
2927
+ releaseDate: "2023-11-06",
2928
+ deprecationDate: "2026-05-12",
2929
+ sources: {
2930
+ documentation: "https://platform.openai.com/docs/guides/images",
2931
+ pricing: "https://openai.com/pricing",
2932
+ lastVerified: "2026-01-25"
2933
+ },
2934
+ capabilities: {
2935
+ sizes: ["1024x1024", "1024x1792", "1792x1024"],
2936
+ maxImagesPerRequest: 1,
2937
+ outputFormats: ["png", "url"],
2938
+ features: {
2939
+ generation: true,
2940
+ editing: false,
2941
+ variations: false,
2942
+ styleControl: true,
2943
+ qualityControl: true,
2944
+ transparency: false,
2945
+ promptRevision: true
2946
+ },
2947
+ limits: { maxPromptLength: 4e3 },
2948
+ vendorOptions: {
2949
+ quality: {
2950
+ type: "enum",
2951
+ label: "Quality",
2952
+ description: "Image quality: standard or HD",
2953
+ enum: ["standard", "hd"],
2954
+ default: "standard",
2955
+ controlType: "radio"
2956
+ },
2957
+ style: {
2958
+ type: "enum",
2959
+ label: "Style",
2960
+ description: "Image style: vivid (hyper-real) or natural",
2961
+ enum: ["vivid", "natural"],
2962
+ default: "vivid",
2963
+ controlType: "radio"
2964
+ }
2965
+ }
2966
+ },
2967
+ pricing: {
2968
+ perImageStandard: 0.04,
2969
+ perImageHD: 0.08,
2970
+ currency: "USD"
2971
+ }
2972
+ },
2973
+ "dall-e-2": {
2974
+ name: "dall-e-2",
2975
+ displayName: "DALL-E 2",
2976
+ provider: Vendor.OpenAI,
2977
+ description: "Fast image generation with editing and variation support",
2978
+ isActive: true,
2979
+ releaseDate: "2022-11-03",
2980
+ deprecationDate: "2026-05-12",
2981
+ sources: {
2982
+ documentation: "https://platform.openai.com/docs/guides/images",
2983
+ pricing: "https://openai.com/pricing",
2984
+ lastVerified: "2026-01-25"
2985
+ },
2986
+ capabilities: {
2987
+ sizes: ["256x256", "512x512", "1024x1024"],
2988
+ maxImagesPerRequest: 10,
2989
+ outputFormats: ["png", "url"],
2990
+ features: {
2991
+ generation: true,
2992
+ editing: true,
2993
+ variations: true,
2994
+ styleControl: false,
2995
+ qualityControl: false,
2996
+ transparency: false,
2997
+ promptRevision: false
2998
+ },
2999
+ limits: { maxPromptLength: 1e3 },
3000
+ vendorOptions: {}
3001
+ },
3002
+ pricing: {
3003
+ perImage: 0.02,
3004
+ currency: "USD"
3005
+ }
3006
+ },
3007
+ // ======================== Google ========================
3008
+ "imagen-4.0-generate-001": {
3009
+ name: "imagen-4.0-generate-001",
3010
+ displayName: "Imagen 4.0 Generate",
3011
+ provider: Vendor.Google,
3012
+ description: "Google Imagen 4.0 - standard quality image generation",
3013
+ isActive: true,
3014
+ releaseDate: "2025-06-01",
3015
+ sources: {
3016
+ documentation: "https://ai.google.dev/gemini-api/docs/imagen",
3017
+ pricing: "https://ai.google.dev/pricing",
3018
+ lastVerified: "2026-01-25"
3019
+ },
3020
+ capabilities: {
3021
+ sizes: ["1024x1024"],
3022
+ aspectRatios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
3023
+ maxImagesPerRequest: 4,
3024
+ outputFormats: ["png", "jpeg"],
3025
+ features: {
3026
+ generation: true,
3027
+ editing: false,
3028
+ variations: false,
3029
+ styleControl: false,
3030
+ qualityControl: false,
3031
+ transparency: false,
3032
+ promptRevision: false
3033
+ },
3034
+ limits: { maxPromptLength: 480 },
3035
+ vendorOptions: {
3036
+ aspectRatio: {
3037
+ type: "enum",
3038
+ label: "Aspect Ratio",
3039
+ description: "Output image proportions",
3040
+ enum: ["1:1", "3:4", "4:3", "16:9", "9:16"],
3041
+ default: "1:1",
3042
+ controlType: "select"
3043
+ },
3044
+ sampleImageSize: {
3045
+ type: "enum",
3046
+ label: "Resolution",
3047
+ description: "Output image resolution",
3048
+ enum: ["1K", "2K"],
3049
+ default: "1K",
3050
+ controlType: "radio"
3051
+ },
3052
+ outputMimeType: {
3053
+ type: "enum",
3054
+ label: "Output Format",
3055
+ description: "Image file format",
3056
+ enum: ["image/png", "image/jpeg"],
3057
+ default: "image/png",
3058
+ controlType: "select"
3059
+ },
3060
+ negativePrompt: {
3061
+ type: "string",
3062
+ label: "Negative Prompt",
3063
+ description: "Elements to avoid in the generated image",
3064
+ controlType: "textarea"
3065
+ },
3066
+ personGeneration: {
3067
+ type: "enum",
3068
+ label: "Person Generation",
3069
+ description: "Controls whether people can appear in images",
3070
+ enum: ["dont_allow", "allow_adult", "allow_all"],
3071
+ default: "allow_adult",
3072
+ controlType: "select"
3073
+ },
3074
+ safetyFilterLevel: {
3075
+ type: "enum",
3076
+ label: "Safety Filter",
3077
+ description: "Content safety filtering threshold",
3078
+ enum: ["block_none", "block_only_high", "block_medium_and_above", "block_low_and_above"],
3079
+ default: "block_medium_and_above",
3080
+ controlType: "select"
3081
+ },
3082
+ enhancePrompt: {
3083
+ type: "boolean",
3084
+ label: "Enhance Prompt",
3085
+ description: "Use LLM-based prompt rewriting for better quality",
3086
+ default: true,
3087
+ controlType: "checkbox"
3088
+ },
3089
+ seed: {
3090
+ type: "number",
3091
+ label: "Seed",
3092
+ description: "Random seed for reproducible generation (1-2147483647)",
3093
+ min: 1,
3094
+ max: 2147483647,
3095
+ controlType: "text"
3096
+ },
3097
+ addWatermark: {
3098
+ type: "boolean",
3099
+ label: "Add Watermark",
3100
+ description: "Add invisible SynthID watermark",
3101
+ default: true,
3102
+ controlType: "checkbox"
3103
+ },
3104
+ language: {
3105
+ type: "enum",
3106
+ label: "Prompt Language",
3107
+ description: "Language of the input prompt",
3108
+ enum: ["auto", "en", "zh", "zh-CN", "zh-TW", "hi", "ja", "ko", "pt", "es"],
3109
+ default: "en",
3110
+ controlType: "select"
3111
+ }
3112
+ }
3113
+ },
3114
+ pricing: {
3115
+ perImage: 0.04,
3116
+ currency: "USD"
3117
+ }
3118
+ },
3119
+ "imagen-4.0-ultra-generate-001": {
3120
+ name: "imagen-4.0-ultra-generate-001",
3121
+ displayName: "Imagen 4.0 Ultra",
3122
+ provider: Vendor.Google,
3123
+ description: "Google Imagen 4.0 Ultra - highest quality image generation",
3124
+ isActive: true,
3125
+ releaseDate: "2025-06-01",
3126
+ sources: {
3127
+ documentation: "https://ai.google.dev/gemini-api/docs/imagen",
3128
+ pricing: "https://ai.google.dev/pricing",
3129
+ lastVerified: "2026-01-25"
3130
+ },
3131
+ capabilities: {
3132
+ sizes: ["1024x1024"],
3133
+ aspectRatios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
3134
+ maxImagesPerRequest: 4,
3135
+ outputFormats: ["png", "jpeg"],
3136
+ features: {
3137
+ generation: true,
3138
+ editing: false,
3139
+ variations: false,
3140
+ styleControl: false,
3141
+ qualityControl: true,
3142
+ transparency: false,
3143
+ promptRevision: false
3144
+ },
3145
+ limits: { maxPromptLength: 480 },
3146
+ vendorOptions: {
3147
+ aspectRatio: {
3148
+ type: "enum",
3149
+ label: "Aspect Ratio",
3150
+ description: "Output image proportions",
3151
+ enum: ["1:1", "3:4", "4:3", "16:9", "9:16"],
3152
+ default: "1:1",
3153
+ controlType: "select"
3154
+ },
3155
+ sampleImageSize: {
3156
+ type: "enum",
3157
+ label: "Resolution",
3158
+ description: "Output image resolution",
3159
+ enum: ["1K", "2K"],
3160
+ default: "1K",
3161
+ controlType: "radio"
3162
+ },
3163
+ outputMimeType: {
3164
+ type: "enum",
3165
+ label: "Output Format",
3166
+ description: "Image file format",
3167
+ enum: ["image/png", "image/jpeg"],
3168
+ default: "image/png",
3169
+ controlType: "select"
3170
+ },
3171
+ negativePrompt: {
3172
+ type: "string",
3173
+ label: "Negative Prompt",
3174
+ description: "Elements to avoid in the generated image",
3175
+ controlType: "textarea"
3176
+ },
3177
+ personGeneration: {
3178
+ type: "enum",
3179
+ label: "Person Generation",
3180
+ description: "Controls whether people can appear in images",
3181
+ enum: ["dont_allow", "allow_adult", "allow_all"],
3182
+ default: "allow_adult",
3183
+ controlType: "select"
3184
+ },
3185
+ safetyFilterLevel: {
3186
+ type: "enum",
3187
+ label: "Safety Filter",
3188
+ description: "Content safety filtering threshold",
3189
+ enum: ["block_none", "block_only_high", "block_medium_and_above", "block_low_and_above"],
3190
+ default: "block_medium_and_above",
3191
+ controlType: "select"
3192
+ },
3193
+ enhancePrompt: {
3194
+ type: "boolean",
3195
+ label: "Enhance Prompt",
3196
+ description: "Use LLM-based prompt rewriting for better quality",
3197
+ default: true,
3198
+ controlType: "checkbox"
3199
+ },
3200
+ seed: {
3201
+ type: "number",
3202
+ label: "Seed",
3203
+ description: "Random seed for reproducible generation (1-2147483647)",
3204
+ min: 1,
3205
+ max: 2147483647,
3206
+ controlType: "text"
3207
+ },
3208
+ addWatermark: {
3209
+ type: "boolean",
3210
+ label: "Add Watermark",
3211
+ description: "Add invisible SynthID watermark",
3212
+ default: true,
3213
+ controlType: "checkbox"
3214
+ },
3215
+ language: {
3216
+ type: "enum",
3217
+ label: "Prompt Language",
3218
+ description: "Language of the input prompt",
3219
+ enum: ["auto", "en", "zh", "zh-CN", "zh-TW", "hi", "ja", "ko", "pt", "es"],
3220
+ default: "en",
3221
+ controlType: "select"
3222
+ }
3223
+ }
3224
+ },
3225
+ pricing: {
3226
+ perImage: 0.08,
3227
+ currency: "USD"
3228
+ }
3229
+ },
3230
+ "imagen-4.0-fast-generate-001": {
3231
+ name: "imagen-4.0-fast-generate-001",
3232
+ displayName: "Imagen 4.0 Fast",
3233
+ provider: Vendor.Google,
3234
+ description: "Google Imagen 4.0 Fast - optimized for speed",
3235
+ isActive: true,
3236
+ releaseDate: "2025-06-01",
3237
+ sources: {
3238
+ documentation: "https://ai.google.dev/gemini-api/docs/imagen",
3239
+ pricing: "https://ai.google.dev/pricing",
3240
+ lastVerified: "2026-01-25"
3241
+ },
3242
+ capabilities: {
3243
+ sizes: ["1024x1024"],
3244
+ aspectRatios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
3245
+ maxImagesPerRequest: 4,
3246
+ outputFormats: ["png", "jpeg"],
3247
+ features: {
3248
+ generation: true,
3249
+ editing: false,
3250
+ variations: false,
3251
+ styleControl: false,
3252
+ qualityControl: false,
3253
+ transparency: false,
3254
+ promptRevision: false
3255
+ },
3256
+ limits: { maxPromptLength: 480 },
3257
+ vendorOptions: {
3258
+ aspectRatio: {
3259
+ type: "enum",
3260
+ label: "Aspect Ratio",
3261
+ description: "Output image proportions",
3262
+ enum: ["1:1", "3:4", "4:3", "16:9", "9:16"],
3263
+ default: "1:1",
3264
+ controlType: "select"
3265
+ },
3266
+ sampleImageSize: {
3267
+ type: "enum",
3268
+ label: "Resolution",
3269
+ description: "Output image resolution",
3270
+ enum: ["1K", "2K"],
3271
+ default: "1K",
3272
+ controlType: "radio"
3273
+ },
3274
+ outputMimeType: {
3275
+ type: "enum",
3276
+ label: "Output Format",
3277
+ description: "Image file format",
3278
+ enum: ["image/png", "image/jpeg"],
3279
+ default: "image/png",
3280
+ controlType: "select"
3281
+ },
3282
+ negativePrompt: {
3283
+ type: "string",
3284
+ label: "Negative Prompt",
3285
+ description: "Elements to avoid in the generated image",
3286
+ controlType: "textarea"
3287
+ },
3288
+ personGeneration: {
3289
+ type: "enum",
3290
+ label: "Person Generation",
3291
+ description: "Controls whether people can appear in images",
3292
+ enum: ["dont_allow", "allow_adult", "allow_all"],
3293
+ default: "allow_adult",
3294
+ controlType: "select"
3295
+ },
3296
+ safetyFilterLevel: {
3297
+ type: "enum",
3298
+ label: "Safety Filter",
3299
+ description: "Content safety filtering threshold",
3300
+ enum: ["block_none", "block_only_high", "block_medium_and_above", "block_low_and_above"],
3301
+ default: "block_medium_and_above",
3302
+ controlType: "select"
3303
+ },
3304
+ enhancePrompt: {
3305
+ type: "boolean",
3306
+ label: "Enhance Prompt",
3307
+ description: "Use LLM-based prompt rewriting for better quality",
3308
+ default: true,
3309
+ controlType: "checkbox"
3310
+ },
3311
+ seed: {
3312
+ type: "number",
3313
+ label: "Seed",
3314
+ description: "Random seed for reproducible generation (1-2147483647)",
3315
+ min: 1,
3316
+ max: 2147483647,
3317
+ controlType: "text"
3318
+ },
3319
+ addWatermark: {
3320
+ type: "boolean",
3321
+ label: "Add Watermark",
3322
+ description: "Add invisible SynthID watermark",
3323
+ default: true,
3324
+ controlType: "checkbox"
3325
+ },
3326
+ language: {
3327
+ type: "enum",
3328
+ label: "Prompt Language",
3329
+ description: "Language of the input prompt",
3330
+ enum: ["auto", "en", "zh", "zh-CN", "zh-TW", "hi", "ja", "ko", "pt", "es"],
3331
+ default: "en",
3332
+ controlType: "select"
3333
+ }
3334
+ }
3335
+ },
3336
+ pricing: {
3337
+ perImage: 0.02,
3338
+ currency: "USD"
3339
+ }
3340
+ },
3341
+ // ======================== xAI Grok ========================
3342
+ "grok-imagine-image": {
3343
+ name: "grok-imagine-image",
3344
+ displayName: "Grok Imagine Image",
3345
+ provider: Vendor.Grok,
3346
+ description: "xAI Grok Imagine image generation with aspect ratio control and editing support",
3347
+ isActive: true,
3348
+ releaseDate: "2025-01-01",
3349
+ sources: {
3350
+ documentation: "https://docs.x.ai/docs/guides/image-generation",
3351
+ pricing: "https://docs.x.ai/docs/models",
3352
+ lastVerified: "2026-02-01"
3353
+ },
3354
+ capabilities: {
3355
+ sizes: ["1024x1024"],
3356
+ aspectRatios: ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3"],
3357
+ maxImagesPerRequest: 10,
3358
+ outputFormats: ["png", "jpeg"],
3359
+ features: {
3360
+ generation: true,
3361
+ editing: true,
3362
+ variations: false,
3363
+ styleControl: false,
3364
+ qualityControl: false,
3365
+ // quality not supported by xAI API
3366
+ transparency: false,
3367
+ promptRevision: true
3368
+ },
3369
+ limits: { maxPromptLength: 4096 },
3370
+ vendorOptions: {
3371
+ n: {
3372
+ type: "number",
3373
+ label: "Number of Images",
3374
+ description: "Number of images to generate (1-10)",
3375
+ min: 1,
3376
+ max: 10,
3377
+ default: 1,
3378
+ controlType: "slider"
3379
+ },
3380
+ response_format: {
3381
+ type: "enum",
3382
+ label: "Response Format",
3383
+ description: "Format of the returned image",
3384
+ enum: ["url", "b64_json"],
3385
+ default: "url",
3386
+ controlType: "radio"
3387
+ }
3388
+ }
3389
+ },
3390
+ pricing: {
3391
+ perImage: 0.02,
3392
+ currency: "USD"
3393
+ }
3394
+ },
3395
+ "grok-2-image-1212": {
3396
+ name: "grok-2-image-1212",
3397
+ displayName: "Grok 2 Image",
3398
+ provider: Vendor.Grok,
3399
+ description: "xAI Grok 2 image generation (text-only input, no editing)",
3400
+ isActive: true,
3401
+ releaseDate: "2024-12-12",
3402
+ sources: {
3403
+ documentation: "https://docs.x.ai/docs/guides/image-generation",
3404
+ pricing: "https://docs.x.ai/docs/models",
3405
+ lastVerified: "2026-02-01"
3406
+ },
3407
+ capabilities: {
3408
+ sizes: ["1024x1024"],
3409
+ aspectRatios: ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3"],
3410
+ maxImagesPerRequest: 10,
3411
+ outputFormats: ["png", "jpeg"],
3412
+ features: {
3413
+ generation: true,
3414
+ editing: false,
3415
+ variations: false,
3416
+ styleControl: false,
3417
+ qualityControl: false,
3418
+ // quality not supported by xAI API
3419
+ transparency: false,
3420
+ promptRevision: false
3421
+ },
3422
+ limits: { maxPromptLength: 4096 },
3423
+ vendorOptions: {
3424
+ n: {
3425
+ type: "number",
3426
+ label: "Number of Images",
3427
+ description: "Number of images to generate (1-10)",
3428
+ min: 1,
3429
+ max: 10,
3430
+ default: 1,
3431
+ controlType: "slider"
3432
+ },
3433
+ response_format: {
3434
+ type: "enum",
3435
+ label: "Response Format",
3436
+ description: "Format of the returned image",
3437
+ enum: ["url", "b64_json"],
3438
+ default: "url",
3439
+ controlType: "radio"
3440
+ }
3441
+ }
3442
+ },
3443
+ pricing: {
3444
+ perImage: 0.07,
3445
+ currency: "USD"
3446
+ }
3447
+ }
3448
+ };
3449
+ var helpers = createRegistryHelpers(IMAGE_MODEL_REGISTRY);
3450
+ var getImageModelInfo = helpers.getInfo;
3451
+
3452
+ // src/capabilities/images/ImageGeneration.ts
3453
+ var ImageGeneration = class _ImageGeneration {
3454
+ provider;
3455
+ connector;
3456
+ defaultModel;
3457
+ constructor(connector) {
3458
+ this.connector = connector;
3459
+ this.provider = createImageProvider(connector);
3460
+ this.defaultModel = this.getDefaultModel();
3461
+ }
3462
+ /**
3463
+ * Create an ImageGeneration instance
3464
+ */
3465
+ static create(options) {
3466
+ const connector = typeof options.connector === "string" ? Connector.get(options.connector) : options.connector;
3467
+ if (!connector) {
3468
+ throw new Error(`Connector not found: ${options.connector}`);
3469
+ }
3470
+ return new _ImageGeneration(connector);
3471
+ }
3472
+ /**
3473
+ * Generate images from a text prompt
3474
+ */
3475
+ async generate(options) {
3476
+ const fullOptions = {
3477
+ model: options.model || this.defaultModel,
3478
+ prompt: options.prompt,
3479
+ size: options.size,
3480
+ quality: options.quality,
3481
+ style: options.style,
3482
+ n: options.n,
3483
+ response_format: options.response_format || "b64_json"
3484
+ };
3485
+ return this.provider.generateImage(fullOptions);
3486
+ }
3487
+ /**
3488
+ * Edit an existing image
3489
+ * Note: Not all models/vendors support this
3490
+ */
3491
+ async edit(options) {
3492
+ if (!this.provider.editImage) {
3493
+ throw new Error(`Image editing not supported by ${this.provider.name}`);
3494
+ }
3495
+ const fullOptions = {
3496
+ ...options,
3497
+ model: options.model || this.getEditModel()
3498
+ };
3499
+ return this.provider.editImage(fullOptions);
3500
+ }
3501
+ /**
3502
+ * Create variations of an existing image
3503
+ * Note: Only DALL-E 2 supports this
3504
+ */
3505
+ async createVariation(options) {
3506
+ if (!this.provider.createVariation) {
3507
+ throw new Error(`Image variations not supported by ${this.provider.name}`);
3508
+ }
3509
+ const fullOptions = {
3510
+ ...options,
3511
+ model: options.model || "dall-e-2"
3512
+ // Only DALL-E 2 supports variations
3513
+ };
3514
+ return this.provider.createVariation(fullOptions);
3515
+ }
3516
+ /**
3517
+ * List available models for this provider
3518
+ */
3519
+ async listModels() {
3520
+ if (this.provider.listModels) {
3521
+ return this.provider.listModels();
3522
+ }
3523
+ const vendor = this.connector.vendor;
3524
+ if (vendor && IMAGE_MODELS[vendor]) {
3525
+ return Object.values(IMAGE_MODELS[vendor]);
3526
+ }
3527
+ return [];
3528
+ }
3529
+ /**
3530
+ * Get information about a specific model
3531
+ */
3532
+ getModelInfo(modelName) {
3533
+ return getImageModelInfo(modelName);
3534
+ }
3535
+ /**
3536
+ * Get the underlying provider
3537
+ */
3538
+ getProvider() {
3539
+ return this.provider;
3540
+ }
3541
+ /**
3542
+ * Get the current connector
3543
+ */
3544
+ getConnector() {
3545
+ return this.connector;
3546
+ }
3547
+ /**
3548
+ * Get the default model for this vendor
3549
+ */
3550
+ getDefaultModel() {
3551
+ const vendor = this.connector.vendor;
3552
+ switch (vendor) {
3553
+ case Vendor.OpenAI:
3554
+ return IMAGE_MODELS[Vendor.OpenAI].DALL_E_3;
3555
+ case Vendor.Google:
3556
+ return IMAGE_MODELS[Vendor.Google].IMAGEN_4_GENERATE;
3557
+ case Vendor.Grok:
3558
+ return IMAGE_MODELS[Vendor.Grok].GROK_IMAGINE_IMAGE;
3559
+ default:
3560
+ throw new Error(`No default image model for vendor: ${vendor}`);
3561
+ }
3562
+ }
3563
+ /**
3564
+ * Get the default edit model for this vendor
3565
+ */
3566
+ getEditModel() {
3567
+ const vendor = this.connector.vendor;
3568
+ switch (vendor) {
3569
+ case Vendor.OpenAI:
3570
+ return IMAGE_MODELS[Vendor.OpenAI].GPT_IMAGE_1;
3571
+ case Vendor.Google:
3572
+ return IMAGE_MODELS[Vendor.Google].IMAGEN_4_GENERATE;
3573
+ case Vendor.Grok:
3574
+ return IMAGE_MODELS[Vendor.Grok].GROK_IMAGINE_IMAGE;
3575
+ default:
3576
+ throw new Error(`No edit model for vendor: ${vendor}`);
3577
+ }
3578
+ }
3579
+ };
3580
+
3581
+ exports.ImageGeneration = ImageGeneration;
3582
+ //# sourceMappingURL=index.cjs.map
3583
+ //# sourceMappingURL=index.cjs.map