@smythos/sre 1.7.20 → 1.7.40
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.js +114 -76
- package/dist/index.js.map +1 -1
- package/dist/types/Components/DataSourceIndexer.class.d.ts +4 -12
- package/dist/types/Components/GenAILLM.class.d.ts +5 -5
- package/dist/types/Components/index.d.ts +3 -3
- package/dist/types/index.d.ts +3 -3
- package/dist/types/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.d.ts +1 -0
- package/dist/types/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.d.ts +11 -4
- package/dist/types/subsystems/IO/VectorDB.service/embed/index.d.ts +5 -0
- package/dist/types/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.d.ts +35 -0
- package/dist/types/subsystems/Security/Account.service/AccountConnector.d.ts +2 -2
- package/dist/types/subsystems/Security/Vault.service/connectors/SecretsManager.class.d.ts +2 -3
- package/dist/types/types/VectorDB.types.d.ts +4 -0
- package/dist/types/utils/string.utils.d.ts +1 -0
- package/package.json +3 -3
- package/src/Components/APIEndpoint.class.ts +1 -6
- package/src/Components/Component.class.ts +14 -1
- package/src/Components/DataSourceIndexer.class.ts +148 -34
- package/src/Components/GenAILLM.class.ts +21 -11
- package/src/Components/RAG/DataSourceCleaner.class.ts +178 -0
- package/src/Components/RAG/DataSourceComponent.class.ts +111 -0
- package/src/Components/RAG/DataSourceIndexer.class.ts +254 -0
- package/src/Components/{DataSourceLookup.class.ts → RAG/DataSourceLookup.class.ts} +92 -3
- package/src/Components/ServerlessCode.class.ts +1 -4
- package/src/Components/index.ts +3 -3
- package/src/helpers/S3Cache.helper.ts +2 -1
- package/src/index.ts +212 -212
- package/src/index.ts.bak +212 -212
- package/src/subsystems/IO/NKV.service/connectors/NKVRedis.class.ts +3 -1
- package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +145 -19
- package/src/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.ts +56 -22
- package/src/subsystems/IO/VectorDB.service/embed/GoogleEmbedding.ts +1 -0
- package/src/subsystems/IO/VectorDB.service/embed/OpenAIEmbedding.ts +2 -1
- package/src/subsystems/IO/VectorDB.service/embed/index.ts +18 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +35 -10
- package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +12 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +4 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +13 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +17 -5
- package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +18 -3
- package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +14 -5
- package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +6 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.ts +5 -5
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +8 -3
- package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +9 -8
- package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +92 -1
- package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.ts +32 -6
- package/src/subsystems/Security/Account.service/AccountConnector.ts +3 -3
- package/src/subsystems/Security/Vault.service/connectors/SecretsManager.class.ts +8 -63
- package/src/types/VectorDB.types.ts +4 -0
- package/src/utils/array.utils.ts +11 -0
- package/src/utils/base64.utils.ts +1 -1
- package/src/utils/string.utils.ts +3 -192
- package/src/Components/DataSourceCleaner.class.ts +0 -92
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JSONContent } from '@sre/helpers/JsonContent.helper';
|
|
2
2
|
import { LLMConnector } from '../LLMConnector';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
|
-
import { APIKeySource, ILLMRequestFuncParams, TLLMChatResponse, TLLMPreparedParams } from '@sre/types/LLM.types';
|
|
4
|
+
import { APIKeySource, ILLMRequestFuncParams, TLLMChatResponse, TLLMEvent, TLLMPreparedParams } from '@sre/types/LLM.types';
|
|
5
5
|
import { Logger } from '@sre/helpers/Log.helper';
|
|
6
6
|
import { delay } from '@sre/utils/index';
|
|
7
7
|
import { hookAsync } from '@sre/Core/HookService';
|
|
@@ -54,13 +54,13 @@ export class EchoConnector extends LLMConnector {
|
|
|
54
54
|
const isLastChunk = i === chunks.length - 1;
|
|
55
55
|
// Add space between chunks except for the last one to avoid trailing space in file URLs
|
|
56
56
|
const delta = { content: chunks[i] + (isLastChunk ? '' : ' ') };
|
|
57
|
-
emitter.emit(
|
|
58
|
-
emitter.emit(
|
|
57
|
+
emitter.emit(TLLMEvent.Data, delta);
|
|
58
|
+
emitter.emit(TLLMEvent.Content, delta.content);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// Emit end event after all chunks are processed
|
|
62
62
|
setTimeout(() => {
|
|
63
|
-
emitter.emit(
|
|
63
|
+
emitter.emit(TLLMEvent.End, [], [], 'stop'); // Empty arrays for toolsData and usage_data, with finishReason
|
|
64
64
|
}, 100);
|
|
65
65
|
})();
|
|
66
66
|
|
|
@@ -186,9 +186,11 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
186
186
|
(async () => {
|
|
187
187
|
try {
|
|
188
188
|
for await (const chunk of stream) {
|
|
189
|
+
emitter.emit(TLLMEvent.Data, chunk);
|
|
190
|
+
|
|
189
191
|
const chunkText = chunk.text ?? '';
|
|
190
192
|
if (chunkText) {
|
|
191
|
-
emitter.emit(
|
|
193
|
+
emitter.emit(TLLMEvent.Content, chunkText);
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
const toolCalls = chunk.candidates?.[0]?.content?.parts?.filter((part) => part.functionCall);
|
|
@@ -213,21 +215,28 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
|
|
218
|
+
const finishReason = 'stop'; // GoogleAI doesn't provide finishReason in streaming
|
|
219
|
+
const reportedUsage: any[] = [];
|
|
220
|
+
|
|
216
221
|
if (usage) {
|
|
217
|
-
this.reportUsage(usage, {
|
|
222
|
+
const reported = this.reportUsage(usage, {
|
|
218
223
|
modelEntryName: context.modelEntryName,
|
|
219
224
|
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
220
225
|
agentId: context.agentId,
|
|
221
226
|
teamId: context.teamId,
|
|
222
227
|
});
|
|
228
|
+
reportedUsage.push(reported);
|
|
223
229
|
}
|
|
224
230
|
|
|
231
|
+
// Note: GoogleAI stream doesn't provide explicit finish reasons
|
|
232
|
+
// If we had a non-stop finish reason, we would emit Interrupted here
|
|
233
|
+
|
|
225
234
|
setTimeout(() => {
|
|
226
|
-
emitter.emit(
|
|
235
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
227
236
|
}, 100);
|
|
228
237
|
} catch (error) {
|
|
229
238
|
logger.error(`streamRequest ${this.name}`, error, acRequest.candidate);
|
|
230
|
-
emitter.emit(
|
|
239
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
231
240
|
}
|
|
232
241
|
})();
|
|
233
242
|
|
|
@@ -108,6 +108,7 @@ export class GroqConnector extends LLMConnector {
|
|
|
108
108
|
const stream = await groq.chat.completions.create({ ...body, stream: true, stream_options: { include_usage: true } });
|
|
109
109
|
|
|
110
110
|
let toolsData: ToolData[] = [];
|
|
111
|
+
let finishReason = 'stop';
|
|
111
112
|
|
|
112
113
|
(async () => {
|
|
113
114
|
for await (const chunk of stream as any) {
|
|
@@ -117,10 +118,10 @@ export class GroqConnector extends LLMConnector {
|
|
|
117
118
|
if (usage) {
|
|
118
119
|
usage_data.push(usage);
|
|
119
120
|
}
|
|
120
|
-
emitter.emit(
|
|
121
|
+
emitter.emit(TLLMEvent.Data, delta);
|
|
121
122
|
|
|
122
123
|
if (delta?.content) {
|
|
123
|
-
emitter.emit(
|
|
124
|
+
emitter.emit(TLLMEvent.Content, delta.content);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
if (delta?.tool_calls) {
|
|
@@ -139,24 +140,35 @@ export class GroqConnector extends LLMConnector {
|
|
|
139
140
|
}
|
|
140
141
|
});
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
// Capture finish reason
|
|
145
|
+
if (chunk.choices[0]?.finish_reason) {
|
|
146
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
147
|
+
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
if (toolsData.length > 0) {
|
|
145
151
|
emitter.emit(TLLMEvent.ToolInfo, toolsData);
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
const reportedUsage: any[] = [];
|
|
148
155
|
usage_data.forEach((usage) => {
|
|
149
|
-
|
|
150
|
-
this.reportUsage(usage, {
|
|
156
|
+
const reported = this.reportUsage(usage, {
|
|
151
157
|
modelEntryName: context.modelEntryName,
|
|
152
158
|
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
153
159
|
agentId: context.agentId,
|
|
154
160
|
teamId: context.teamId,
|
|
155
161
|
});
|
|
162
|
+
reportedUsage.push(reported);
|
|
156
163
|
});
|
|
157
164
|
|
|
165
|
+
// Emit interrupted event if finishReason is not 'stop'
|
|
166
|
+
if (finishReason !== 'stop') {
|
|
167
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
168
|
+
}
|
|
169
|
+
|
|
158
170
|
setTimeout(() => {
|
|
159
|
-
emitter.emit(
|
|
171
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
160
172
|
}, 100);
|
|
161
173
|
})();
|
|
162
174
|
|
|
@@ -138,14 +138,17 @@ export class OllamaConnector extends LLMConnector {
|
|
|
138
138
|
|
|
139
139
|
let toolsData: ToolData[] = [];
|
|
140
140
|
let fullContent = '';
|
|
141
|
+
let finishReason = 'stop';
|
|
141
142
|
|
|
142
143
|
(async () => {
|
|
143
144
|
for await (const chunk of stream) {
|
|
145
|
+
emitter.emit(TLLMEvent.Data, chunk);
|
|
146
|
+
|
|
144
147
|
// Emit content deltas
|
|
145
148
|
if (chunk.message?.content) {
|
|
146
149
|
const content = chunk.message.content;
|
|
147
150
|
fullContent += content;
|
|
148
|
-
emitter.emit(
|
|
151
|
+
emitter.emit(TLLMEvent.Content, content);
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
// Handle tool calls accumulation
|
|
@@ -181,6 +184,11 @@ export class OllamaConnector extends LLMConnector {
|
|
|
181
184
|
};
|
|
182
185
|
usage_data.push(usage);
|
|
183
186
|
}
|
|
187
|
+
|
|
188
|
+
// Capture finish reason from Ollama's done_reason
|
|
189
|
+
if (chunk.done_reason) {
|
|
190
|
+
finishReason = chunk.done_reason;
|
|
191
|
+
}
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
// Emit tool info if tools were requested
|
|
@@ -189,18 +197,25 @@ export class OllamaConnector extends LLMConnector {
|
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
// Report usage
|
|
200
|
+
const reportedUsage: any[] = [];
|
|
192
201
|
usage_data.forEach((usage) => {
|
|
193
|
-
this.reportUsage(usage, {
|
|
202
|
+
const reported = this.reportUsage(usage, {
|
|
194
203
|
modelEntryName: context.modelEntryName,
|
|
195
204
|
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
196
205
|
agentId: context.agentId,
|
|
197
206
|
teamId: context.teamId,
|
|
198
207
|
});
|
|
208
|
+
reportedUsage.push(reported);
|
|
199
209
|
});
|
|
200
210
|
|
|
211
|
+
// Emit interrupted event if finishReason is not 'stop'
|
|
212
|
+
if (finishReason !== 'stop') {
|
|
213
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
214
|
+
}
|
|
215
|
+
|
|
201
216
|
// Final end event
|
|
202
217
|
setTimeout(() => {
|
|
203
|
-
emitter.emit(
|
|
218
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
204
219
|
}, 100);
|
|
205
220
|
})();
|
|
206
221
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
TLLMChatResponse,
|
|
15
15
|
ILLMRequestContext,
|
|
16
16
|
TLLMPreparedParams,
|
|
17
|
+
TLLMEvent,
|
|
17
18
|
} from '@sre/types/LLM.types';
|
|
18
19
|
import { LLMHelper } from '@sre/LLMManager/LLM.helper';
|
|
19
20
|
|
|
@@ -97,6 +98,8 @@ export class PerplexityConnector extends LLMConnector {
|
|
|
97
98
|
//fallback to chatRequest
|
|
98
99
|
const emitter = new EventEmitter();
|
|
99
100
|
|
|
101
|
+
// TODO: need to implement proper streaming for Perplexity
|
|
102
|
+
|
|
100
103
|
setTimeout(() => {
|
|
101
104
|
try {
|
|
102
105
|
logger.debug(`streamRequest ${this.name}`, acRequest.candidate);
|
|
@@ -105,17 +108,23 @@ export class PerplexityConnector extends LLMConnector {
|
|
|
105
108
|
const finishReason = respose.finishReason;
|
|
106
109
|
const usage = respose.usage;
|
|
107
110
|
|
|
108
|
-
emitter.emit(
|
|
109
|
-
emitter.emit(
|
|
110
|
-
|
|
111
|
+
emitter.emit(TLLMEvent.Data, respose);
|
|
112
|
+
emitter.emit(TLLMEvent.Content, respose.content);
|
|
113
|
+
|
|
114
|
+
// Only emit Interrupted if finishReason is not 'stop'
|
|
115
|
+
if (finishReason !== 'stop') {
|
|
116
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
emitter.emit(TLLMEvent.End, [], [usage], finishReason);
|
|
111
120
|
})
|
|
112
121
|
.catch((error) => {
|
|
113
|
-
emitter.emit(
|
|
122
|
+
emitter.emit(TLLMEvent.Error, error.message || error.toString());
|
|
114
123
|
});
|
|
115
124
|
//emitter.emit('finishReason', respose.finishReason);
|
|
116
125
|
} catch (error) {
|
|
117
126
|
logger.error(`streamRequest ${this.name}`, error, acRequest.candidate);
|
|
118
|
-
emitter.emit(
|
|
127
|
+
emitter.emit(TLLMEvent.Error, error.message || error.toString());
|
|
119
128
|
}
|
|
120
129
|
}, 100);
|
|
121
130
|
|
|
@@ -138,12 +138,14 @@ export class VertexAIConnector extends LLMConnector {
|
|
|
138
138
|
for await (const chunk of streamResult.stream) {
|
|
139
139
|
const chunkText = chunk.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
140
140
|
if (chunkText) {
|
|
141
|
-
emitter.emit(
|
|
141
|
+
emitter.emit(TLLMEvent.Content, chunkText);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const aggregatedResponse = await streamResult.response;
|
|
146
146
|
|
|
147
|
+
emitter.emit(TLLMEvent.Data, aggregatedResponse);
|
|
148
|
+
|
|
147
149
|
// Check for function calls in the final response (like Anthropic does)
|
|
148
150
|
const functionCalls = aggregatedResponse.candidates?.[0]?.content?.parts?.filter((part) => part.functionCall);
|
|
149
151
|
if (functionCalls && functionCalls.length > 0) {
|
|
@@ -176,15 +178,15 @@ export class VertexAIConnector extends LLMConnector {
|
|
|
176
178
|
const finishReason = (aggregatedResponse.candidates?.[0]?.finishReason || 'stop').toLowerCase();
|
|
177
179
|
|
|
178
180
|
if (finishReason !== 'stop') {
|
|
179
|
-
emitter.emit(
|
|
181
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
setTimeout(() => {
|
|
183
|
-
emitter.emit(
|
|
185
|
+
emitter.emit(TLLMEvent.End, toolsData, usageData, finishReason);
|
|
184
186
|
}, 100);
|
|
185
187
|
} catch (error) {
|
|
186
188
|
logger.error(`streamRequest ${this.name}`, error, acRequest.candidate);
|
|
187
|
-
emitter.emit(
|
|
189
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
188
190
|
}
|
|
189
191
|
}, 100);
|
|
190
192
|
|
|
@@ -73,7 +73,7 @@ export class ChatCompletionsApiInterface extends OpenAIApiInterface {
|
|
|
73
73
|
// Step 3: Emit final events
|
|
74
74
|
this.emitFinalEvents(emitter, finalToolsData, reportedUsage, finishReason);
|
|
75
75
|
} catch (error) {
|
|
76
|
-
emitter.emit(
|
|
76
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
77
77
|
}
|
|
78
78
|
})();
|
|
79
79
|
|
|
@@ -267,11 +267,11 @@ export class ChatCompletionsApiInterface extends OpenAIApiInterface {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
// Emit data event for delta
|
|
270
|
-
emitter.emit(
|
|
270
|
+
emitter.emit(TLLMEvent.Data, delta);
|
|
271
271
|
|
|
272
272
|
// Handle content deltas
|
|
273
273
|
if (!delta?.tool_calls && delta?.content) {
|
|
274
|
-
emitter.emit(
|
|
274
|
+
emitter.emit(TLLMEvent.Content, delta?.content, delta?.role);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
// Handle tool calls
|
|
@@ -350,12 +350,12 @@ export class ChatCompletionsApiInterface extends OpenAIApiInterface {
|
|
|
350
350
|
|
|
351
351
|
// Emit interrupted event if finishReason is not 'stop'
|
|
352
352
|
if (finishReason !== 'stop') {
|
|
353
|
-
emitter.emit(
|
|
353
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
// Emit end event with setImmediate to ensure proper event ordering
|
|
357
357
|
setImmediate(() => {
|
|
358
|
-
emitter.emit(
|
|
358
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
359
359
|
});
|
|
360
360
|
}
|
|
361
361
|
|
|
@@ -119,7 +119,7 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
119
119
|
// Step 3: Emit final events
|
|
120
120
|
this.emitFinalEvents(emitter, finalToolsData, reportedUsage, finishReason);
|
|
121
121
|
} catch (error) {
|
|
122
|
-
emitter.emit(
|
|
122
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
123
123
|
}
|
|
124
124
|
})();
|
|
125
125
|
|
|
@@ -362,8 +362,10 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
362
362
|
role: 'assistant',
|
|
363
363
|
content: part.delta,
|
|
364
364
|
};
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
|
|
366
|
+
// TODO: we have inconsistency for data event with chat completions API, we need to check and fix it
|
|
367
|
+
emitter.emit(TLLMEvent.Data, deltaMsg);
|
|
368
|
+
emitter.emit(TLLMEvent.Content, part.delta, 'assistant');
|
|
367
369
|
}
|
|
368
370
|
} catch (error) {
|
|
369
371
|
console.warn('Error handling output text delta:', error);
|
|
@@ -412,6 +414,7 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
412
414
|
}
|
|
413
415
|
|
|
414
416
|
if (addingNew) {
|
|
417
|
+
// TODO: Check whether this event is being used.
|
|
415
418
|
emitter.emit('tool_call_started', {
|
|
416
419
|
id: callId,
|
|
417
420
|
name: functionName || '',
|
|
@@ -458,6 +461,7 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
458
461
|
}
|
|
459
462
|
|
|
460
463
|
const entry = existingIndex === -1 ? updated[finalIndex] : updated[finalIndex];
|
|
464
|
+
// TODO: Check whether this event is being used.
|
|
461
465
|
emitter.emit('tool_call_progress', {
|
|
462
466
|
id: entry.callId || itemId,
|
|
463
467
|
name: entry.name,
|
|
@@ -489,6 +493,7 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
489
493
|
const updated = toolsData.map((t, idx) => (idx === toolIndex ? { ...t, arguments: finalArguments } : t));
|
|
490
494
|
|
|
491
495
|
const updatedEntry = updated[toolIndex];
|
|
496
|
+
// TODO: Check whether this event is being used.
|
|
492
497
|
emitter.emit('tool_call_completed', {
|
|
493
498
|
id: updatedEntry.callId || itemId,
|
|
494
499
|
name: updatedEntry.name,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ILLMRequestFuncParams,
|
|
15
15
|
TLLMChatResponse,
|
|
16
16
|
ILLMRequestContext,
|
|
17
|
+
TLLMEvent,
|
|
17
18
|
} from '@sre/types/LLM.types';
|
|
18
19
|
import { LLMHelper } from '@sre/LLMManager/LLM.helper';
|
|
19
20
|
|
|
@@ -202,10 +203,10 @@ export class xAIConnector extends LLMConnector {
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
if (delta) {
|
|
205
|
-
emitter.emit(
|
|
206
|
+
emitter.emit(TLLMEvent.Data, delta);
|
|
206
207
|
|
|
207
208
|
if (delta.content) {
|
|
208
|
-
emitter.emit(
|
|
209
|
+
emitter.emit(TLLMEvent.Content, delta.content, delta.role);
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
if (delta.tool_calls) {
|
|
@@ -238,11 +239,11 @@ export class xAIConnector extends LLMConnector {
|
|
|
238
239
|
if (citations && citations.length > 0) {
|
|
239
240
|
const citationsText = '\n\n**Sources:**\n' + citations.map((url, index) => `${index + 1}. ${url}`).join('\n');
|
|
240
241
|
|
|
241
|
-
emitter.emit(
|
|
242
|
+
emitter.emit(TLLMEvent.Content, citationsText, 'assistant');
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
if (toolsData.length > 0) {
|
|
245
|
-
emitter.emit(
|
|
246
|
+
emitter.emit(TLLMEvent.ToolInfo, toolsData);
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
// Report usage if available
|
|
@@ -257,20 +258,20 @@ export class xAIConnector extends LLMConnector {
|
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
if (finishReason !== 'stop') {
|
|
260
|
-
emitter.emit(
|
|
261
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
setTimeout(() => {
|
|
264
|
-
emitter.emit(
|
|
265
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
265
266
|
}, 100);
|
|
266
267
|
});
|
|
267
268
|
|
|
268
269
|
response.data.on('error', (error) => {
|
|
269
|
-
emitter.emit(
|
|
270
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
270
271
|
});
|
|
271
272
|
} catch (error) {
|
|
272
273
|
logger.error(`streamRequest ${this.name}`, error, acRequest.candidate);
|
|
273
|
-
emitter.emit(
|
|
274
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
return emitter;
|
package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts
CHANGED
|
@@ -214,6 +214,93 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
214
214
|
return this.isValidSingleModel(data);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Determines whether a file path should be ignored by the directory watcher.
|
|
219
|
+
*
|
|
220
|
+
* This method implements a sophisticated filtering strategy for dot-segment paths
|
|
221
|
+
* (paths containing directories that start with a dot, like .git, .env, .cache).
|
|
222
|
+
*
|
|
223
|
+
* **Filtering Strategy:**
|
|
224
|
+
* 1. Paths WITHOUT dot segments: Never ignored
|
|
225
|
+
* 2. Paths WITH dot segments:
|
|
226
|
+
* - If SMYTH_PATH is not configured: All ignored
|
|
227
|
+
* - If SMYTH_PATH is configured:
|
|
228
|
+
* - Allow the watched directory even if SMYTH_PATH contains dot-segments
|
|
229
|
+
* (e.g., /home/user/.smyth/models/OpenAI/default.json is allowed)
|
|
230
|
+
* - Ignore dot-segments INSIDE the models directory
|
|
231
|
+
* (e.g., /home/user/.smyth/models/.hidden/model.json is ignored)
|
|
232
|
+
* - Paths outside watched directory: Ignored
|
|
233
|
+
*
|
|
234
|
+
* @param filePath - The file path to check
|
|
235
|
+
* @param watchedDir - The absolute path of the directory being watched (models folder)
|
|
236
|
+
* @param smythPath - The resolved SMYTH_PATH, or null if not configured
|
|
237
|
+
* @returns true if the path should be ignored, false if it should be watched
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* // Path without dot-segment (allowed)
|
|
242
|
+
* shouldIgnorePath('/models/OpenAI/default.json', '/models', '/home/.smyth') // => false
|
|
243
|
+
*
|
|
244
|
+
* // Dot-segment inside models directory (ignored)
|
|
245
|
+
* shouldIgnorePath('/models/.git/config', '/models', '/home/.smyth') // => true
|
|
246
|
+
*
|
|
247
|
+
* // Dot-segment in parent path only (allowed)
|
|
248
|
+
* shouldIgnorePath('/home/.smyth/models/OpenAI/default.json', '/home/.smyth/models', '/home/.smyth') // => false
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
private shouldIgnorePath(filePath: string, watchedDir: string, smythPath: string | null): boolean {
|
|
252
|
+
// Check if the file path contains a dot-segment (e.g., /.git/, /.env/, /.cache/)
|
|
253
|
+
// Regex explanation: [\\/]\. matches a path separator (/ or \) followed by a dot
|
|
254
|
+
const hasDotSegment = /[\\/]\./.test(filePath);
|
|
255
|
+
|
|
256
|
+
// CASE 1: If there is NO dot-segment at all, we never ignore this path
|
|
257
|
+
// Examples: /models/OpenAI/default.json, /models/Anthropic/claude.json
|
|
258
|
+
if (!hasDotSegment) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// CASE 2: If there IS a dot-segment and SMYTH_PATH is not configured,
|
|
263
|
+
// we ignore all such paths to prevent watching system/hidden files
|
|
264
|
+
// Examples: /.git/config, /node_modules/.cache/file.json
|
|
265
|
+
if (hasDotSegment && !smythPath) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Resolve the file path to an absolute path for accurate comparison
|
|
270
|
+
// This ensures we can reliably compare against the watched directory path
|
|
271
|
+
const resolvedPath = path.resolve(filePath);
|
|
272
|
+
|
|
273
|
+
// Check if the resolved path is inside the watched directory (models folder)
|
|
274
|
+
// This handles two cases:
|
|
275
|
+
// 1. The path exactly matches the watched directory
|
|
276
|
+
// 2. The path is a child of the watched directory (starts with watchedDir + separator)
|
|
277
|
+
const isInsideWatchedDir = resolvedPath === watchedDir || resolvedPath.startsWith(watchedDir + path.sep);
|
|
278
|
+
|
|
279
|
+
// CASE 3: If the path is outside the watched directory, ignore it
|
|
280
|
+
// This prevents watching unrelated files that happen to have dot-segments
|
|
281
|
+
if (!isInsideWatchedDir) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// CASE 4: Path is inside the watched directory
|
|
286
|
+
// Now we need to determine if the dot-segment is in the models directory itself
|
|
287
|
+
// or if it is part of the parent path (e.g., SMYTH_PATH containing .smyth)
|
|
288
|
+
|
|
289
|
+
// Get the relative path from the watched directory to determine where the dot-segment is
|
|
290
|
+
const relativePath = path.relative(watchedDir, resolvedPath);
|
|
291
|
+
|
|
292
|
+
// Check if the dot-segment appears in the relative portion (inside models directory)
|
|
293
|
+
// Regex explanation: (^|[\\/])\. matches a dot at the start OR after a path separator
|
|
294
|
+
// Examples that match: '.git/config', 'subdir/.hidden/file.json'
|
|
295
|
+
const hasDotSegmentInsideWatchedDir = /(^|[\\/])\./.test(relativePath);
|
|
296
|
+
|
|
297
|
+
// FINAL DECISION:
|
|
298
|
+
// - If dot-segment is INSIDE the models directory (e.g., models/.git/config), IGNORE it (return true)
|
|
299
|
+
// - If dot-segment is OUTSIDE the models directory (e.g., /home/user/.smyth/models/OpenAI/default.json), ALLOW it (return false)
|
|
300
|
+
// This allows SMYTH_PATH to contain dot-segments while preventing dot-segments within the models folder
|
|
301
|
+
return hasDotSegmentInsideWatchedDir;
|
|
302
|
+
}
|
|
303
|
+
|
|
217
304
|
private initDirWatcher(dir) {
|
|
218
305
|
const stats = fsSync.statSync(dir);
|
|
219
306
|
|
|
@@ -257,8 +344,12 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
257
344
|
maxWait: 5000,
|
|
258
345
|
});
|
|
259
346
|
|
|
347
|
+
const smythPath = process.env.SMYTH_PATH ? path.resolve(process.env.SMYTH_PATH) : null;
|
|
348
|
+
const watchedDir = path.resolve(dir);
|
|
349
|
+
|
|
260
350
|
const watcher = chokidar.watch(dir, {
|
|
261
|
-
|
|
351
|
+
// Use the extracted method for path filtering
|
|
352
|
+
ignored: (filePath: string) => this.shouldIgnorePath(filePath, watchedDir, smythPath),
|
|
262
353
|
persistent: true,
|
|
263
354
|
ignoreInitial: true, // Don't fire events for files that already exist
|
|
264
355
|
awaitWriteFinish: {
|