@flutchai/flutch-sdk 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -53,6 +53,7 @@ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
53
53
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
54
54
  var net__namespace = /*#__PURE__*/_interopNamespace(net);
55
55
  var mongoose__default = /*#__PURE__*/_interopDefault(mongoose);
56
+ var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
56
57
  var LangGraph__namespace = /*#__PURE__*/_interopNamespace(LangGraph);
57
58
  var axios2__default = /*#__PURE__*/_interopDefault(axios2);
58
59
 
@@ -1365,10 +1366,10 @@ exports.AbstractGraphBuilder = class AbstractGraphBuilder {
1365
1366
  return null;
1366
1367
  }
1367
1368
  try {
1368
- const fs2 = await import('fs/promises');
1369
- const path3 = await import('path');
1370
- const manifestFullPath = path3.resolve(this.manifestPath);
1371
- const manifestContent = await fs2.readFile(manifestFullPath, "utf-8");
1369
+ const fs3 = await import('fs/promises');
1370
+ const path4 = await import('path');
1371
+ const manifestFullPath = path4.resolve(this.manifestPath);
1372
+ const manifestContent = await fs3.readFile(manifestFullPath, "utf-8");
1372
1373
  const manifest = JSON.parse(manifestContent);
1373
1374
  this.manifest = manifest;
1374
1375
  return manifest;
@@ -1387,10 +1388,10 @@ exports.AbstractGraphBuilder = class AbstractGraphBuilder {
1387
1388
  return null;
1388
1389
  }
1389
1390
  try {
1390
- const fs2 = __require("fs");
1391
- const path3 = __require("path");
1392
- const manifestFullPath = path3.resolve(this.manifestPath);
1393
- const manifestContent = fs2.readFileSync(manifestFullPath, "utf-8");
1391
+ const fs3 = __require("fs");
1392
+ const path4 = __require("path");
1393
+ const manifestFullPath = path4.resolve(this.manifestPath);
1394
+ const manifestContent = fs3.readFileSync(manifestFullPath, "utf-8");
1394
1395
  const manifest = JSON.parse(manifestContent);
1395
1396
  this.manifest = manifest;
1396
1397
  return manifest;
@@ -1430,12 +1431,12 @@ exports.AbstractGraphBuilder = class AbstractGraphBuilder {
1430
1431
  let configSchema = null;
1431
1432
  if (versionConfig.configSchemaPath) {
1432
1433
  try {
1433
- const fs2 = await import('fs/promises');
1434
+ const fs3 = await import('fs/promises');
1434
1435
  const schemaPath = path2__namespace.resolve(
1435
1436
  process.cwd(),
1436
1437
  versionConfig.configSchemaPath
1437
1438
  );
1438
- const schemaContent = await fs2.readFile(schemaPath, "utf-8");
1439
+ const schemaContent = await fs3.readFile(schemaPath, "utf-8");
1439
1440
  const schemaData = JSON.parse(schemaContent);
1440
1441
  configSchema = schemaData.schema;
1441
1442
  } catch (error) {
@@ -7325,6 +7326,205 @@ exports.StaticDiscovery = class StaticDiscovery {
7325
7326
  exports.StaticDiscovery = __decorateClass([
7326
7327
  common.Injectable()
7327
7328
  ], exports.StaticDiscovery);
7329
+ var ALGORITHM = "aes-256-cbc";
7330
+ var IV_LENGTH = 16;
7331
+ function encryptTokens(tokens, key) {
7332
+ const iv = crypto__namespace.randomBytes(IV_LENGTH);
7333
+ const cipher = crypto__namespace.createCipheriv(
7334
+ ALGORITHM,
7335
+ Buffer.from(key),
7336
+ iv
7337
+ );
7338
+ const json = JSON.stringify(tokens);
7339
+ let encrypted = cipher.update(json, "utf8", "hex");
7340
+ encrypted += cipher.final("hex");
7341
+ return iv.toString("hex") + ":" + encrypted;
7342
+ }
7343
+ function decryptTokens(encrypted, key) {
7344
+ const separatorIndex = encrypted.indexOf(":");
7345
+ if (separatorIndex === -1) {
7346
+ throw new Error("Invalid encrypted token format");
7347
+ }
7348
+ const ivHex = encrypted.substring(0, separatorIndex);
7349
+ const encryptedData = encrypted.substring(separatorIndex + 1);
7350
+ const iv = Buffer.from(ivHex, "hex");
7351
+ const decipher = crypto__namespace.createDecipheriv(
7352
+ ALGORITHM,
7353
+ Buffer.from(key),
7354
+ iv
7355
+ );
7356
+ let decrypted = decipher.update(encryptedData, "hex", "utf8");
7357
+ decrypted += decipher.final("utf8");
7358
+ return JSON.parse(decrypted);
7359
+ }
7360
+
7361
+ // src/oauth/oauth-token.manager.ts
7362
+ var EXPIRY_BUFFER_MS = 6e4;
7363
+ var OAuthTokenManager = class {
7364
+ store;
7365
+ encryptionKey;
7366
+ cache = /* @__PURE__ */ new Map();
7367
+ constructor(options) {
7368
+ if (!options.encryptionKey || options.encryptionKey.length < 32) {
7369
+ throw new Error(
7370
+ "OAUTH_ENCRYPTION_KEY must be at least 32 characters for AES-256-CBC"
7371
+ );
7372
+ }
7373
+ this.store = options.store;
7374
+ this.encryptionKey = options.encryptionKey;
7375
+ }
7376
+ /**
7377
+ * Get a fresh access token for the given provider.
7378
+ * Returns from cache → store → refresh flow (in that order).
7379
+ */
7380
+ async getAccessToken(config) {
7381
+ const cached = this.cache.get(config.provider);
7382
+ if (cached && cached.expiresAt > Date.now() + EXPIRY_BUFFER_MS) {
7383
+ return cached.token;
7384
+ }
7385
+ const encrypted = await this.store.get(config.provider);
7386
+ if (!encrypted) {
7387
+ throw new Error(
7388
+ `No OAuth tokens found for "${config.provider}". Complete the OAuth consent flow first and call saveTokens().`
7389
+ );
7390
+ }
7391
+ const tokens = decryptTokens(encrypted, this.encryptionKey);
7392
+ if (tokens.expiresAt > Date.now() + EXPIRY_BUFFER_MS) {
7393
+ this.setCache(config.provider, tokens.accessToken, tokens.expiresAt);
7394
+ return tokens.accessToken;
7395
+ }
7396
+ const refreshed = await this.refreshAccessToken(config, tokens.refreshToken);
7397
+ await this.persistTokens(config.provider, refreshed);
7398
+ return refreshed.accessToken;
7399
+ }
7400
+ /**
7401
+ * Store tokens after the initial OAuth consent flow.
7402
+ * Call this once after the user completes the OAuth redirect.
7403
+ */
7404
+ async saveTokens(provider, tokens) {
7405
+ await this.persistTokens(provider, tokens);
7406
+ }
7407
+ /**
7408
+ * Remove all tokens for a provider.
7409
+ */
7410
+ async revokeTokens(provider) {
7411
+ await this.store.delete(provider);
7412
+ this.cache.delete(provider);
7413
+ }
7414
+ /**
7415
+ * Check if tokens exist for a provider (without decrypting).
7416
+ */
7417
+ async hasTokens(provider) {
7418
+ const encrypted = await this.store.get(provider);
7419
+ return encrypted !== null;
7420
+ }
7421
+ async refreshAccessToken(config, refreshToken) {
7422
+ try {
7423
+ const response = await axios2__default.default.post(
7424
+ config.tokenUrl,
7425
+ new URLSearchParams({
7426
+ grant_type: "refresh_token",
7427
+ client_id: config.clientId,
7428
+ client_secret: config.clientSecret,
7429
+ refresh_token: refreshToken
7430
+ }).toString(),
7431
+ {
7432
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
7433
+ timeout: 1e4
7434
+ }
7435
+ );
7436
+ const data = response.data;
7437
+ return {
7438
+ accessToken: data.access_token,
7439
+ // Some providers rotate refresh tokens; keep the old one if not rotated
7440
+ refreshToken: data.refresh_token || refreshToken,
7441
+ expiresAt: Date.now() + (data.expires_in || 3600) * 1e3
7442
+ };
7443
+ } catch (error) {
7444
+ const status = error?.response?.status;
7445
+ const body = error?.response?.data;
7446
+ throw new Error(
7447
+ `OAuth refresh failed for "${config.provider}": ${status || "network error"} ${JSON.stringify(body) || error.message}`
7448
+ );
7449
+ }
7450
+ }
7451
+ async persistTokens(provider, tokens) {
7452
+ const encrypted = encryptTokens(tokens, this.encryptionKey);
7453
+ await this.store.save(provider, encrypted);
7454
+ this.setCache(provider, tokens.accessToken, tokens.expiresAt);
7455
+ }
7456
+ setCache(provider, token, expiresAt) {
7457
+ this.cache.set(provider, { token, expiresAt });
7458
+ }
7459
+ };
7460
+ var FileTokenStore = class {
7461
+ constructor(filePath) {
7462
+ this.filePath = filePath;
7463
+ }
7464
+ async get(provider) {
7465
+ const data = this.readFile();
7466
+ return data[provider] ?? null;
7467
+ }
7468
+ async save(provider, encrypted) {
7469
+ const data = this.readFile();
7470
+ data[provider] = encrypted;
7471
+ this.writeFile(data);
7472
+ }
7473
+ async delete(provider) {
7474
+ const data = this.readFile();
7475
+ delete data[provider];
7476
+ this.writeFile(data);
7477
+ }
7478
+ readFile() {
7479
+ try {
7480
+ if (fs__namespace.existsSync(this.filePath)) {
7481
+ const content = fs__namespace.readFileSync(this.filePath, "utf8");
7482
+ return JSON.parse(content);
7483
+ }
7484
+ } catch {
7485
+ }
7486
+ return {};
7487
+ }
7488
+ writeFile(data) {
7489
+ const dir = path2__namespace.dirname(this.filePath);
7490
+ if (!fs__namespace.existsSync(dir)) {
7491
+ fs__namespace.mkdirSync(dir, { recursive: true });
7492
+ }
7493
+ fs__namespace.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf8");
7494
+ }
7495
+ };
7496
+
7497
+ // src/oauth/stores/mongo-token.store.ts
7498
+ var DEFAULT_COLLECTION = "oauth_tokens";
7499
+ var MongoTokenStore = class {
7500
+ constructor(db, collectionName) {
7501
+ this.db = db;
7502
+ this.collectionName = collectionName ?? DEFAULT_COLLECTION;
7503
+ }
7504
+ collectionName;
7505
+ initialized = false;
7506
+ async get(provider) {
7507
+ await this.ensureIndex();
7508
+ const doc = await this.db.collection(this.collectionName).findOne({ provider });
7509
+ return doc?.encrypted ?? null;
7510
+ }
7511
+ async save(provider, encrypted) {
7512
+ await this.ensureIndex();
7513
+ await this.db.collection(this.collectionName).updateOne(
7514
+ { provider },
7515
+ { $set: { provider, encrypted, updatedAt: /* @__PURE__ */ new Date() } },
7516
+ { upsert: true }
7517
+ );
7518
+ }
7519
+ async delete(provider) {
7520
+ await this.db.collection(this.collectionName).deleteOne({ provider });
7521
+ }
7522
+ async ensureIndex() {
7523
+ if (this.initialized) return;
7524
+ await this.db.collection(this.collectionName).createIndex({ provider: 1 }, { unique: true });
7525
+ this.initialized = true;
7526
+ }
7527
+ };
7328
7528
 
7329
7529
  exports.AttachmentType = AttachmentType;
7330
7530
  exports.BaseGraphServiceController = exports.GraphController;
@@ -7337,6 +7537,7 @@ exports.ChatFeature = ChatFeature;
7337
7537
  exports.DEFAULT_ATTACHMENT_THRESHOLD = DEFAULT_ATTACHMENT_THRESHOLD;
7338
7538
  exports.DEFAULT_TRACER_OPTIONS = DEFAULT_TRACER_OPTIONS;
7339
7539
  exports.Endpoint = Endpoint;
7540
+ exports.FileTokenStore = FileTokenStore;
7340
7541
  exports.GraphEngineType = GraphEngineType;
7341
7542
  exports.GraphManifestSchema = GraphManifestSchema;
7342
7543
  exports.GraphManifestValidator = GraphManifestValidator;
@@ -7347,6 +7548,8 @@ exports.McpToolFilter = McpToolFilter;
7347
7548
  exports.ModelInitializer = ModelInitializer;
7348
7549
  exports.ModelProvider = ModelProvider;
7349
7550
  exports.ModelType = ModelType;
7551
+ exports.MongoTokenStore = MongoTokenStore;
7552
+ exports.OAuthTokenManager = OAuthTokenManager;
7350
7553
  exports.RetrieverSearchType = RetrieverSearchType;
7351
7554
  exports.StreamChannel = StreamChannel;
7352
7555
  exports.UIEndpoint = UIEndpoint;
@@ -7362,7 +7565,9 @@ exports.createEndpointDescriptors = createEndpointDescriptors;
7362
7565
  exports.createGraphAttachment = createGraphAttachment;
7363
7566
  exports.createMongoClientAdapter = createMongoClientAdapter;
7364
7567
  exports.createStaticMessage = createStaticMessage;
7568
+ exports.decryptTokens = decryptTokens;
7365
7569
  exports.dispatchAttachments = dispatchAttachments;
7570
+ exports.encryptTokens = encryptTokens;
7366
7571
  exports.executeToolWithAttachments = executeToolWithAttachments;
7367
7572
  exports.findCallbackMethod = findCallbackMethod;
7368
7573
  exports.findEndpointMethod = findEndpointMethod;