@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.
Files changed (81) hide show
  1. package/CHANGELOG +15 -0
  2. package/dist/index.js +52 -46
  3. package/dist/index.js.map +1 -1
  4. package/dist/types/Components/APIEndpoint.class.d.ts +2 -8
  5. package/dist/types/Components/Component.class.d.ts +9 -0
  6. package/dist/types/Components/Triggers/Gmail.trigger.d.ts +0 -17
  7. package/dist/types/Components/Triggers/JobScheduler.trigger.d.ts +10 -0
  8. package/dist/types/Components/Triggers/Trigger.class.d.ts +11 -0
  9. package/dist/types/Components/index.d.ts +6 -0
  10. package/dist/types/Core/Connector.class.d.ts +1 -0
  11. package/dist/types/Core/ConnectorsService.d.ts +2 -0
  12. package/dist/types/Core/HookService.d.ts +1 -1
  13. package/dist/types/helpers/Conversation.helper.d.ts +2 -0
  14. package/dist/types/helpers/Crypto.helper.d.ts +8 -0
  15. package/dist/types/index.d.ts +13 -0
  16. package/dist/types/subsystems/AgentManager/Agent.class.d.ts +4 -2
  17. package/dist/types/subsystems/AgentManager/AgentData.service/AgentDataConnector.d.ts +13 -0
  18. package/dist/types/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.d.ts +1 -4
  19. package/dist/types/subsystems/AgentManager/Scheduler.service/Job.class.d.ts +29 -6
  20. package/dist/types/subsystems/AgentManager/Scheduler.service/SchedulerConnector.d.ts +11 -3
  21. package/dist/types/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.d.ts +31 -7
  22. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.d.ts +2 -5
  23. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.d.ts +3 -6
  24. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.d.ts +7 -0
  25. package/dist/types/subsystems/LLMManager/LLM.service/connectors/xAI.class.d.ts +2 -5
  26. package/dist/types/types/Agent.types.d.ts +1 -0
  27. package/dist/types/types/LLM.types.d.ts +2 -5
  28. package/dist/types/types/SRE.types.d.ts +4 -1
  29. package/package.json +6 -2
  30. package/src/Components/APICall/OAuth.helper.ts +30 -35
  31. package/src/Components/APIEndpoint.class.ts +25 -6
  32. package/src/Components/Component.class.ts +11 -0
  33. package/src/Components/Triggers/Gmail.trigger.ts +282 -0
  34. package/src/Components/Triggers/JobScheduler.trigger.ts +45 -0
  35. package/src/Components/Triggers/README.md +3 -0
  36. package/src/Components/Triggers/Trigger.class.ts +101 -0
  37. package/src/Components/Triggers/WhatsApp.trigger.ts +219 -0
  38. package/src/Components/index.ts +8 -0
  39. package/src/Core/AgentProcess.helper.ts +4 -6
  40. package/src/Core/Connector.class.ts +11 -3
  41. package/src/Core/ConnectorsService.ts +5 -0
  42. package/src/Core/ExternalEventsReceiver.ts +317 -0
  43. package/src/Core/HookService.ts +20 -6
  44. package/src/Core/SmythRuntime.class.ts +7 -0
  45. package/src/Core/SystemEvents.ts +17 -0
  46. package/src/Core/boot.ts +2 -0
  47. package/src/helpers/Conversation.helper.ts +35 -11
  48. package/src/helpers/Crypto.helper.ts +28 -0
  49. package/src/index.ts +208 -195
  50. package/src/index.ts.bak +208 -195
  51. package/src/subsystems/AGENTS.md +594 -0
  52. package/src/subsystems/AgentManager/Agent.class.ts +71 -21
  53. package/src/subsystems/AgentManager/AgentData.service/AgentDataConnector.ts +24 -1
  54. package/src/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.ts +2 -2
  55. package/src/subsystems/AgentManager/AgentRuntime.class.ts +34 -5
  56. package/src/subsystems/AgentManager/Scheduler.service/Job.class.ts +414 -0
  57. package/src/subsystems/AgentManager/Scheduler.service/Schedule.class.ts +200 -0
  58. package/src/subsystems/AgentManager/Scheduler.service/SchedulerConnector.ts +200 -0
  59. package/src/subsystems/AgentManager/Scheduler.service/connectors/LocalScheduler.class.ts +767 -0
  60. package/src/subsystems/AgentManager/Scheduler.service/index.ts +11 -0
  61. package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +1 -1
  62. package/src/subsystems/LLMManager/LLM.service/LLMCredentials.helper.ts +61 -2
  63. package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +3 -0
  64. package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +3 -1
  65. package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +5 -1
  66. package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +247 -56
  67. package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +3 -0
  68. package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +28 -21
  69. package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +3 -0
  70. package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +121 -33
  71. package/src/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.ts +38 -27
  72. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +115 -18
  73. package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +3 -0
  74. package/src/subsystems/LLMManager/ModelsProvider.service/ModelsProviderConnector.ts +1 -6
  75. package/src/subsystems/MemoryManager/LLMContext.ts +3 -8
  76. package/src/subsystems/MemoryManager/RuntimeContext.ts +10 -9
  77. package/src/subsystems/Security/Credentials/Credentials.class.ts +1 -0
  78. package/src/subsystems/Security/Credentials/ManagedOAuth2Credentials.class.ts +106 -0
  79. package/src/types/Agent.types.ts +1 -0
  80. package/src/types/LLM.types.ts +2 -2
  81. 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
- if (context.modelInfo.baseURL) {
48
- // Handle baseURL that might include /api/ suffix
49
- const baseURL = context.modelInfo.baseURL;
50
- if (baseURL.endsWith('/api/')) {
51
- // Remove /api/ suffix to get the root host
52
- host = baseURL.replace(/\/api\/$/, '');
53
- } else if (baseURL.endsWith('/api')) {
54
- // Remove /api suffix
55
- host = baseURL.replace(/\/api$/, '');
56
- } else {
57
- host = baseURL;
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({ host });
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 (typeof messageBlock.content === 'string') {
387
- parts.push({ text: messageBlock.content });
388
- } else if (Array.isArray(messageBlock.content)) {
389
- parts.push(...messageBlock.content);
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.map((toolCall: any) => ({
394
- functionCall: {
395
- name: toolCall?.function?.name,
396
- args:
397
- typeof toolCall?.function?.arguments === 'string'
398
- ? JSON.parse(toolCall.function.arguments)
399
- : toolCall?.function?.arguments || {},
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
- messageBlocks.push({
406
- role: messageBlock.role,
407
- parts,
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 toolResults = toolsData.map((toolData) => ({
413
- role: TLLMMessageRole.User,
414
- parts: [
415
- {
416
- functionResponse: {
417
- name: toolData.name,
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(params: ILLMRequestContext): Promise<OpenAI> {
73
- const apiKey = (params.credentials as BasicCredentials)?.apiKey;
74
- const baseURL = params?.modelInfo?.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
- const openai = new OpenAI({ baseURL, apiKey });
77
+ try {
78
+ const openai = new OpenAI({ baseURL, apiKey });
77
79
 
78
- return openai;
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
- const messages = _body?.messages || [];
88
- const lastMessage = messages[messages.length - 1];
89
- const promptTokens = await this.computePromptTokens(messages, context);
90
-
91
- await this.validateTokenLimit({
92
- acRequest,
93
- promptTokens,
94
- context,
95
- maxTokens: _body.max_completion_tokens,
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
- // #region Validate token limit
150
- const messages = body?.messages || body?.input || [];
151
- const lastMessage = messages[messages.length - 1];
152
- const promptTokens = await this.computePromptTokens(messages, context);
153
-
154
- await this.validateTokenLimit({
155
- acRequest,
156
- promptTokens,
157
- context,
158
- maxTokens: body.max_completion_tokens,
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
- const bufferData = await file.readData(AccessCandidate.agent(agentId));
804
- const base64Data = bufferData.toString('base64');
805
- const url = `data:${file.mimetype};base64,${base64Data}`;
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
- imageData.push({
808
- type: 'input_image',
809
- image_url: url,
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
- const bufferData = await file.readData(AccessCandidate.agent(agentId));
827
- const base64Data = bufferData.toString('base64');
828
- const fileData = `data:${file.mimetype};base64,${base64Data}`;
829
- const filename = await file.getName();
830
-
831
- documentData.push({
832
- type: 'input_file',
833
- file: {
834
- file_data: fileData,
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