snow-flow 2.8.9 → 2.9.4

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,288 @@
1
+ "use strict";
2
+ /**
3
+ * Smart ML Data Fetcher
4
+ * Handles intelligent data fetching for ML training with batching and field discovery
5
+ * Prevents token limit errors and optimizes data retrieval
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.MLDataFetcher = void 0;
9
+ const logger_js_1 = require("./logger.js");
10
+ const logger = new logger_js_1.Logger('MLDataFetcher');
11
+ class MLDataFetcher {
12
+ constructor(operationsMCP) {
13
+ this.operationsMCP = operationsMCP;
14
+ }
15
+ /**
16
+ * Smart fetch with automatic field discovery and batching
17
+ */
18
+ async smartFetch(options) {
19
+ const { table, query = '', totalSamples = 1000, batchSize = 100, fields, discoverFields = true, includeContent = true } = options;
20
+ logger.info(`🧠 Smart ML data fetch for ${table} - Target: ${totalSamples} samples`);
21
+ // Step 1: Get total count
22
+ const countResult = await this.getRecordCount(table, query);
23
+ logger.info(`📊 Total available records: ${countResult}`);
24
+ // Step 2: Discover fields if needed
25
+ let fieldsToFetch = fields;
26
+ if (!fieldsToFetch && discoverFields) {
27
+ const discovery = await this.discoverFields(table, query);
28
+ fieldsToFetch = discovery.recommendedFields;
29
+ logger.info(`🔍 Discovered ${discovery.allFields.length} fields, using ${fieldsToFetch.length} for ML`);
30
+ }
31
+ // Step 3: Calculate optimal batching strategy
32
+ const actualTotal = Math.min(totalSamples, countResult);
33
+ const optimalBatchSize = this.calculateOptimalBatchSize(actualTotal, batchSize, fieldsToFetch?.length || 10);
34
+ const numBatches = Math.ceil(actualTotal / optimalBatchSize);
35
+ logger.info(`📦 Fetching ${actualTotal} records in ${numBatches} batches of ${optimalBatchSize}`);
36
+ // Step 4: Fetch data in batches
37
+ const allData = [];
38
+ for (let batch = 0; batch < numBatches; batch++) {
39
+ const offset = batch * optimalBatchSize;
40
+ const limit = Math.min(optimalBatchSize, actualTotal - offset);
41
+ logger.info(` Batch ${batch + 1}/${numBatches}: Fetching ${limit} records (offset: ${offset})`);
42
+ try {
43
+ const batchData = await this.fetchBatch(table, query, limit, offset, fieldsToFetch, includeContent);
44
+ allData.push(...batchData);
45
+ // Small delay between batches to avoid overwhelming the API
46
+ if (batch < numBatches - 1) {
47
+ await new Promise(resolve => setTimeout(resolve, 200)); // Increased from 100ms to 200ms
48
+ }
49
+ }
50
+ catch (error) {
51
+ if (error.message?.includes('exceeds maximum allowed tokens')) {
52
+ // Reduce batch size and retry
53
+ logger.warn(`⚠️ Token limit hit, reducing batch size and retrying...`);
54
+ const smallerBatchSize = Math.floor(optimalBatchSize / 2);
55
+ return this.smartFetch({
56
+ ...options,
57
+ batchSize: smallerBatchSize
58
+ });
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ return {
64
+ data: allData,
65
+ totalFetched: allData.length,
66
+ batchesProcessed: numBatches,
67
+ fields: fieldsToFetch || []
68
+ };
69
+ }
70
+ /**
71
+ * Get total count of records matching query
72
+ */
73
+ async getRecordCount(table, query) {
74
+ try {
75
+ const result = await this.operationsMCP.handleTool('snow_query_table', {
76
+ table,
77
+ query,
78
+ limit: 1,
79
+ include_content: false // Count only, no data
80
+ });
81
+ // Extract count from result
82
+ if (result?.content?.[0]?.text) {
83
+ const text = result.content[0].text;
84
+ const match = text.match(/Found (\d+) .* records/);
85
+ if (match) {
86
+ return parseInt(match[1], 10);
87
+ }
88
+ }
89
+ return 0;
90
+ }
91
+ catch (error) {
92
+ logger.error('Failed to get record count:', error);
93
+ return 0;
94
+ }
95
+ }
96
+ /**
97
+ * Discover available fields by sampling a few records
98
+ */
99
+ async discoverFields(table, query) {
100
+ logger.info('🔍 Discovering fields from sample records...');
101
+ try {
102
+ // Fetch just 3 records with all fields to discover schema
103
+ const result = await this.operationsMCP.handleTool('snow_query_table', {
104
+ table,
105
+ query,
106
+ limit: 3,
107
+ include_content: true
108
+ // No fields specified = get all fields
109
+ });
110
+ const sampleData = this.extractDataFromResult(result);
111
+ if (sampleData.length === 0) {
112
+ logger.warn('No sample data available for field discovery');
113
+ return {
114
+ allFields: [],
115
+ recommendedFields: this.getDefaultFields(table),
116
+ sampleData: []
117
+ };
118
+ }
119
+ // Extract all field names from sample
120
+ const allFields = Object.keys(sampleData[0] || {});
121
+ // Recommend fields for ML (exclude system fields and large text fields)
122
+ const recommendedFields = this.selectMLFields(allFields, sampleData, table);
123
+ return {
124
+ allFields,
125
+ recommendedFields,
126
+ sampleData
127
+ };
128
+ }
129
+ catch (error) {
130
+ logger.error('Field discovery failed:', error);
131
+ return {
132
+ allFields: [],
133
+ recommendedFields: this.getDefaultFields(table),
134
+ sampleData: []
135
+ };
136
+ }
137
+ }
138
+ /**
139
+ * Fetch a single batch of data
140
+ */
141
+ async fetchBatch(table, query, limit, offset, fields, includeContent = true) {
142
+ try {
143
+ // Build query with offset
144
+ const offsetQuery = query ? `${query}^ORDERBY${offset}` : `ORDERBY${offset}`;
145
+ const result = await this.operationsMCP.handleTool('snow_query_table', {
146
+ table,
147
+ query: offsetQuery,
148
+ limit,
149
+ fields,
150
+ include_content: includeContent
151
+ });
152
+ return this.extractDataFromResult(result);
153
+ }
154
+ catch (error) {
155
+ logger.error(`Failed to fetch batch (offset: ${offset}, limit: ${limit}):`, error);
156
+ return [];
157
+ }
158
+ }
159
+ /**
160
+ * Extract data from MCP tool result
161
+ */
162
+ extractDataFromResult(result) {
163
+ if (!result?.content?.[0]?.text) {
164
+ return [];
165
+ }
166
+ try {
167
+ const text = result.content[0].text;
168
+ // Try to parse as JSON first
169
+ if (text.includes('[') && text.includes(']')) {
170
+ const jsonMatch = text.match(/\[[\s\S]*\]/);
171
+ if (jsonMatch) {
172
+ return JSON.parse(jsonMatch[0]);
173
+ }
174
+ }
175
+ // Try to extract from formatted output
176
+ const lines = text.split('\n');
177
+ const data = [];
178
+ let currentRecord = null;
179
+ for (const line of lines) {
180
+ if (line.includes('number:') || line.includes('sys_id:')) {
181
+ if (currentRecord) {
182
+ data.push(currentRecord);
183
+ }
184
+ currentRecord = {};
185
+ }
186
+ if (currentRecord && line.includes(':')) {
187
+ const [key, ...valueParts] = line.split(':');
188
+ const cleanKey = key.trim().replace(/^[-\s]+/, '');
189
+ const value = valueParts.join(':').trim();
190
+ if (cleanKey && value) {
191
+ currentRecord[cleanKey] = value;
192
+ }
193
+ }
194
+ }
195
+ if (currentRecord && Object.keys(currentRecord).length > 0) {
196
+ data.push(currentRecord);
197
+ }
198
+ return data;
199
+ }
200
+ catch (error) {
201
+ logger.error('Failed to extract data from result:', error);
202
+ return [];
203
+ }
204
+ }
205
+ /**
206
+ * Calculate optimal batch size based on data characteristics
207
+ */
208
+ calculateOptimalBatchSize(totalRecords, requestedBatchSize, numFields) {
209
+ // Estimate tokens per record (rough approximation)
210
+ const avgTokensPerField = 10; // Conservative estimate
211
+ const tokensPerRecord = numFields * avgTokensPerField;
212
+ const maxTokensPerBatch = 20000; // Leave buffer below 25000 limit
213
+ // Calculate max records per batch based on token limit
214
+ const maxRecordsPerBatch = Math.floor(maxTokensPerBatch / tokensPerRecord);
215
+ // Use the smaller of requested batch size and calculated max
216
+ const optimalSize = Math.min(requestedBatchSize, maxRecordsPerBatch);
217
+ // Ensure at least 10 records per batch but not more than total
218
+ return Math.max(10, Math.min(optimalSize, totalRecords));
219
+ }
220
+ /**
221
+ * Select appropriate fields for ML training
222
+ */
223
+ selectMLFields(allFields, sampleData, table) {
224
+ const excluded = new Set([
225
+ 'sys_id', 'sys_created_on', 'sys_created_by', 'sys_updated_on', 'sys_updated_by',
226
+ 'sys_mod_count', 'sys_tags', 'sys_package', 'sys_policy', 'sys_scope',
227
+ 'sys_domain', 'sys_domain_path', 'sys_class_name'
228
+ ]);
229
+ const mlFields = allFields.filter(field => {
230
+ // Exclude system fields
231
+ if (excluded.has(field))
232
+ return false;
233
+ // Check if field has useful data in samples
234
+ const hasData = sampleData.some(record => {
235
+ const value = record[field];
236
+ return value && value !== 'null' && value !== '';
237
+ });
238
+ return hasData;
239
+ });
240
+ // Always include key fields for the table type
241
+ const keyFields = this.getKeyFields(table);
242
+ const combinedFields = [...new Set([...keyFields, ...mlFields])];
243
+ // Limit to 20 most relevant fields to avoid token issues
244
+ return combinedFields.slice(0, 20);
245
+ }
246
+ /**
247
+ * Get default fields for a table type
248
+ */
249
+ getDefaultFields(table) {
250
+ const fieldMap = {
251
+ incident: [
252
+ 'number', 'short_description', 'description', 'category', 'subcategory',
253
+ 'priority', 'urgency', 'impact', 'state', 'assignment_group',
254
+ 'assigned_to', 'caller_id', 'opened_at', 'resolved_at'
255
+ ],
256
+ change_request: [
257
+ 'number', 'short_description', 'description', 'type', 'category',
258
+ 'priority', 'risk', 'impact', 'state', 'assignment_group',
259
+ 'assigned_to', 'requested_by', 'start_date', 'end_date'
260
+ ],
261
+ problem: [
262
+ 'number', 'short_description', 'description', 'category', 'subcategory',
263
+ 'priority', 'urgency', 'impact', 'state', 'assignment_group',
264
+ 'assigned_to', 'opened_at', 'known_error'
265
+ ],
266
+ sc_request: [
267
+ 'number', 'short_description', 'description', 'request_state', 'approval',
268
+ 'requested_for', 'requested_by', 'assignment_group', 'assigned_to',
269
+ 'opened_at', 'closed_at'
270
+ ]
271
+ };
272
+ return fieldMap[table] || ['number', 'short_description', 'state', 'priority'];
273
+ }
274
+ /**
275
+ * Get key fields that should always be included
276
+ */
277
+ getKeyFields(table) {
278
+ const keyFieldMap = {
279
+ incident: ['number', 'short_description', 'category', 'priority', 'state'],
280
+ change_request: ['number', 'short_description', 'type', 'risk', 'state'],
281
+ problem: ['number', 'short_description', 'category', 'priority', 'state'],
282
+ sc_request: ['number', 'short_description', 'request_state', 'approval']
283
+ };
284
+ return keyFieldMap[table] || ['number', 'short_description', 'state'];
285
+ }
286
+ }
287
+ exports.MLDataFetcher = MLDataFetcher;
288
+ //# sourceMappingURL=ml-data-fetcher.js.map
@@ -13,10 +13,10 @@ const axios_1 = __importDefault(require("axios"));
13
13
  const https_1 = __importDefault(require("https"));
14
14
  const snow_oauth_1 = require("./snow-oauth");
15
15
  const action_type_cache_1 = require("./action-type-cache");
16
- const snow_flow_config_js_1 = require("../config/snow-flow-config.js");
17
16
  const widget_template_generator_js_1 = require("./widget-template-generator.js");
18
17
  const logger_1 = require("./logger");
19
18
  const unified_auth_store_js_1 = require("./unified-auth-store.js");
19
+ const timeout_manager_js_1 = require("./timeout-manager.js");
20
20
  class ServiceNowClient {
21
21
  constructor() {
22
22
  this.credentials = null;
@@ -68,8 +68,10 @@ class ServiceNowClient {
68
68
  maxVersion: 'TLSv1.3',
69
69
  minVersion: 'TLSv1.2'
70
70
  });
71
+ // Use intelligent timeout based on operation type (default to TABLE_QUERY)
72
+ const defaultTimeout = (0, timeout_manager_js_1.getTimeoutConfig)(timeout_manager_js_1.OperationType.TABLE_QUERY).baseTimeout;
71
73
  this.client = axios_1.default.create({
72
- timeout: snow_flow_config_js_1.snowFlowConfig.servicenow.timeout,
74
+ timeout: parseInt(process.env.SNOW_API_TIMEOUT || String(defaultTimeout)),
73
75
  headers: {
74
76
  'Content-Type': 'application/json',
75
77
  'Accept': 'application/json'
@@ -979,21 +981,33 @@ class ServiceNowClient {
979
981
  async searchRecords(table, query, limit = 10) {
980
982
  try {
981
983
  await this.ensureAuthenticated();
982
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
983
- params: {
984
- sysparm_query: query,
985
- sysparm_limit: limit
986
- }
984
+ // Detect operation type for intelligent timeout
985
+ const operationType = (0, timeout_manager_js_1.detectOperationType)({
986
+ action: 'query',
987
+ table,
988
+ limit
987
989
  });
990
+ // Use retry wrapper with intelligent timeout
991
+ const result = await (0, timeout_manager_js_1.withRetry)(async () => {
992
+ const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
993
+ params: {
994
+ sysparm_query: query,
995
+ sysparm_limit: limit
996
+ },
997
+ // Override timeout for this specific request
998
+ timeout: (0, timeout_manager_js_1.getTimeoutConfig)(operationType).baseTimeout
999
+ });
1000
+ return response;
1001
+ }, operationType, `Search ${table} (${limit} records)`);
988
1002
  return {
989
1003
  success: true,
990
1004
  data: {
991
- result: response.data.result || []
1005
+ result: result.data.result || []
992
1006
  }
993
1007
  };
994
1008
  }
995
1009
  catch (error) {
996
- console.error(`Failed to search records in ${table}:`, error);
1010
+ this.logger.error(`Failed to search records in ${table}:`, error);
997
1011
  return {
998
1012
  success: false,
999
1013
  error: error instanceof Error ? error.message : String(error)
@@ -1006,22 +1020,34 @@ class ServiceNowClient {
1006
1020
  async searchRecordsWithOffset(table, query, limit = 10, offset = 0) {
1007
1021
  try {
1008
1022
  await this.ensureAuthenticated();
1009
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
1010
- params: {
1011
- sysparm_query: query,
1012
- sysparm_limit: limit,
1013
- sysparm_offset: offset
1014
- }
1023
+ // Detect operation type for intelligent timeout
1024
+ const operationType = (0, timeout_manager_js_1.detectOperationType)({
1025
+ action: 'query',
1026
+ table,
1027
+ limit
1015
1028
  });
1029
+ // Use retry wrapper with intelligent timeout
1030
+ const result = await (0, timeout_manager_js_1.withRetry)(async () => {
1031
+ const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
1032
+ params: {
1033
+ sysparm_query: query,
1034
+ sysparm_limit: limit,
1035
+ sysparm_offset: offset
1036
+ },
1037
+ // Override timeout for this specific request
1038
+ timeout: (0, timeout_manager_js_1.getTimeoutConfig)(operationType).baseTimeout
1039
+ });
1040
+ return response;
1041
+ }, operationType, `Search ${table} with offset ${offset}`);
1016
1042
  return {
1017
1043
  success: true,
1018
1044
  data: {
1019
- result: response.data.result || []
1045
+ result: result.data.result || []
1020
1046
  }
1021
1047
  };
1022
1048
  }
1023
1049
  catch (error) {
1024
- console.error(`Failed to search records in ${table} with offset ${offset}:`, error);
1050
+ this.logger.error(`Failed to search records in ${table} with offset ${offset}:`, error);
1025
1051
  return {
1026
1052
  success: false,
1027
1053
  error: error instanceof Error ? error.message : String(error)
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Timeout Manager for Snow-Flow
3
+ * Provides intelligent timeout configuration and retry logic
4
+ */
5
+ /**
6
+ * Operation types with their specific timeout requirements
7
+ */
8
+ export declare enum OperationType {
9
+ SIMPLE_QUERY = "simple_query",
10
+ SINGLE_RECORD = "single_record",
11
+ HEALTH_CHECK = "health_check",
12
+ TABLE_QUERY = "table_query",
13
+ CREATE_RECORD = "create_record",
14
+ UPDATE_RECORD = "update_record",
15
+ DELETE_RECORD = "delete_record",
16
+ BATCH_OPERATION = "batch_operation",
17
+ BULK_QUERY = "bulk_query",
18
+ DEPLOYMENT = "deployment",
19
+ WORKFLOW_EXECUTION = "workflow_execution",
20
+ ML_TRAINING = "ml_training",
21
+ ML_BATCH_FETCH = "ml_batch_fetch",
22
+ ML_PREDICTION = "ml_prediction",
23
+ LARGE_EXPORT = "large_export",
24
+ MIGRATION = "migration",
25
+ FULL_SYNC = "full_sync"
26
+ }
27
+ /**
28
+ * Timeout configuration with intelligent defaults
29
+ */
30
+ export interface TimeoutConfig {
31
+ baseTimeout: number;
32
+ maxTimeout: number;
33
+ retryCount: number;
34
+ backoffMultiplier: number;
35
+ jitterRange: number;
36
+ }
37
+ /**
38
+ * Get timeout configuration for operation type
39
+ */
40
+ export declare function getTimeoutConfig(operationType: OperationType): TimeoutConfig;
41
+ /**
42
+ * Calculate timeout with exponential backoff
43
+ */
44
+ export declare function calculateTimeout(config: TimeoutConfig, attemptNumber: number): number;
45
+ /**
46
+ * Retry wrapper with exponential backoff
47
+ */
48
+ export declare function withRetry<T>(operation: () => Promise<T>, operationType: OperationType, operationName?: string): Promise<T>;
49
+ /**
50
+ * Detect operation type from context
51
+ */
52
+ export declare function detectOperationType(context: {
53
+ tool?: string;
54
+ table?: string;
55
+ action?: string;
56
+ limit?: number;
57
+ }): OperationType;
58
+ /**
59
+ * Get human-readable timeout description
60
+ */
61
+ export declare function getTimeoutDescription(operationType: OperationType): string;
62
+ //# sourceMappingURL=timeout-manager.d.ts.map