@smythos/sre 1.6.14 → 1.7.1
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/CHANGELOG +15 -0
- package/dist/index.js +52 -46
- package/dist/index.js.map +1 -1
- package/dist/types/Components/APIEndpoint.class.d.ts +2 -8
- package/dist/types/Components/Component.class.d.ts +9 -0
- package/dist/types/Components/Triggers/Gmail.trigger.d.ts +0 -17
- package/dist/types/Components/Triggers/JobScheduler.trigger.d.ts +10 -0
- package/dist/types/Components/Triggers/Trigger.class.d.ts +11 -0
- package/dist/types/Components/index.d.ts +6 -0
- package/dist/types/Core/Connector.class.d.ts +1 -0
- package/dist/types/Core/ConnectorsService.d.ts +2 -0
- package/dist/types/Core/HookService.d.ts +1 -1
- package/dist/types/helpers/Conversation.helper.d.ts +2 -0
- package/dist/types/helpers/Crypto.helper.d.ts +8 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/subsystems/AgentManager/Agent.class.d.ts +4 -2
- package/dist/types/subsystems/AgentManager/AgentData.service/AgentDataConnector.d.ts +13 -0
- package/dist/types/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.d.ts +1 -4
- package/dist/types/subsystems/AgentManager/Scheduler.service/Job.class.d.ts +29 -6
- package/dist/types/subsystems/AgentManager/Scheduler.service/SchedulerConnector.d.ts +11 -3
- package/dist/types/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.d.ts +31 -7
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.d.ts +2 -5
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.d.ts +3 -6
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.d.ts +7 -0
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/xAI.class.d.ts +2 -5
- package/dist/types/types/Agent.types.d.ts +1 -0
- package/dist/types/types/LLM.types.d.ts +2 -5
- package/dist/types/types/SRE.types.d.ts +4 -1
- package/package.json +6 -2
- package/src/Components/APICall/OAuth.helper.ts +30 -35
- package/src/Components/APIEndpoint.class.ts +25 -6
- package/src/Components/Component.class.ts +11 -0
- package/src/Components/Triggers/Gmail.trigger.ts +282 -0
- package/src/Components/Triggers/JobScheduler.trigger.ts +45 -0
- package/src/Components/Triggers/README.md +3 -0
- package/src/Components/Triggers/Trigger.class.ts +101 -0
- package/src/Components/Triggers/WhatsApp.trigger.ts +219 -0
- package/src/Components/index.ts +8 -0
- package/src/Core/AgentProcess.helper.ts +4 -6
- package/src/Core/Connector.class.ts +11 -3
- package/src/Core/ConnectorsService.ts +5 -0
- package/src/Core/ExternalEventsReceiver.ts +317 -0
- package/src/Core/HookService.ts +20 -6
- package/src/Core/SmythRuntime.class.ts +7 -0
- package/src/Core/SystemEvents.ts +17 -0
- package/src/Core/boot.ts +2 -0
- package/src/helpers/Conversation.helper.ts +35 -11
- package/src/helpers/Crypto.helper.ts +28 -0
- package/src/index.ts +208 -195
- package/src/index.ts.bak +208 -195
- package/src/subsystems/AGENTS.md +594 -0
- package/src/subsystems/AgentManager/Agent.class.ts +71 -21
- package/src/subsystems/AgentManager/AgentData.service/AgentDataConnector.ts +24 -1
- package/src/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.ts +2 -2
- package/src/subsystems/AgentManager/AgentRuntime.class.ts +34 -5
- package/src/subsystems/AgentManager/Scheduler.service/Job.class.ts +414 -0
- package/src/subsystems/AgentManager/Scheduler.service/Schedule.class.ts +200 -0
- package/src/subsystems/AgentManager/Scheduler.service/SchedulerConnector.ts +200 -0
- package/src/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.ts +767 -0
- package/src/subsystems/AgentManager/Scheduler.service/index.ts +11 -0
- package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +1 -1
- package/src/subsystems/LLMManager/LLM.service/LLMCredentials.helper.ts +61 -2
- package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +3 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +3 -1
- package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +5 -1
- package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +247 -56
- package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +3 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +28 -21
- package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +3 -0
- package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +121 -33
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.ts +38 -27
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +115 -18
- package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +3 -0
- package/src/subsystems/LLMManager/ModelsProvider.service/ModelsProviderConnector.ts +1 -6
- package/src/subsystems/MemoryManager/LLMContext.ts +3 -8
- package/src/subsystems/MemoryManager/RuntimeContext.ts +10 -9
- package/src/subsystems/Security/Credentials/Credentials.class.ts +1 -0
- package/src/subsystems/Security/Credentials/ManagedOAuth2Credentials.class.ts +106 -0
- package/src/types/Agent.types.ts +1 -0
- package/src/types/LLM.types.ts +2 -2
- package/src/types/SRE.types.ts +3 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ollama, ChatResponse } from 'ollama';
|
|
1
|
+
import { Ollama, ChatResponse, type ChatRequest } from 'ollama';
|
|
2
2
|
import EventEmitter from 'events';
|
|
3
3
|
|
|
4
4
|
import { JSON_RESPONSE_INSTRUCTION, BUILT_IN_MODEL_PREFIX } from '@sre/constants';
|
|
@@ -14,12 +14,14 @@ import {
|
|
|
14
14
|
TLLMPreparedParams,
|
|
15
15
|
TLLMToolResultMessageBlock,
|
|
16
16
|
TLLMRequestBody,
|
|
17
|
+
BasicCredentials,
|
|
17
18
|
} from '@sre/types/LLM.types';
|
|
18
19
|
import { LLMHelper } from '@sre/LLMManager/LLM.helper';
|
|
19
20
|
|
|
20
21
|
import { LLMConnector } from '../LLMConnector';
|
|
21
22
|
import { SystemEvents } from '@sre/Core/SystemEvents';
|
|
22
23
|
import { Logger } from '@sre/helpers/Log.helper';
|
|
24
|
+
import { hookAsync } from '@sre/Core/HookService';
|
|
23
25
|
|
|
24
26
|
const logger = Logger('OllamaConnector');
|
|
25
27
|
|
|
@@ -44,33 +46,37 @@ export class OllamaConnector extends LLMConnector {
|
|
|
44
46
|
// Extract baseURL and sanitize it for Ollama SDK
|
|
45
47
|
let host = 'http://localhost:11434';
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
const apiKey = (context.credentials as BasicCredentials)?.apiKey;
|
|
50
|
+
const baseURL = context?.modelInfo?.baseURL;
|
|
51
|
+
|
|
52
|
+
if (baseURL) {
|
|
53
|
+
// Extract base URL (origin) using URL class
|
|
54
|
+
const url = new URL(baseURL);
|
|
55
|
+
host = url.origin;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const config: { host: string; headers?: { Authorization?: string } } = { host };
|
|
59
|
+
|
|
60
|
+
if (apiKey) {
|
|
61
|
+
config.headers = {
|
|
62
|
+
Authorization: `Bearer ${apiKey}`,
|
|
63
|
+
};
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
// No API key validation required for Ollama (local by default)
|
|
62
|
-
return new Ollama(
|
|
67
|
+
return new Ollama(config);
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
@hookAsync('LLMConnector.request')
|
|
65
71
|
protected async request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse> {
|
|
66
72
|
try {
|
|
67
73
|
logger.debug(`request ${this.name}`, acRequest.candidate);
|
|
68
74
|
const ollama = this.getClient(context);
|
|
69
75
|
|
|
70
|
-
const result = await ollama.chat({
|
|
76
|
+
const result = (await ollama.chat({
|
|
71
77
|
...body,
|
|
72
78
|
stream: false,
|
|
73
|
-
}) as unknown as ChatResponse;
|
|
79
|
+
})) as unknown as ChatResponse;
|
|
74
80
|
|
|
75
81
|
const message = result.message;
|
|
76
82
|
const finishReason = result.done_reason || 'stop';
|
|
@@ -117,6 +123,7 @@ export class OllamaConnector extends LLMConnector {
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
@hookAsync('LLMConnector.streamRequest')
|
|
120
127
|
protected async streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter> {
|
|
121
128
|
try {
|
|
122
129
|
logger.debug(`streamRequest ${this.name}`, acRequest.candidate);
|
|
@@ -124,10 +131,10 @@ export class OllamaConnector extends LLMConnector {
|
|
|
124
131
|
const usage_data = [];
|
|
125
132
|
|
|
126
133
|
const ollama = this.getClient(context);
|
|
127
|
-
const stream = await ollama.chat({
|
|
134
|
+
const stream = (await ollama.chat({
|
|
128
135
|
...body,
|
|
129
136
|
stream: true,
|
|
130
|
-
}) as AsyncIterable<ChatResponse>;
|
|
137
|
+
})) as AsyncIterable<ChatResponse>;
|
|
131
138
|
|
|
132
139
|
let toolsData: ToolData[] = [];
|
|
133
140
|
let fullContent = '';
|
|
@@ -159,7 +166,7 @@ export class OllamaConnector extends LLMConnector {
|
|
|
159
166
|
toolsData[index].arguments += toolCall.function.arguments;
|
|
160
167
|
} else {
|
|
161
168
|
// For object arguments, merge them properly
|
|
162
|
-
toolsData[index].arguments = { ...toolsData[index].arguments as any, ...toolCall.function?.arguments };
|
|
169
|
+
toolsData[index].arguments = { ...(toolsData[index].arguments as any), ...toolCall.function?.arguments };
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
172
|
});
|
|
@@ -237,7 +244,7 @@ export class OllamaConnector extends LLMConnector {
|
|
|
237
244
|
|
|
238
245
|
// Handle tools
|
|
239
246
|
if (params.toolsConfig?.tools) {
|
|
240
|
-
body.tools = params.toolsConfig.tools.map(tool => ({
|
|
247
|
+
body.tools = params.toolsConfig.tools.map((tool) => ({
|
|
241
248
|
type: 'function',
|
|
242
249
|
function: {
|
|
243
250
|
name: tool.function.name,
|
|
@@ -359,4 +366,4 @@ export class OllamaConnector extends LLMConnector {
|
|
|
359
366
|
return _message;
|
|
360
367
|
});
|
|
361
368
|
}
|
|
362
|
-
}
|
|
369
|
+
}
|
|
@@ -20,6 +20,7 @@ import { LLMHelper } from '@sre/LLMManager/LLM.helper';
|
|
|
20
20
|
import { LLMConnector } from '../LLMConnector';
|
|
21
21
|
import { SystemEvents } from '@sre/Core/SystemEvents';
|
|
22
22
|
import { Logger } from '@sre/helpers/Log.helper';
|
|
23
|
+
import { hookAsync } from '@sre/Core/HookService';
|
|
23
24
|
|
|
24
25
|
const logger = Logger('PerplexityConnector');
|
|
25
26
|
|
|
@@ -58,6 +59,7 @@ export class PerplexityConnector extends LLMConnector {
|
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
@hookAsync('LLMConnector.request')
|
|
61
63
|
protected async request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse> {
|
|
62
64
|
try {
|
|
63
65
|
logger.debug(`request ${this.name}`, acRequest.candidate);
|
|
@@ -89,6 +91,7 @@ export class PerplexityConnector extends LLMConnector {
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
@hookAsync('LLMConnector.streamRequest')
|
|
92
95
|
protected async streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter> {
|
|
93
96
|
//throw new Error('Multimodal request is not supported for Perplexity.');
|
|
94
97
|
//fallback to chatRequest
|
|
@@ -24,6 +24,7 @@ import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.cla
|
|
|
24
24
|
import { LLMConnector } from '../LLMConnector';
|
|
25
25
|
import { SystemEvents } from '@sre/Core/SystemEvents';
|
|
26
26
|
import { Logger } from '@sre/helpers/Log.helper';
|
|
27
|
+
import { hookAsync } from '@sre/Core/HookService';
|
|
27
28
|
|
|
28
29
|
const logger = Logger('VertexAIConnector');
|
|
29
30
|
|
|
@@ -49,6 +50,7 @@ export class VertexAIConnector extends LLMConnector {
|
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
@hookAsync('LLMConnector.request')
|
|
52
54
|
protected async request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse> {
|
|
53
55
|
try {
|
|
54
56
|
logger.debug(`request ${this.name}`, acRequest.candidate);
|
|
@@ -110,6 +112,7 @@ export class VertexAIConnector extends LLMConnector {
|
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
@hookAsync('LLMConnector.streamRequest')
|
|
113
116
|
protected async streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter> {
|
|
114
117
|
const emitter = new EventEmitter();
|
|
115
118
|
|
|
@@ -380,51 +383,136 @@ export class VertexAIConnector extends LLMConnector {
|
|
|
380
383
|
}): TLLMToolResultMessageBlock[] {
|
|
381
384
|
const messageBlocks: TLLMToolResultMessageBlock[] = [];
|
|
382
385
|
|
|
386
|
+
const parseFunctionArgs = (args: unknown) => {
|
|
387
|
+
if (typeof args === 'string') {
|
|
388
|
+
try {
|
|
389
|
+
return JSON.parse(args);
|
|
390
|
+
} catch {
|
|
391
|
+
return args;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return args ?? {};
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const parseFunctionResponse = (response: unknown): any => {
|
|
398
|
+
if (typeof response === 'string') {
|
|
399
|
+
try {
|
|
400
|
+
const parsed = JSON.parse(response);
|
|
401
|
+
if (typeof parsed === 'string' && parsed !== response) {
|
|
402
|
+
return parseFunctionResponse(parsed);
|
|
403
|
+
}
|
|
404
|
+
return parsed;
|
|
405
|
+
} catch {
|
|
406
|
+
return response;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return response ?? {};
|
|
410
|
+
};
|
|
411
|
+
|
|
383
412
|
if (messageBlock) {
|
|
384
|
-
const parts = [];
|
|
413
|
+
const parts: any[] = [];
|
|
385
414
|
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
415
|
+
if (Array.isArray(messageBlock.parts) && messageBlock.parts.length > 0) {
|
|
416
|
+
for (const part of messageBlock.parts) {
|
|
417
|
+
if (!part) continue;
|
|
418
|
+
|
|
419
|
+
if (typeof part.text === 'string' && part.text.trim()) {
|
|
420
|
+
parts.push({ text: part.text.trim() });
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (part.functionCall) {
|
|
425
|
+
parts.push({
|
|
426
|
+
functionCall: {
|
|
427
|
+
name: part.functionCall.name,
|
|
428
|
+
args: parseFunctionArgs(part.functionCall.args),
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (part.functionResponse) {
|
|
435
|
+
parts.push({
|
|
436
|
+
functionResponse: {
|
|
437
|
+
name: part.functionResponse.name,
|
|
438
|
+
response: parseFunctionResponse(part.functionResponse.response),
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if ((part as any).inlineData) {
|
|
445
|
+
parts.push({ inlineData: (part as any).inlineData });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
if (typeof messageBlock.content === 'string' && messageBlock.content.trim()) {
|
|
450
|
+
parts.push({ text: messageBlock.content.trim() });
|
|
451
|
+
} else if (Array.isArray(messageBlock.content) && messageBlock.content.length > 0) {
|
|
452
|
+
parts.push(...messageBlock.content);
|
|
453
|
+
}
|
|
390
454
|
}
|
|
391
455
|
|
|
392
|
-
if (messageBlock.tool_calls) {
|
|
393
|
-
const functionCalls = messageBlock.tool_calls
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
: toolCall
|
|
400
|
-
|
|
401
|
-
|
|
456
|
+
if (Array.isArray(messageBlock.tool_calls) && messageBlock.tool_calls.length > 0) {
|
|
457
|
+
const functionCalls = messageBlock.tool_calls
|
|
458
|
+
.map((toolCall: any) => {
|
|
459
|
+
if (!toolCall?.function?.name) return undefined;
|
|
460
|
+
return {
|
|
461
|
+
functionCall: {
|
|
462
|
+
name: toolCall.function.name,
|
|
463
|
+
args: parseFunctionArgs(toolCall.function.arguments),
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
})
|
|
467
|
+
.filter(Boolean);
|
|
468
|
+
|
|
402
469
|
parts.push(...functionCalls);
|
|
403
470
|
}
|
|
404
471
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
472
|
+
const hasFunctionCall = parts.some((part) => part.functionCall);
|
|
473
|
+
if (!hasFunctionCall && toolsData.length > 0) {
|
|
474
|
+
toolsData.forEach((toolCall) => {
|
|
475
|
+
parts.push({
|
|
476
|
+
functionCall: {
|
|
477
|
+
name: toolCall.name,
|
|
478
|
+
args: parseFunctionArgs(toolCall.arguments),
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (parts.length > 0) {
|
|
485
|
+
let role = messageBlock.role;
|
|
486
|
+
if (role === TLLMMessageRole.Assistant) {
|
|
487
|
+
role = TLLMMessageRole.Model;
|
|
488
|
+
} else if (role === TLLMMessageRole.Tool) {
|
|
489
|
+
role = TLLMMessageRole.Function;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
messageBlocks.push({
|
|
493
|
+
role,
|
|
494
|
+
parts,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
409
497
|
}
|
|
410
498
|
|
|
411
499
|
// Transform tool results
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
response: {
|
|
419
|
-
name: toolData.name,
|
|
420
|
-
content: toolData.result,
|
|
421
|
-
},
|
|
422
|
-
},
|
|
500
|
+
const functionResponseParts = toolsData
|
|
501
|
+
.filter((toolData) => toolData.result !== undefined)
|
|
502
|
+
.map((toolData) => ({
|
|
503
|
+
functionResponse: {
|
|
504
|
+
name: toolData.name,
|
|
505
|
+
response: parseFunctionResponse(toolData.result),
|
|
423
506
|
},
|
|
424
|
-
|
|
425
|
-
|
|
507
|
+
}));
|
|
508
|
+
|
|
509
|
+
if (functionResponseParts.length > 0) {
|
|
510
|
+
messageBlocks.push({
|
|
511
|
+
role: TLLMMessageRole.Function,
|
|
512
|
+
parts: functionResponseParts,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
426
515
|
|
|
427
|
-
messageBlocks.push(...toolResults);
|
|
428
516
|
return messageBlocks;
|
|
429
517
|
}
|
|
430
518
|
}
|
|
@@ -28,6 +28,7 @@ import { Logger } from '@sre/helpers/Log.helper';
|
|
|
28
28
|
import { LLMConnector } from '../../LLMConnector';
|
|
29
29
|
import { OpenAIApiInterface, OpenAIApiInterfaceFactory } from './apiInterfaces';
|
|
30
30
|
import { HandlerDependencies } from './types';
|
|
31
|
+
import { hookAsync } from '@sre/Core/HookService';
|
|
31
32
|
|
|
32
33
|
const logger = Logger('OpenAIConnector');
|
|
33
34
|
|
|
@@ -69,31 +70,38 @@ export class OpenAIConnector extends LLMConnector {
|
|
|
69
70
|
return responseInterface;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
protected async getClient(
|
|
73
|
-
const apiKey = (
|
|
74
|
-
const baseURL =
|
|
73
|
+
protected async getClient(context: ILLMRequestContext): Promise<OpenAI> {
|
|
74
|
+
const apiKey = (context.credentials as BasicCredentials)?.apiKey || '';
|
|
75
|
+
const baseURL = context?.modelInfo?.baseURL;
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
try {
|
|
78
|
+
const openai = new OpenAI({ baseURL, apiKey });
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
return openai;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Error: on OpenAI client initialization', error);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
@hookAsync('LLMConnector.request')
|
|
81
88
|
protected async request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse> {
|
|
82
89
|
try {
|
|
83
90
|
logger.debug(`request ${this.name}`, acRequest.candidate);
|
|
84
91
|
const _body = body as OpenAI.ChatCompletionCreateParams;
|
|
85
92
|
|
|
86
|
-
// #region Validate token limit
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// #region Validate the token limit only if it's a legacy model.
|
|
94
|
+
if (context?.modelEntryName?.startsWith('legacy/')) {
|
|
95
|
+
const messages = _body?.messages || [];
|
|
96
|
+
const promptTokens = await this.computePromptTokens(messages, context);
|
|
97
|
+
|
|
98
|
+
await this.validateTokenLimit({
|
|
99
|
+
acRequest,
|
|
100
|
+
promptTokens,
|
|
101
|
+
context,
|
|
102
|
+
maxTokens: _body.max_completion_tokens,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
97
105
|
// #endregion Validate token limit
|
|
98
106
|
|
|
99
107
|
const responseInterface = this.getInterfaceType(context);
|
|
@@ -143,20 +151,23 @@ export class OpenAIConnector extends LLMConnector {
|
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
@hookAsync('LLMConnector.streamRequest')
|
|
146
155
|
protected async streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter> {
|
|
147
156
|
try {
|
|
148
157
|
logger.debug(`streamRequest ${this.name}`, acRequest.candidate);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
|
|
159
|
+
// #region Validate the token limit only if it's a legacy model.
|
|
160
|
+
if (context?.modelEntryName?.startsWith('legacy/')) {
|
|
161
|
+
const messages = body?.messages || body?.input || [];
|
|
162
|
+
const promptTokens = await this.computePromptTokens(messages, context);
|
|
163
|
+
|
|
164
|
+
await this.validateTokenLimit({
|
|
165
|
+
acRequest,
|
|
166
|
+
promptTokens,
|
|
167
|
+
context,
|
|
168
|
+
maxTokens: body.max_completion_tokens,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
160
171
|
// #endregion Validate token limit
|
|
161
172
|
|
|
162
173
|
const responseInterface = this.getInterfaceType(context);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
3
|
import type { Stream } from 'openai/streaming';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs';
|
|
4
7
|
|
|
5
8
|
import { BinaryInput } from '@sre/helpers/BinaryInput.helper';
|
|
6
9
|
import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
|
|
@@ -10,6 +13,7 @@ import { HandlerDependencies, TToolType } from '../types';
|
|
|
10
13
|
import { SUPPORTED_MIME_TYPES_MAP } from '@sre/constants';
|
|
11
14
|
import { SEARCH_TOOL_COSTS } from './constants';
|
|
12
15
|
import { isValidOpenAIReasoningEffort } from './utils';
|
|
16
|
+
import { uid } from '@sre/utils';
|
|
13
17
|
|
|
14
18
|
// File size limits in bytes
|
|
15
19
|
const MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB
|
|
@@ -30,6 +34,7 @@ const EVENT_TYPES = {
|
|
|
30
34
|
FUNCTION_CALL_ARGUMENTS_DELTA: 'response.function_call_arguments.delta',
|
|
31
35
|
FUNCTION_CALL_ARGUMENTS_DONE: 'response.function_call_arguments.done',
|
|
32
36
|
OUTPUT_ITEM_DONE: 'response.output_item.done',
|
|
37
|
+
INCOMPLETE: 'response.incomplete',
|
|
33
38
|
} as const;
|
|
34
39
|
|
|
35
40
|
// Type definitions for web search events (augmenting SDK types locally)
|
|
@@ -176,6 +181,14 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
176
181
|
break;
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
case EVENT_TYPES.INCOMPLETE:
|
|
185
|
+
finishReason = 'incomplete';
|
|
186
|
+
const responseData = (part as any)?.response;
|
|
187
|
+
if (responseData?.usage) {
|
|
188
|
+
usageData.push(responseData.usage);
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
|
|
179
192
|
default: {
|
|
180
193
|
const eventType = String(part.type);
|
|
181
194
|
// Handle legacy started event if ever emitted
|
|
@@ -790,8 +803,62 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
790
803
|
return Promise.all(promises);
|
|
791
804
|
}
|
|
792
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Upload file to OpenAI Files API
|
|
808
|
+
* Similar to GoogleAI's uploadFile implementation
|
|
809
|
+
*/
|
|
810
|
+
private async uploadFile({
|
|
811
|
+
file,
|
|
812
|
+
agentId,
|
|
813
|
+
purpose = 'user_data',
|
|
814
|
+
}: {
|
|
815
|
+
file: BinaryInput;
|
|
816
|
+
agentId: string;
|
|
817
|
+
purpose?: 'user_data' | 'assistants' | 'batch' | 'fine-tune' | 'vision';
|
|
818
|
+
}): Promise<{ fileId: string; filename: string }> {
|
|
819
|
+
try {
|
|
820
|
+
if (!file?.mimetype) {
|
|
821
|
+
throw new Error('Missing required parameters to upload file to OpenAI!');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const tempDir = os.tmpdir();
|
|
825
|
+
const fileName = await file.getName();
|
|
826
|
+
const tempFilePath = path.join(tempDir, `${uid()}_${fileName}`);
|
|
827
|
+
|
|
828
|
+
// Write file to temporary location
|
|
829
|
+
const bufferData = await file.readData(AccessCandidate.agent(agentId));
|
|
830
|
+
await fs.promises.writeFile(tempFilePath, new Uint8Array(bufferData));
|
|
831
|
+
|
|
832
|
+
const openai = await this.deps.getClient(this.context);
|
|
833
|
+
|
|
834
|
+
// Upload file to OpenAI Files API
|
|
835
|
+
const uploadResponse = await openai.files.create({
|
|
836
|
+
file: fs.createReadStream(tempFilePath),
|
|
837
|
+
purpose: purpose,
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
const fileId = uploadResponse.id;
|
|
841
|
+
if (!fileId) {
|
|
842
|
+
throw new Error('File upload did not return a file ID.');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Clean up temporary file
|
|
846
|
+
await fs.promises.unlink(tempFilePath).catch(() => {
|
|
847
|
+
// Ignore cleanup errors
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
return {
|
|
851
|
+
fileId,
|
|
852
|
+
filename: fileName,
|
|
853
|
+
};
|
|
854
|
+
} catch (error: any) {
|
|
855
|
+
throw new Error(`Error uploading file to OpenAI: ${error.message}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
793
859
|
/**
|
|
794
860
|
* Process image files with Responses API specific formatting
|
|
861
|
+
* Uses OpenAI Files API for uploading images
|
|
795
862
|
*/
|
|
796
863
|
private async processImageData(files: BinaryInput[], agentId: string): Promise<any[]> {
|
|
797
864
|
if (files.length === 0) return [];
|
|
@@ -800,14 +867,30 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
800
867
|
for (const file of files) {
|
|
801
868
|
await this.validateFileSize(file, MAX_IMAGE_SIZE, 'Image');
|
|
802
869
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
870
|
+
try {
|
|
871
|
+
// Upload file to OpenAI Files API with 'vision' purpose
|
|
872
|
+
const { fileId } = await this.uploadFile({
|
|
873
|
+
file,
|
|
874
|
+
agentId,
|
|
875
|
+
purpose: 'vision',
|
|
876
|
+
});
|
|
806
877
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
878
|
+
imageData.push({
|
|
879
|
+
type: 'input_image',
|
|
880
|
+
file_id: fileId,
|
|
881
|
+
});
|
|
882
|
+
} catch (error) {
|
|
883
|
+
// If Files API upload fails, fall back to base64 inline data
|
|
884
|
+
console.warn('Failed to upload image via Files API, falling back to base64:', error);
|
|
885
|
+
const bufferData = await file.readData(AccessCandidate.agent(agentId));
|
|
886
|
+
const base64Data = bufferData.toString('base64');
|
|
887
|
+
const url = `data:${file.mimetype};base64,${base64Data}`;
|
|
888
|
+
|
|
889
|
+
imageData.push({
|
|
890
|
+
type: 'input_image',
|
|
891
|
+
image_url: url,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
811
894
|
}
|
|
812
895
|
|
|
813
896
|
return imageData;
|
|
@@ -815,6 +898,7 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
815
898
|
|
|
816
899
|
/**
|
|
817
900
|
* Process document files with Responses API specific formatting
|
|
901
|
+
* Uses OpenAI Files API for uploading documents
|
|
818
902
|
*/
|
|
819
903
|
private async processDocumentData(files: BinaryInput[], agentId: string): Promise<any[]> {
|
|
820
904
|
if (files.length === 0) return [];
|
|
@@ -823,18 +907,31 @@ export class ResponsesApiInterface extends OpenAIApiInterface {
|
|
|
823
907
|
for (const file of files) {
|
|
824
908
|
await this.validateFileSize(file, MAX_DOCUMENT_SIZE, 'Document');
|
|
825
909
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
910
|
+
try {
|
|
911
|
+
// Upload file to OpenAI Files API with 'user_data' purpose
|
|
912
|
+
const { fileId, filename } = await this.uploadFile({
|
|
913
|
+
file,
|
|
914
|
+
agentId,
|
|
915
|
+
purpose: 'user_data',
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
documentData.push({
|
|
919
|
+
type: 'input_file',
|
|
920
|
+
file_id: fileId,
|
|
921
|
+
});
|
|
922
|
+
} catch (error) {
|
|
923
|
+
// If Files API upload fails, fall back to base64 inline data
|
|
924
|
+
console.warn('Failed to upload document via Files API, falling back to base64:', error);
|
|
925
|
+
const bufferData = await file.readData(AccessCandidate.agent(agentId));
|
|
926
|
+
const base64Data = bufferData.toString('base64');
|
|
927
|
+
const filename = await file.getName();
|
|
928
|
+
|
|
929
|
+
documentData.push({
|
|
930
|
+
type: 'input_file',
|
|
835
931
|
filename,
|
|
836
|
-
|
|
837
|
-
|
|
932
|
+
file_data: `data:${file.mimetype};base64,${base64Data}`,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
838
935
|
}
|
|
839
936
|
|
|
840
937
|
return documentData;
|
|
@@ -20,6 +20,7 @@ import { LLMHelper } from '@sre/LLMManager/LLM.helper';
|
|
|
20
20
|
import { LLMConnector } from '../LLMConnector';
|
|
21
21
|
import { SystemEvents } from '@sre/Core/SystemEvents';
|
|
22
22
|
import { Logger } from '@sre/helpers/Log.helper';
|
|
23
|
+
import { hookAsync } from '@sre/Core/HookService';
|
|
23
24
|
|
|
24
25
|
const logger = Logger('xAIConnector');
|
|
25
26
|
|
|
@@ -97,6 +98,7 @@ export class xAIConnector extends LLMConnector {
|
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
@hookAsync('LLMConnector.request')
|
|
100
102
|
protected async request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse> {
|
|
101
103
|
try {
|
|
102
104
|
logger.debug(`request ${this.name}`, acRequest.candidate);
|
|
@@ -153,6 +155,7 @@ export class xAIConnector extends LLMConnector {
|
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
@hookAsync('LLMConnector.streamRequest')
|
|
156
159
|
protected async streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter> {
|
|
157
160
|
const emitter = new EventEmitter();
|
|
158
161
|
|
|
@@ -394,12 +394,7 @@ export abstract class ModelsProviderConnector extends SecureConnector {
|
|
|
394
394
|
baseURL: entry.baseURL,
|
|
395
395
|
fallbackLLM: entry.fallbackLLM,
|
|
396
396
|
isUserCustomLLM: true,
|
|
397
|
-
|
|
398
|
-
// TODO: Credentials will usually look like { apiKey: 'api-key-goes-here' }.
|
|
399
|
-
// However, for fallback models we also need to handle ['vault', 'internal']
|
|
400
|
-
// using the same credentials format, since fallback models can be either
|
|
401
|
-
// personal or built-in.
|
|
402
|
-
credentials: entry?.credentials || ['vault', 'internal'],
|
|
397
|
+
credentials: entry?.credentials || {},
|
|
403
398
|
};
|
|
404
399
|
}
|
|
405
400
|
|