@resolveio/server-lib 20.14.6 → 20.14.8

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.
@@ -84,12 +84,15 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
84
84
  };
85
85
  Object.defineProperty(exports, "__esModule", { value: true });
86
86
  exports.loadCronJobMethods = loadCronJobMethods;
87
+ var axios_1 = require("axios");
87
88
  var Excel = require("exceljs");
88
89
  var moment = require("moment-timezone");
89
90
  var simpl_schema_1 = require("simpl-schema");
90
91
  var cron_job_collection_1 = require("../collections/cron-job.collection");
91
92
  var file_collection_1 = require("../collections/file.collection");
93
+ var openai_usage_ledger_collection_1 = require("../collections/openai-usage-ledger.collection");
92
94
  var report_builder_report_collection_1 = require("../collections/report-builder-report.collection");
95
+ var resolveio_server_app_1 = require("../resolveio-server-app");
93
96
  var common_1 = require("../util/common");
94
97
  function getFilterTargets(fieldPath, report) {
95
98
  if (!fieldPath) {
@@ -789,6 +792,103 @@ function applyClientGroupSortToData(results, report, sorts) {
789
792
  };
790
793
  sortGroupLevel(results, 0);
791
794
  }
795
+ var OPENAI_USAGE_BILLING_ENDPOINT = 'https://backend.resolveio.com/api/openai-usage/report';
796
+ var OPENAI_USAGE_DEFAULT_LOOKBACK_DAYS = 30;
797
+ var OPENAI_USAGE_DEFAULT_BATCH_SIZE = 500;
798
+ var OPENAI_USAGE_DEFAULT_OVERLAP_MINUTES = 10;
799
+ var toNumberSetting = function (value, fallback, allowZero) {
800
+ if (allowZero === void 0) { allowZero = false; }
801
+ var parsed = Number(value);
802
+ if (!Number.isFinite(parsed)) {
803
+ return fallback;
804
+ }
805
+ if (allowZero && parsed === 0) {
806
+ return 0;
807
+ }
808
+ return parsed > 0 ? Math.floor(parsed) : fallback;
809
+ };
810
+ var normalizeString = function (value) {
811
+ if (typeof value !== 'string') {
812
+ return '';
813
+ }
814
+ return value.trim();
815
+ };
816
+ var normalizeIdentifier = function (value) {
817
+ if (value === undefined || value === null) {
818
+ return '';
819
+ }
820
+ return String(value).trim();
821
+ };
822
+ var normalizeDate = function (value) {
823
+ if (!value) {
824
+ return null;
825
+ }
826
+ var date = value instanceof Date ? value : new Date(value);
827
+ if (Number.isNaN(date.getTime())) {
828
+ return null;
829
+ }
830
+ return date;
831
+ };
832
+ var resolveOpenAIUsageSyncConfig = function () {
833
+ var _a, _b;
834
+ var config = ((_a = resolveio_server_app_1.ResolveIOServer.getServerConfig) === null || _a === void 0 ? void 0 : _a.call(resolveio_server_app_1.ResolveIOServer)) || {};
835
+ var endpoint = normalizeString(config['OPENAI_USAGE_BILLING_URL']
836
+ || config['OPENAI_USAGE_REPORT_URL']
837
+ || process.env.OPENAI_USAGE_BILLING_URL
838
+ || process.env.OPENAI_USAGE_REPORT_URL) || OPENAI_USAGE_BILLING_ENDPOINT;
839
+ var apiKey = normalizeString(config['OPENAI_USAGE_INGEST_KEY']
840
+ || config['OPENAI_USAGE_API_KEY']
841
+ || process.env.OPENAI_USAGE_INGEST_KEY
842
+ || process.env.OPENAI_USAGE_API_KEY
843
+ || process.env.OPENAI_USAGE_REPORT_KEY);
844
+ var batchSize = toNumberSetting(config['OPENAI_USAGE_SYNC_BATCH_SIZE'] || process.env.OPENAI_USAGE_SYNC_BATCH_SIZE, OPENAI_USAGE_DEFAULT_BATCH_SIZE);
845
+ var lookbackDays = toNumberSetting(config['OPENAI_USAGE_SYNC_LOOKBACK_DAYS'] || process.env.OPENAI_USAGE_SYNC_LOOKBACK_DAYS, OPENAI_USAGE_DEFAULT_LOOKBACK_DAYS);
846
+ var overlapMinutes = toNumberSetting(config['OPENAI_USAGE_SYNC_OVERLAP_MINUTES'] || process.env.OPENAI_USAGE_SYNC_OVERLAP_MINUTES, OPENAI_USAGE_DEFAULT_OVERLAP_MINUTES, true);
847
+ var enabledRaw = (_b = config['OPENAI_USAGE_SYNC_ENABLED']) !== null && _b !== void 0 ? _b : process.env.OPENAI_USAGE_SYNC_ENABLED;
848
+ var enabled = typeof enabledRaw === 'string'
849
+ ? enabledRaw.trim().toLowerCase() !== 'false' && enabledRaw.trim() !== '0'
850
+ : enabledRaw !== false;
851
+ return { config: config, endpoint: endpoint, apiKey: apiKey, batchSize: batchSize, lookbackDays: lookbackDays, overlapMinutes: overlapMinutes, enabled: enabled };
852
+ };
853
+ var buildOpenAIUsageQuery = function (cursorAt, cursorId) {
854
+ var base = {
855
+ billable: { $ne: false }
856
+ };
857
+ if (!cursorId) {
858
+ base.timestamp = { $gte: cursorAt };
859
+ return base;
860
+ }
861
+ base.$or = [
862
+ { timestamp: { $gt: cursorAt } },
863
+ { timestamp: cursorAt, _id: { $gt: cursorId } }
864
+ ];
865
+ return base;
866
+ };
867
+ var normalizeUsagePayloadEntry = function (entry) {
868
+ if (!entry) {
869
+ return null;
870
+ }
871
+ var timestamp = normalizeDate(entry.timestamp);
872
+ var idClient = normalizeIdentifier(entry.id_client);
873
+ if (!timestamp || !idClient) {
874
+ return null;
875
+ }
876
+ var id = normalizeIdentifier(entry._id) || normalizeIdentifier(entry.id) || '';
877
+ return {
878
+ _id: id || undefined,
879
+ id_client: idClient,
880
+ timestamp: timestamp,
881
+ model: normalizeString(entry.model) || 'unknown',
882
+ input_tokens: Number(entry.input_tokens || 0),
883
+ output_tokens: Number(entry.output_tokens || 0),
884
+ total_tokens: Number(entry.total_tokens || 0),
885
+ cost_estimate: Number(entry.cost_estimate || 0),
886
+ billable: entry.billable !== false,
887
+ category: normalizeString(entry.category),
888
+ id_request: normalizeIdentifier(entry.id_request),
889
+ id_conversation: normalizeIdentifier(entry.id_conversation)
890
+ };
891
+ };
792
892
  function loadCronJobMethods(methodManager) {
793
893
  methodManager.methods({
794
894
  cronEmailMergedDocsCleanUp: {
@@ -836,6 +936,111 @@ function loadCronJobMethods(methodManager) {
836
936
  });
837
937
  }
838
938
  },
939
+ cronOpenAIUsageBillingSync: {
940
+ function: function () {
941
+ return __awaiter(this, void 0, void 0, function () {
942
+ var _a, config, endpoint, apiKey, batchSize, lookbackDays, overlapMinutes, enabled, rootUrl, jobDoc, runData, lastSyncedAt, lastSyncedId, lookbackMs, cursorAt, cursorId, totalSent, cursorMoved, source, i, query, entries, payloadEntries, lastEntry;
943
+ var _b;
944
+ return __generator(this, function (_c) {
945
+ switch (_c.label) {
946
+ case 0:
947
+ _a = resolveOpenAIUsageSyncConfig(), config = _a.config, endpoint = _a.endpoint, apiKey = _a.apiKey, batchSize = _a.batchSize, lookbackDays = _a.lookbackDays, overlapMinutes = _a.overlapMinutes, enabled = _a.enabled;
948
+ if (!enabled) {
949
+ return [2 /*return*/, true];
950
+ }
951
+ rootUrl = normalizeString(config['ROOT_URL']);
952
+ if (rootUrl === 'https://resolveio.com' || rootUrl === 'http://localhost:4200') {
953
+ return [2 /*return*/, true];
954
+ }
955
+ if (!endpoint || !apiKey) {
956
+ console.warn('[OpenAI Usage Sync] Missing endpoint or API key.');
957
+ return [2 /*return*/, false];
958
+ }
959
+ return [4 /*yield*/, cron_job_collection_1.CronJobs.findOne({ name: 'OpenAI Usage Billing Sync' })];
960
+ case 1:
961
+ jobDoc = _c.sent();
962
+ runData = jobDoc === null || jobDoc === void 0 ? void 0 : jobDoc.method_run_data;
963
+ lastSyncedAt = normalizeDate(runData === null || runData === void 0 ? void 0 : runData.last_synced_at);
964
+ lastSyncedId = normalizeString(runData === null || runData === void 0 ? void 0 : runData.last_synced_id);
965
+ lookbackMs = lookbackDays * 24 * 60 * 60 * 1000;
966
+ cursorAt = lastSyncedAt || new Date(Date.now() - lookbackMs);
967
+ if (lastSyncedAt && overlapMinutes > 0) {
968
+ cursorAt = new Date(cursorAt.getTime() - overlapMinutes * 60 * 1000);
969
+ }
970
+ cursorId = lastSyncedId;
971
+ totalSent = 0;
972
+ cursorMoved = false;
973
+ source = {
974
+ clientSlug: (_b = resolveio_server_app_1.ResolveIOServer.getClientName) === null || _b === void 0 ? void 0 : _b.call(resolveio_server_app_1.ResolveIOServer),
975
+ clientName: normalizeString(config['CLIENT_NAME']),
976
+ rootUrl: rootUrl,
977
+ reportedAt: new Date(),
978
+ appVersion: process.env.APP_VERSION
979
+ };
980
+ i = 0;
981
+ _c.label = 2;
982
+ case 2:
983
+ if (!(i < 1000)) return [3 /*break*/, 7];
984
+ query = buildOpenAIUsageQuery(cursorAt, cursorId);
985
+ return [4 /*yield*/, openai_usage_ledger_collection_1.OpenAIUsageLedger.find(query, {
986
+ sort: { timestamp: 1, _id: 1 },
987
+ limit: batchSize
988
+ })];
989
+ case 3:
990
+ entries = _c.sent();
991
+ if (!entries.length) {
992
+ return [3 /*break*/, 7];
993
+ }
994
+ payloadEntries = entries
995
+ .map(function (entry) { return normalizeUsagePayloadEntry(entry); })
996
+ .filter(Boolean);
997
+ if (!payloadEntries.length) return [3 /*break*/, 5];
998
+ return [4 /*yield*/, axios_1.default.post(endpoint, { entries: payloadEntries, source: source }, {
999
+ headers: {
1000
+ 'Content-Type': 'application/json',
1001
+ 'X-ResolveIO-OpenAI-Usage-Key': apiKey,
1002
+ 'X-OpenAI-Usage-Key': apiKey,
1003
+ 'X-API-Key': apiKey
1004
+ },
1005
+ timeout: 20000
1006
+ })];
1007
+ case 4:
1008
+ _c.sent();
1009
+ totalSent += payloadEntries.length;
1010
+ _c.label = 5;
1011
+ case 5:
1012
+ lastEntry = entries[entries.length - 1];
1013
+ if (lastEntry) {
1014
+ cursorAt = normalizeDate(lastEntry.timestamp) || cursorAt;
1015
+ cursorId = normalizeString(lastEntry._id) || cursorId;
1016
+ cursorMoved = true;
1017
+ }
1018
+ if (entries.length < batchSize) {
1019
+ return [3 /*break*/, 7];
1020
+ }
1021
+ _c.label = 6;
1022
+ case 6:
1023
+ i++;
1024
+ return [3 /*break*/, 2];
1025
+ case 7:
1026
+ if (!cursorMoved) return [3 /*break*/, 9];
1027
+ return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ name: 'OpenAI Usage Billing Sync' }, {
1028
+ $set: {
1029
+ 'method_run_data.last_synced_at': cursorAt,
1030
+ 'method_run_data.last_synced_id': cursorId,
1031
+ 'method_run_data.last_sent_at': new Date(),
1032
+ 'method_run_data.last_sent_count': totalSent
1033
+ }
1034
+ })];
1035
+ case 8:
1036
+ _c.sent();
1037
+ _c.label = 9;
1038
+ case 9: return [2 /*return*/, true];
1039
+ }
1040
+ });
1041
+ });
1042
+ }
1043
+ },
839
1044
  reportbuilderCronJob: {
840
1045
  check: new simpl_schema_1.default({
841
1046
  data: {