@smythos/sre 1.5.43 → 1.5.45

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 (233) hide show
  1. package/CHANGELOG +90 -90
  2. package/LICENSE +18 -18
  3. package/README.md +135 -135
  4. package/dist/index.js +13 -13
  5. package/dist/index.js.map +1 -1
  6. package/dist/types/Components/GenAILLM.class.d.ts +6 -0
  7. package/dist/types/helpers/AWSLambdaCode.helper.d.ts +8 -5
  8. package/dist/types/index.d.ts +1 -0
  9. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Groq.class.d.ts +5 -0
  10. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.d.ts +13 -1
  11. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.d.ts +0 -4
  12. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.d.ts +44 -29
  13. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/constants.d.ts +4 -2
  14. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/utils.d.ts +6 -0
  15. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/types.d.ts +0 -4
  16. package/dist/types/subsystems/LLMManager/ModelsProvider.service/connectors/SmythModelsProvider.class.d.ts +39 -0
  17. package/dist/types/types/LLM.types.d.ts +4 -1
  18. package/package.json +5 -2
  19. package/src/Components/APICall/APICall.class.ts +156 -156
  20. package/src/Components/APICall/AccessTokenManager.ts +130 -130
  21. package/src/Components/APICall/ArrayBufferResponse.helper.ts +58 -58
  22. package/src/Components/APICall/OAuth.helper.ts +294 -294
  23. package/src/Components/APICall/mimeTypeCategories.ts +46 -46
  24. package/src/Components/APICall/parseData.ts +167 -167
  25. package/src/Components/APICall/parseHeaders.ts +41 -41
  26. package/src/Components/APICall/parseProxy.ts +68 -68
  27. package/src/Components/APICall/parseUrl.ts +91 -91
  28. package/src/Components/APIEndpoint.class.ts +234 -234
  29. package/src/Components/APIOutput.class.ts +58 -58
  30. package/src/Components/AgentPlugin.class.ts +102 -102
  31. package/src/Components/Async.class.ts +155 -155
  32. package/src/Components/Await.class.ts +90 -90
  33. package/src/Components/Classifier.class.ts +158 -158
  34. package/src/Components/Component.class.ts +132 -132
  35. package/src/Components/ComponentHost.class.ts +38 -38
  36. package/src/Components/DataSourceCleaner.class.ts +92 -92
  37. package/src/Components/DataSourceIndexer.class.ts +181 -181
  38. package/src/Components/DataSourceLookup.class.ts +161 -161
  39. package/src/Components/ECMASandbox.class.ts +71 -71
  40. package/src/Components/FEncDec.class.ts +29 -29
  41. package/src/Components/FHash.class.ts +33 -33
  42. package/src/Components/FSign.class.ts +80 -80
  43. package/src/Components/FSleep.class.ts +25 -25
  44. package/src/Components/FTimestamp.class.ts +25 -25
  45. package/src/Components/FileStore.class.ts +78 -78
  46. package/src/Components/ForEach.class.ts +97 -97
  47. package/src/Components/GPTPlugin.class.ts +70 -70
  48. package/src/Components/GenAILLM.class.ts +586 -579
  49. package/src/Components/HuggingFace.class.ts +314 -314
  50. package/src/Components/Image/imageSettings.config.ts +70 -70
  51. package/src/Components/ImageGenerator.class.ts +502 -502
  52. package/src/Components/JSONFilter.class.ts +54 -54
  53. package/src/Components/LLMAssistant.class.ts +213 -213
  54. package/src/Components/LogicAND.class.ts +28 -28
  55. package/src/Components/LogicAtLeast.class.ts +85 -85
  56. package/src/Components/LogicAtMost.class.ts +86 -86
  57. package/src/Components/LogicOR.class.ts +29 -29
  58. package/src/Components/LogicXOR.class.ts +34 -34
  59. package/src/Components/MCPClient.class.ts +112 -112
  60. package/src/Components/MemoryDeleteKeyVal.class.ts +70 -70
  61. package/src/Components/MemoryReadKeyVal.class.ts +66 -66
  62. package/src/Components/MemoryWriteKeyVal.class.ts +62 -62
  63. package/src/Components/MemoryWriteObject.class.ts +97 -97
  64. package/src/Components/MultimodalLLM.class.ts +128 -128
  65. package/src/Components/OpenAPI.class.ts +72 -72
  66. package/src/Components/PromptGenerator.class.ts +122 -122
  67. package/src/Components/ScrapflyWebScrape.class.ts +159 -159
  68. package/src/Components/ServerlessCode.class.ts +123 -123
  69. package/src/Components/TavilyWebSearch.class.ts +98 -98
  70. package/src/Components/VisionLLM.class.ts +104 -104
  71. package/src/Components/ZapierAction.class.ts +127 -127
  72. package/src/Components/index.ts +97 -97
  73. package/src/Core/AgentProcess.helper.ts +240 -240
  74. package/src/Core/Connector.class.ts +123 -123
  75. package/src/Core/ConnectorsService.ts +197 -197
  76. package/src/Core/DummyConnector.ts +49 -49
  77. package/src/Core/HookService.ts +105 -105
  78. package/src/Core/SmythRuntime.class.ts +235 -235
  79. package/src/Core/SystemEvents.ts +16 -16
  80. package/src/Core/boot.ts +56 -56
  81. package/src/config.ts +15 -15
  82. package/src/constants.ts +126 -126
  83. package/src/data/hugging-face.params.json +579 -579
  84. package/src/helpers/AWSLambdaCode.helper.ts +588 -528
  85. package/src/helpers/BinaryInput.helper.ts +331 -331
  86. package/src/helpers/Conversation.helper.ts +1119 -1119
  87. package/src/helpers/ECMASandbox.helper.ts +54 -54
  88. package/src/helpers/JsonContent.helper.ts +97 -97
  89. package/src/helpers/LocalCache.helper.ts +97 -97
  90. package/src/helpers/Log.helper.ts +274 -274
  91. package/src/helpers/OpenApiParser.helper.ts +150 -150
  92. package/src/helpers/S3Cache.helper.ts +147 -147
  93. package/src/helpers/SmythURI.helper.ts +5 -5
  94. package/src/helpers/Sysconfig.helper.ts +77 -77
  95. package/src/helpers/TemplateString.helper.ts +243 -243
  96. package/src/helpers/TypeChecker.helper.ts +329 -329
  97. package/src/index.ts +4 -3
  98. package/src/index.ts.bak +4 -3
  99. package/src/subsystems/AgentManager/Agent.class.ts +1114 -1114
  100. package/src/subsystems/AgentManager/Agent.helper.ts +3 -3
  101. package/src/subsystems/AgentManager/AgentData.service/AgentDataConnector.ts +230 -230
  102. package/src/subsystems/AgentManager/AgentData.service/connectors/CLIAgentDataConnector.class.ts +66 -66
  103. package/src/subsystems/AgentManager/AgentData.service/connectors/LocalAgentDataConnector.class.ts +142 -142
  104. package/src/subsystems/AgentManager/AgentData.service/connectors/NullAgentData.class.ts +39 -39
  105. package/src/subsystems/AgentManager/AgentData.service/index.ts +18 -18
  106. package/src/subsystems/AgentManager/AgentLogger.class.ts +297 -297
  107. package/src/subsystems/AgentManager/AgentRequest.class.ts +51 -51
  108. package/src/subsystems/AgentManager/AgentRuntime.class.ts +559 -559
  109. package/src/subsystems/AgentManager/AgentSSE.class.ts +101 -101
  110. package/src/subsystems/AgentManager/AgentSettings.class.ts +52 -52
  111. package/src/subsystems/AgentManager/Component.service/ComponentConnector.ts +32 -32
  112. package/src/subsystems/AgentManager/Component.service/connectors/LocalComponentConnector.class.ts +60 -60
  113. package/src/subsystems/AgentManager/Component.service/index.ts +11 -11
  114. package/src/subsystems/AgentManager/EmbodimentSettings.class.ts +47 -47
  115. package/src/subsystems/AgentManager/ForkedAgent.class.ts +154 -154
  116. package/src/subsystems/AgentManager/OSResourceMonitor.ts +77 -77
  117. package/src/subsystems/ComputeManager/Code.service/CodeConnector.ts +98 -98
  118. package/src/subsystems/ComputeManager/Code.service/connectors/AWSLambdaCode.class.ts +172 -170
  119. package/src/subsystems/ComputeManager/Code.service/connectors/ECMASandbox.class.ts +131 -131
  120. package/src/subsystems/ComputeManager/Code.service/index.ts +13 -13
  121. package/src/subsystems/IO/CLI.service/CLIConnector.ts +47 -47
  122. package/src/subsystems/IO/CLI.service/index.ts +9 -9
  123. package/src/subsystems/IO/Log.service/LogConnector.ts +32 -32
  124. package/src/subsystems/IO/Log.service/connectors/ConsoleLog.class.ts +28 -28
  125. package/src/subsystems/IO/Log.service/index.ts +13 -13
  126. package/src/subsystems/IO/NKV.service/NKVConnector.ts +43 -43
  127. package/src/subsystems/IO/NKV.service/connectors/NKVLocalStorage.class.ts +234 -234
  128. package/src/subsystems/IO/NKV.service/connectors/NKVRAM.class.ts +204 -204
  129. package/src/subsystems/IO/NKV.service/connectors/NKVRedis.class.ts +182 -182
  130. package/src/subsystems/IO/NKV.service/index.ts +14 -14
  131. package/src/subsystems/IO/Router.service/RouterConnector.ts +21 -21
  132. package/src/subsystems/IO/Router.service/connectors/ExpressRouter.class.ts +48 -48
  133. package/src/subsystems/IO/Router.service/connectors/NullRouter.class.ts +40 -40
  134. package/src/subsystems/IO/Router.service/index.ts +11 -11
  135. package/src/subsystems/IO/Storage.service/SmythFS.class.ts +489 -489
  136. package/src/subsystems/IO/Storage.service/StorageConnector.ts +66 -66
  137. package/src/subsystems/IO/Storage.service/connectors/LocalStorage.class.ts +327 -327
  138. package/src/subsystems/IO/Storage.service/connectors/S3Storage.class.ts +482 -482
  139. package/src/subsystems/IO/Storage.service/index.ts +13 -13
  140. package/src/subsystems/IO/VectorDB.service/VectorDBConnector.ts +108 -108
  141. package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +454 -454
  142. package/src/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.ts +384 -384
  143. package/src/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.ts +421 -421
  144. package/src/subsystems/IO/VectorDB.service/embed/BaseEmbedding.ts +107 -107
  145. package/src/subsystems/IO/VectorDB.service/embed/OpenAIEmbedding.ts +109 -109
  146. package/src/subsystems/IO/VectorDB.service/embed/index.ts +21 -21
  147. package/src/subsystems/IO/VectorDB.service/index.ts +14 -14
  148. package/src/subsystems/LLMManager/LLM.helper.ts +251 -251
  149. package/src/subsystems/LLMManager/LLM.inference.ts +339 -339
  150. package/src/subsystems/LLMManager/LLM.service/LLMConnector.ts +489 -489
  151. package/src/subsystems/LLMManager/LLM.service/LLMCredentials.helper.ts +171 -171
  152. package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +659 -659
  153. package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +400 -400
  154. package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +77 -77
  155. package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +757 -757
  156. package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +304 -291
  157. package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +250 -250
  158. package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +423 -423
  159. package/src/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.ts +488 -455
  160. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.ts +528 -528
  161. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/OpenAIApiInterface.ts +100 -100
  162. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/OpenAIApiInterfaceFactory.ts +81 -81
  163. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +1168 -853
  164. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/constants.ts +13 -37
  165. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/index.ts +4 -4
  166. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/utils.ts +11 -0
  167. package/src/subsystems/LLMManager/LLM.service/connectors/openai/types.ts +32 -37
  168. package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +471 -471
  169. package/src/subsystems/LLMManager/LLM.service/index.ts +44 -44
  170. package/src/subsystems/LLMManager/ModelsProvider.service/ModelsProviderConnector.ts +300 -300
  171. package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +252 -252
  172. package/src/subsystems/LLMManager/ModelsProvider.service/index.ts +11 -11
  173. package/src/subsystems/LLMManager/custom-models.ts +854 -854
  174. package/src/subsystems/LLMManager/models.ts +2540 -2540
  175. package/src/subsystems/LLMManager/paramMappings.ts +69 -69
  176. package/src/subsystems/MemoryManager/Cache.service/CacheConnector.ts +86 -86
  177. package/src/subsystems/MemoryManager/Cache.service/connectors/LocalStorageCache.class.ts +297 -297
  178. package/src/subsystems/MemoryManager/Cache.service/connectors/RAMCache.class.ts +201 -201
  179. package/src/subsystems/MemoryManager/Cache.service/connectors/RedisCache.class.ts +252 -252
  180. package/src/subsystems/MemoryManager/Cache.service/connectors/S3Cache.class.ts +373 -373
  181. package/src/subsystems/MemoryManager/Cache.service/index.ts +15 -15
  182. package/src/subsystems/MemoryManager/LLMCache.ts +72 -72
  183. package/src/subsystems/MemoryManager/LLMContext.ts +124 -124
  184. package/src/subsystems/MemoryManager/LLMMemory.service/LLMMemoryConnector.ts +26 -26
  185. package/src/subsystems/MemoryManager/RuntimeContext.ts +266 -266
  186. package/src/subsystems/Security/AccessControl/ACL.class.ts +208 -208
  187. package/src/subsystems/Security/AccessControl/AccessCandidate.class.ts +82 -82
  188. package/src/subsystems/Security/AccessControl/AccessRequest.class.ts +52 -52
  189. package/src/subsystems/Security/Account.service/AccountConnector.ts +44 -44
  190. package/src/subsystems/Security/Account.service/connectors/AWSAccount.class.ts +76 -76
  191. package/src/subsystems/Security/Account.service/connectors/DummyAccount.class.ts +130 -130
  192. package/src/subsystems/Security/Account.service/connectors/JSONFileAccount.class.ts +159 -159
  193. package/src/subsystems/Security/Account.service/index.ts +14 -14
  194. package/src/subsystems/Security/Credentials.helper.ts +62 -62
  195. package/src/subsystems/Security/ManagedVault.service/ManagedVaultConnector.ts +38 -38
  196. package/src/subsystems/Security/ManagedVault.service/connectors/NullManagedVault.class.ts +53 -53
  197. package/src/subsystems/Security/ManagedVault.service/connectors/SecretManagerManagedVault.ts +154 -154
  198. package/src/subsystems/Security/ManagedVault.service/index.ts +12 -12
  199. package/src/subsystems/Security/SecureConnector.class.ts +110 -110
  200. package/src/subsystems/Security/Vault.service/Vault.helper.ts +30 -30
  201. package/src/subsystems/Security/Vault.service/VaultConnector.ts +29 -29
  202. package/src/subsystems/Security/Vault.service/connectors/HashicorpVault.class.ts +46 -46
  203. package/src/subsystems/Security/Vault.service/connectors/JSONFileVault.class.ts +221 -221
  204. package/src/subsystems/Security/Vault.service/connectors/NullVault.class.ts +54 -54
  205. package/src/subsystems/Security/Vault.service/connectors/SecretsManager.class.ts +140 -140
  206. package/src/subsystems/Security/Vault.service/index.ts +12 -12
  207. package/src/types/ACL.types.ts +104 -104
  208. package/src/types/AWS.types.ts +10 -10
  209. package/src/types/Agent.types.ts +61 -61
  210. package/src/types/AgentLogger.types.ts +17 -17
  211. package/src/types/Cache.types.ts +1 -1
  212. package/src/types/Common.types.ts +2 -2
  213. package/src/types/LLM.types.ts +496 -491
  214. package/src/types/Redis.types.ts +8 -8
  215. package/src/types/SRE.types.ts +64 -64
  216. package/src/types/Security.types.ts +14 -14
  217. package/src/types/Storage.types.ts +5 -5
  218. package/src/types/VectorDB.types.ts +86 -86
  219. package/src/utils/base64.utils.ts +275 -275
  220. package/src/utils/cli.utils.ts +68 -68
  221. package/src/utils/data.utils.ts +322 -322
  222. package/src/utils/date-time.utils.ts +22 -22
  223. package/src/utils/general.utils.ts +238 -238
  224. package/src/utils/index.ts +12 -12
  225. package/src/utils/lazy-client.ts +261 -261
  226. package/src/utils/numbers.utils.ts +13 -13
  227. package/src/utils/oauth.utils.ts +35 -35
  228. package/src/utils/string.utils.ts +414 -414
  229. package/src/utils/url.utils.ts +19 -19
  230. package/src/utils/validation.utils.ts +74 -74
  231. package/dist/bundle-analysis-lazy.html +0 -4949
  232. package/dist/bundle-analysis.html +0 -4949
  233. package/dist/types/utils/package-manager.utils.d.ts +0 -26
@@ -1,528 +1,528 @@
1
- import EventEmitter from 'events';
2
- import OpenAI from 'openai';
3
- import { BinaryInput } from '@sre/helpers/BinaryInput.helper';
4
- import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
5
- import {
6
- TLLMParams,
7
- TLLMPreparedParams,
8
- ILLMRequestContext,
9
- ToolData,
10
- TLLMMessageRole,
11
- APIKeySource,
12
- TLLMEvent,
13
- OpenAIToolDefinition,
14
- LegacyToolDefinition,
15
- } from '@sre/types/LLM.types';
16
- import { OpenAIApiInterface, ToolConfig } from './OpenAIApiInterface';
17
- import { HandlerDependencies } from '../types';
18
- import { JSON_RESPONSE_INSTRUCTION, SUPPORTED_MIME_TYPES_MAP } from '@sre/constants';
19
- import {
20
- MODELS_WITHOUT_PRESENCE_PENALTY_SUPPORT,
21
- MODELS_WITHOUT_TEMPERATURE_SUPPORT,
22
- MODELS_WITHOUT_SYSTEM_MESSAGE_SUPPORT,
23
- MODELS_WITHOUT_JSON_RESPONSE_SUPPORT,
24
- } from './constants';
25
-
26
- // File size limits in bytes
27
- const MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB
28
- const MAX_DOCUMENT_SIZE = 25 * 1024 * 1024; // 25MB
29
-
30
- /**
31
- * OpenAI Chat Completions API interface implementation
32
- * Handles all Chat Completions API-specific logic including:
33
- * - Stream creation and handling
34
- * - Request body preparation
35
- * - Tool and message transformations
36
- * - File attachment handling
37
- */
38
- export class ChatCompletionsApiInterface extends OpenAIApiInterface {
39
- private deps: HandlerDependencies;
40
- private validImageMimeTypes = SUPPORTED_MIME_TYPES_MAP.OpenAI.image;
41
- private validDocumentMimeTypes = SUPPORTED_MIME_TYPES_MAP.OpenAI.document;
42
-
43
- constructor(context: ILLMRequestContext, deps: HandlerDependencies) {
44
- super(context);
45
- this.deps = deps;
46
- }
47
-
48
- public async createRequest(body: OpenAI.ChatCompletionCreateParams, context: ILLMRequestContext): Promise<OpenAI.ChatCompletion> {
49
- const openai = await this.deps.getClient(context);
50
- return await openai.chat.completions.create({
51
- ...body,
52
- stream: false,
53
- });
54
- }
55
-
56
- public async createStream(
57
- body: OpenAI.ChatCompletionCreateParams,
58
- context: ILLMRequestContext
59
- ): Promise<AsyncIterable<OpenAI.ChatCompletionChunk>> {
60
- const openai = await this.deps.getClient(context);
61
- return await openai.chat.completions.create({
62
- ...body,
63
- stream: true,
64
- stream_options: { include_usage: true },
65
- });
66
- }
67
-
68
- public handleStream(stream: AsyncIterable<OpenAI.ChatCompletionChunk>, context: ILLMRequestContext): EventEmitter {
69
- const emitter = new EventEmitter();
70
- const usage_data: OpenAI.Completions.CompletionUsage[] = [];
71
- const reportedUsage: any[] = [];
72
- let finishReason = 'stop';
73
-
74
- // Process stream asynchronously while returning emitter immediately
75
- (async () => {
76
- let finalToolsData: ToolData[] = [];
77
-
78
- try {
79
- // Step 1: Process the stream
80
- const streamResult = await this.processStream(stream, emitter, usage_data);
81
- finalToolsData = streamResult.toolsData;
82
- finishReason = streamResult.finishReason;
83
-
84
- // Step 2: Report usage statistics
85
- this.reportUsageStatistics(usage_data, context, reportedUsage);
86
-
87
- // Step 3: Emit final events
88
- this.emitFinalEvents(emitter, finalToolsData, reportedUsage, finishReason);
89
- } catch (error) {
90
- emitter.emit('error', error);
91
- }
92
- })();
93
-
94
- return emitter;
95
- }
96
-
97
- public async prepareRequestBody(params: TLLMPreparedParams): Promise<OpenAI.ChatCompletionCreateParams> {
98
- let messages = await this.prepareMessages(params);
99
-
100
- // Convert system messages for models that don't support them
101
- if (MODELS_WITHOUT_SYSTEM_MESSAGE_SUPPORT.includes(params.modelEntryName)) {
102
- messages = this.convertSystemMessagesToUserMessages(messages);
103
- }
104
-
105
- // Handle JSON response format
106
- if (params.responseFormat === 'json') {
107
- const supportsSystemMessages = !MODELS_WITHOUT_SYSTEM_MESSAGE_SUPPORT.includes(params.modelEntryName);
108
-
109
- if (supportsSystemMessages) {
110
- // For models that support system messages
111
- if (messages?.[0]?.role === TLLMMessageRole.System) {
112
- messages[0] = { ...messages[0], content: messages[0].content + JSON_RESPONSE_INSTRUCTION };
113
- } else {
114
- messages.unshift({ role: TLLMMessageRole.System, content: JSON_RESPONSE_INSTRUCTION });
115
- }
116
- } else {
117
- // For models that don't support system messages, prepend to first user message
118
- const firstUserMessageIndex = messages.findIndex((msg) => msg.role === TLLMMessageRole.User);
119
- if (firstUserMessageIndex !== -1) {
120
- const userMessage = messages[firstUserMessageIndex];
121
- const content = typeof userMessage.content === 'string' ? userMessage.content : '';
122
- messages[firstUserMessageIndex] = {
123
- ...userMessage,
124
- content: JSON_RESPONSE_INSTRUCTION + '\n\n' + content,
125
- };
126
- } else {
127
- // If no user message exists, create one with the instruction
128
- messages.push({ role: TLLMMessageRole.User, content: JSON_RESPONSE_INSTRUCTION });
129
- }
130
- }
131
-
132
- params.responseFormat = { type: 'json_object' };
133
- }
134
-
135
- const body: OpenAI.ChatCompletionCreateParams = {
136
- model: params.model as string,
137
- messages,
138
- };
139
-
140
- // Handle max tokens
141
- if (params?.maxTokens !== undefined) {
142
- body.max_completion_tokens = params.maxTokens;
143
- }
144
-
145
- // Handle temperature
146
- if (params?.temperature !== undefined && !MODELS_WITHOUT_TEMPERATURE_SUPPORT.includes(params.modelEntryName)) {
147
- body.temperature = params.temperature;
148
- }
149
-
150
- // Handle topP
151
- if (params?.topP !== undefined) {
152
- body.top_p = params.topP;
153
- }
154
-
155
- // Handle frequency penalty
156
- if (params?.frequencyPenalty !== undefined) {
157
- body.frequency_penalty = params.frequencyPenalty;
158
- }
159
-
160
- // Handle presence penalty
161
- if (params?.presencePenalty !== undefined && !MODELS_WITHOUT_PRESENCE_PENALTY_SUPPORT.includes(params.modelEntryName)) {
162
- body.presence_penalty = params.presencePenalty;
163
- }
164
-
165
- // Handle response format
166
- if (params?.responseFormat?.type && !MODELS_WITHOUT_JSON_RESPONSE_SUPPORT.includes(params.modelEntryName)) {
167
- body.response_format = params.responseFormat;
168
- }
169
-
170
- // Handle stop sequences
171
- if (params?.stopSequences?.length) {
172
- body.stop = params.stopSequences;
173
- }
174
-
175
- // Handle tools configuration
176
- if (params?.toolsConfig?.tools && params?.toolsConfig?.tools?.length > 0) {
177
- body.tools = params?.toolsConfig?.tools as OpenAI.ChatCompletionTool[];
178
- body.tool_choice = params?.toolsConfig?.tool_choice;
179
- }
180
-
181
- return body;
182
- }
183
-
184
- /**
185
- * Type guard to check if a tool is an OpenAI tool definition
186
- */
187
- private isOpenAIToolDefinition(tool: OpenAIToolDefinition | LegacyToolDefinition): tool is OpenAIToolDefinition {
188
- return 'parameters' in tool;
189
- }
190
-
191
- /**
192
- * Transform OpenAI tool definitions to ChatCompletionTool format
193
- */
194
- public transformToolsConfig(config: ToolConfig): OpenAI.ChatCompletionTool[] {
195
- return config.toolDefinitions.map((tool) => {
196
- // Handle OpenAI tool definition format
197
- if (this.isOpenAIToolDefinition(tool)) {
198
- return {
199
- type: 'function',
200
- function: {
201
- name: tool.name,
202
- description: tool.description,
203
- parameters: tool.parameters,
204
- },
205
- };
206
- }
207
-
208
- // Handle legacy format for backward compatibility
209
- return {
210
- type: 'function',
211
- function: {
212
- name: tool.name,
213
- description: tool.description,
214
- parameters: {
215
- type: 'object',
216
- properties: tool.properties || {},
217
- required: tool.requiredFields || [],
218
- },
219
- },
220
- };
221
- });
222
- }
223
-
224
- public async handleFileAttachments(
225
- files: BinaryInput[],
226
- agentId: string,
227
- messages: OpenAI.ChatCompletionMessageParam[]
228
- ): Promise<OpenAI.ChatCompletionMessageParam[]> {
229
- if (files.length === 0) return messages;
230
-
231
- const uploadedFiles = await this.uploadFiles(files, agentId);
232
- const validImageFiles = this.getValidImageFiles(uploadedFiles);
233
- const validDocumentFiles = this.getValidDocumentFiles(uploadedFiles);
234
-
235
- // Process images and documents with Chat Completions specific formatting
236
- const imageData = await this.processImageData(validImageFiles, agentId);
237
- const documentData = await this.processDocumentData(validDocumentFiles, agentId);
238
-
239
- // For Chat Completions, we modify the last user message
240
- const messagesCopy = [...messages];
241
- const userMessage =
242
- Array.isArray(messagesCopy) && messagesCopy.length > 0 ? messagesCopy[messagesCopy.length - 1] : { role: 'user', content: '' };
243
- const prompt = userMessage?.content && typeof userMessage.content === 'string' ? userMessage.content : '';
244
-
245
- const promptData = [{ type: 'text', text: prompt || '' }, ...imageData, ...documentData];
246
-
247
- // Replace the last message or add a new one if array was empty
248
- if (messagesCopy.length > 0) {
249
- messagesCopy[messagesCopy.length - 1] = { role: 'user', content: promptData };
250
- } else {
251
- messagesCopy.push({ role: 'user', content: promptData });
252
- }
253
-
254
- return messagesCopy;
255
- }
256
-
257
- /**
258
- * Process the chat completions API stream format
259
- */
260
- private async processStream(
261
- stream: AsyncIterable<OpenAI.ChatCompletionChunk>,
262
- emitter: EventEmitter,
263
- usage_data: OpenAI.Completions.CompletionUsage[]
264
- ): Promise<{ toolsData: ToolData[]; finishReason: string }> {
265
- let toolsData: ToolData[] = [];
266
- let finishReason = 'stop';
267
-
268
- for await (const part of stream) {
269
- const delta = part.choices[0]?.delta;
270
- const usage = part.usage;
271
-
272
- // Collect usage statistics
273
- if (usage) {
274
- usage_data.push(usage);
275
- }
276
-
277
- // Emit data event for delta
278
- emitter.emit('data', delta);
279
-
280
- // Handle content deltas
281
- if (!delta?.tool_calls && delta?.content) {
282
- emitter.emit('content', delta?.content, delta?.role);
283
- }
284
-
285
- // Handle tool calls
286
- if (delta?.tool_calls) {
287
- const toolCall = delta?.tool_calls?.[0];
288
- const index = toolCall?.index;
289
-
290
- if (!toolsData[index]) {
291
- toolsData[index] = {
292
- index: index || 0,
293
- id: '',
294
- type: 'function',
295
- name: '',
296
- arguments: '',
297
- role: 'tool',
298
- };
299
- }
300
-
301
- if (toolCall?.function?.name) {
302
- toolsData[index].name = toolCall.function.name;
303
- }
304
- if (toolCall?.function?.arguments) {
305
- toolsData[index].arguments += toolCall.function.arguments;
306
- }
307
- if (toolCall?.id) {
308
- toolsData[index].id = toolCall.id;
309
- }
310
- }
311
-
312
- // Handle finish reason
313
- if (part.choices[0]?.finish_reason) {
314
- finishReason = part.choices[0].finish_reason;
315
- }
316
- }
317
-
318
- return { toolsData: this.extractToolCalls(toolsData), finishReason };
319
- }
320
-
321
- /**
322
- * Extract and format tool calls from the accumulated data
323
- */
324
- private extractToolCalls(toolsData: ToolData[]): ToolData[] {
325
- return toolsData.map((tool) => ({
326
- index: tool.index,
327
- name: tool.name,
328
- arguments: tool.arguments,
329
- id: tool.id,
330
- type: tool.type,
331
- role: tool.role,
332
- }));
333
- }
334
-
335
- /**
336
- * Report usage statistics
337
- */
338
- private reportUsageStatistics(usage_data: OpenAI.Completions.CompletionUsage[], context: ILLMRequestContext, reportedUsage: any[]): void {
339
- // Report normal usage
340
- usage_data.forEach((usage) => {
341
- const reported = this.deps.reportUsage(usage, this.buildUsageContext(context));
342
- reportedUsage.push(reported);
343
- });
344
- }
345
-
346
- /**
347
- * Emit final events
348
- */
349
- private emitFinalEvents(emitter: EventEmitter, toolsData: ToolData[], reportedUsage: any[], finishReason: string): void {
350
- // Emit tool info event if tools were called
351
- if (toolsData.length > 0) {
352
- emitter.emit(TLLMEvent.ToolInfo, toolsData);
353
- }
354
-
355
- // Emit interrupted event if finishReason is not 'stop'
356
- if (finishReason !== 'stop') {
357
- emitter.emit('interrupted', finishReason);
358
- }
359
-
360
- // Emit end event with setImmediate to ensure proper event ordering
361
- setImmediate(() => {
362
- emitter.emit('end', toolsData, reportedUsage, finishReason);
363
- });
364
- }
365
-
366
- /**
367
- * Build usage context parameters from request context
368
- */
369
- private buildUsageContext(context: ILLMRequestContext) {
370
- return {
371
- modelEntryName: context.modelEntryName,
372
- keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
373
- agentId: context.agentId,
374
- teamId: context.teamId,
375
- };
376
- }
377
-
378
- /**
379
- * Get valid image files based on supported MIME types
380
- */
381
- private getValidImageFiles(files: BinaryInput[]): BinaryInput[] {
382
- return files.filter((file) => this.validImageMimeTypes.includes(file?.mimetype));
383
- }
384
-
385
- /**
386
- * Get valid document files based on supported MIME types
387
- */
388
- private getValidDocumentFiles(files: BinaryInput[]): BinaryInput[] {
389
- return files.filter((file) => this.validDocumentMimeTypes.includes(file?.mimetype));
390
- }
391
-
392
- /**
393
- * Upload files to storage
394
- */
395
- private async uploadFiles(files: BinaryInput[], agentId: string): Promise<BinaryInput[]> {
396
- const promises = files.map((file) => {
397
- const binaryInput = BinaryInput.from(file);
398
- return binaryInput.upload(AccessCandidate.agent(agentId)).then(() => binaryInput);
399
- });
400
-
401
- return Promise.all(promises);
402
- }
403
-
404
- /**
405
- * Process image files with Chat Completions specific formatting
406
- */
407
- private async processImageData(files: BinaryInput[], agentId: string): Promise<any[]> {
408
- if (files.length === 0) return [];
409
-
410
- const imageData = [];
411
- for (const file of files) {
412
- await this.validateFileSize(file, MAX_IMAGE_SIZE, 'Image', agentId);
413
-
414
- const bufferData = await file.readData(AccessCandidate.agent(agentId));
415
- const base64Data = bufferData.toString('base64');
416
- const url = `data:${file.mimetype};base64,${base64Data}`;
417
-
418
- imageData.push({
419
- type: 'image_url',
420
- image_url: { url },
421
- });
422
- }
423
-
424
- return imageData;
425
- }
426
-
427
- /**
428
- * Process document files with Chat Completions specific formatting
429
- */
430
- private async processDocumentData(files: BinaryInput[], agentId: string): Promise<any[]> {
431
- if (files.length === 0) return [];
432
-
433
- const documentData = [];
434
- for (const file of files) {
435
- await this.validateFileSize(file, MAX_DOCUMENT_SIZE, 'Document', agentId);
436
-
437
- const bufferData = await file.readData(AccessCandidate.agent(agentId));
438
- const base64Data = bufferData.toString('base64');
439
- const fileData = `data:${file.mimetype};base64,${base64Data}`;
440
- const filename = await file.getName();
441
-
442
- documentData.push({
443
- type: 'file',
444
- file: {
445
- file_data: fileData,
446
- filename,
447
- },
448
- });
449
- }
450
-
451
- return documentData;
452
- }
453
-
454
- /**
455
- * Validate file size before processing
456
- */
457
- private async validateFileSize(file: BinaryInput, maxSize: number, fileType: string, agentId: string): Promise<void> {
458
- await file.ready();
459
- const fileInfo = await file.getJsonData(AccessCandidate.agent(agentId));
460
- if (fileInfo.size > maxSize) {
461
- throw new Error(`${fileType} file size (${fileInfo.size} bytes) exceeds maximum allowed size of ${maxSize} bytes`);
462
- }
463
- }
464
-
465
- getInterfaceName(): string {
466
- return 'chat.completions';
467
- }
468
-
469
- validateParameters(params: TLLMParams): boolean {
470
- // Basic validation for Chat Completions parameters
471
- return !!params.model && Array.isArray(params.messages);
472
- }
473
-
474
- /**
475
- * Convert system messages to user messages for models that don't support system messages
476
- */
477
- private convertSystemMessagesToUserMessages(messages: OpenAI.ChatCompletionMessageParam[]): OpenAI.ChatCompletionMessageParam[] {
478
- const convertedMessages: OpenAI.ChatCompletionMessageParam[] = [];
479
- const systemMessages: string[] = [];
480
-
481
- // Extract system messages and collect other messages
482
- for (const message of messages) {
483
- if (message.role === TLLMMessageRole.System) {
484
- const content = typeof message.content === 'string' ? message.content : '';
485
- if (content.trim()) {
486
- systemMessages.push(content);
487
- }
488
- } else {
489
- convertedMessages.push(message);
490
- }
491
- }
492
-
493
- // If we have system messages, prepend them to the first user message
494
- if (systemMessages.length > 0) {
495
- const systemContent = systemMessages.join('\n\n');
496
- const firstUserMessageIndex = convertedMessages.findIndex((msg) => msg.role === TLLMMessageRole.User);
497
-
498
- if (firstUserMessageIndex !== -1) {
499
- const userMessage = convertedMessages[firstUserMessageIndex];
500
- const existingContent = typeof userMessage.content === 'string' ? userMessage.content : '';
501
- convertedMessages[firstUserMessageIndex] = {
502
- ...userMessage,
503
- content: systemContent + '\n\n' + existingContent,
504
- };
505
- } else {
506
- // If no user message exists, create one with the system content
507
- convertedMessages.unshift({ role: TLLMMessageRole.User, content: systemContent });
508
- }
509
- }
510
-
511
- return convertedMessages;
512
- }
513
-
514
- /**
515
- * Prepare messages for Chat Completions API
516
- */
517
- private async prepareMessages(params: TLLMParams): Promise<OpenAI.ChatCompletionMessageParam[]> {
518
- const messages = params?.messages || [];
519
- const files: BinaryInput[] = params?.files || [];
520
-
521
- // Handle files if present
522
- if (files.length > 0) {
523
- return await this.handleFileAttachments(files, params.agentId, [...messages]);
524
- }
525
-
526
- return messages;
527
- }
528
- }
1
+ import EventEmitter from 'events';
2
+ import OpenAI from 'openai';
3
+ import { BinaryInput } from '@sre/helpers/BinaryInput.helper';
4
+ import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
5
+ import { TLLMParams, TLLMPreparedParams, ILLMRequestContext, ToolData, TLLMMessageRole, APIKeySource, TLLMEvent } from '@sre/types/LLM.types';
6
+ import { OpenAIApiInterface, ToolConfig } from './OpenAIApiInterface';
7
+ import { HandlerDependencies } from '../types';
8
+ import { JSON_RESPONSE_INSTRUCTION, SUPPORTED_MIME_TYPES_MAP } from '@sre/constants';
9
+ import {
10
+ MODELS_WITHOUT_PRESENCE_PENALTY_SUPPORT,
11
+ MODELS_WITHOUT_TEMPERATURE_SUPPORT,
12
+ MODELS_WITHOUT_SYSTEM_MESSAGE_SUPPORT,
13
+ MODELS_WITHOUT_JSON_RESPONSE_SUPPORT,
14
+ } from './constants';
15
+
16
+ import { isValidOpenAIReasoningEffort } from './utils';
17
+
18
+ // File size limits in bytes
19
+ const MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB
20
+ const MAX_DOCUMENT_SIZE = 25 * 1024 * 1024; // 25MB
21
+
22
+ /**
23
+ * OpenAI Chat Completions API interface implementation
24
+ * Handles all Chat Completions API-specific logic including:
25
+ * - Stream creation and handling
26
+ * - Request body preparation
27
+ * - Tool and message transformations
28
+ * - File attachment handling
29
+ */
30
+ export class ChatCompletionsApiInterface extends OpenAIApiInterface {
31
+ private deps: HandlerDependencies;
32
+ private validImageMimeTypes = SUPPORTED_MIME_TYPES_MAP.OpenAI.image;
33
+ private validDocumentMimeTypes = SUPPORTED_MIME_TYPES_MAP.OpenAI.document;
34
+
35
+ constructor(context: ILLMRequestContext, deps: HandlerDependencies) {
36
+ super(context);
37
+ this.deps = deps;
38
+ }
39
+
40
+ public async createRequest(body: OpenAI.ChatCompletionCreateParams, context: ILLMRequestContext): Promise<OpenAI.ChatCompletion> {
41
+ const openai = await this.deps.getClient(context);
42
+ return await openai.chat.completions.create({
43
+ ...body,
44
+ stream: false,
45
+ });
46
+ }
47
+
48
+ public async createStream(
49
+ body: OpenAI.ChatCompletionCreateParams,
50
+ context: ILLMRequestContext
51
+ ): Promise<AsyncIterable<OpenAI.ChatCompletionChunk>> {
52
+ const openai = await this.deps.getClient(context);
53
+ return await openai.chat.completions.create({
54
+ ...body,
55
+ stream: true,
56
+ stream_options: { include_usage: true },
57
+ });
58
+ }
59
+
60
+ public handleStream(stream: AsyncIterable<OpenAI.ChatCompletionChunk>, context: ILLMRequestContext): EventEmitter {
61
+ const emitter = new EventEmitter();
62
+
63
+ // Process stream asynchronously while returning emitter immediately
64
+ (async () => {
65
+ let finalToolsData: ToolData[] = [];
66
+
67
+ try {
68
+ // Step 1: Process the stream
69
+ const streamResult = await this.processStream(stream, emitter);
70
+ finalToolsData = streamResult.toolsData;
71
+
72
+ const finishReason = streamResult.finishReason || 'stop';
73
+ const usageData = streamResult.usageData;
74
+
75
+ // Step 2: Report usage statistics
76
+ const reportedUsage = this.reportUsageStatistics(usageData, context);
77
+
78
+ // Step 3: Emit final events
79
+ this.emitFinalEvents(emitter, finalToolsData, reportedUsage, finishReason);
80
+ } catch (error) {
81
+ emitter.emit('error', error);
82
+ }
83
+ })();
84
+
85
+ return emitter;
86
+ }
87
+
88
+ public async prepareRequestBody(params: TLLMPreparedParams): Promise<OpenAI.ChatCompletionCreateParams> {
89
+ let messages = await this.prepareMessages(params);
90
+
91
+ // Convert system messages for models that don't support them
92
+ if (MODELS_WITHOUT_SYSTEM_MESSAGE_SUPPORT.includes(params.modelEntryName)) {
93
+ messages = this.convertSystemMessagesToUserMessages(messages);
94
+ }
95
+
96
+ // Handle JSON response format
97
+ if (params.responseFormat === 'json') {
98
+ const supportsSystemMessages = !MODELS_WITHOUT_SYSTEM_MESSAGE_SUPPORT.includes(params.modelEntryName);
99
+
100
+ if (supportsSystemMessages) {
101
+ // For models that support system messages
102
+ if (messages?.[0]?.role === TLLMMessageRole.System) {
103
+ messages[0] = { ...messages[0], content: messages[0].content + JSON_RESPONSE_INSTRUCTION };
104
+ } else {
105
+ messages.unshift({ role: TLLMMessageRole.System, content: JSON_RESPONSE_INSTRUCTION });
106
+ }
107
+ } else {
108
+ // For models that don't support system messages, prepend to first user message
109
+ const firstUserMessageIndex = messages.findIndex((msg) => msg.role === TLLMMessageRole.User);
110
+ if (firstUserMessageIndex !== -1) {
111
+ const userMessage = messages[firstUserMessageIndex];
112
+ const content = typeof userMessage.content === 'string' ? userMessage.content : '';
113
+ messages[firstUserMessageIndex] = {
114
+ ...userMessage,
115
+ content: JSON_RESPONSE_INSTRUCTION + '\n\n' + content,
116
+ };
117
+ } else {
118
+ // If no user message exists, create one with the instruction
119
+ messages.push({ role: TLLMMessageRole.User, content: JSON_RESPONSE_INSTRUCTION });
120
+ }
121
+ }
122
+
123
+ params.responseFormat = { type: 'json_object' };
124
+ }
125
+
126
+ const body: OpenAI.ChatCompletionCreateParams = {
127
+ model: params.model as string,
128
+ messages,
129
+ };
130
+
131
+ // Handle max tokens
132
+ if (params?.maxTokens !== undefined) {
133
+ body.max_completion_tokens = params.maxTokens;
134
+ }
135
+
136
+ // Handle temperature
137
+ if (params?.temperature !== undefined && !MODELS_WITHOUT_TEMPERATURE_SUPPORT.includes(params.modelEntryName)) {
138
+ body.temperature = params.temperature;
139
+ }
140
+
141
+ // Handle topP
142
+ if (params?.topP !== undefined) {
143
+ body.top_p = params.topP;
144
+ }
145
+
146
+ // Handle frequency penalty
147
+ if (params?.frequencyPenalty !== undefined) {
148
+ body.frequency_penalty = params.frequencyPenalty;
149
+ }
150
+
151
+ // Handle presence penalty
152
+ if (params?.presencePenalty !== undefined && !MODELS_WITHOUT_PRESENCE_PENALTY_SUPPORT.includes(params.modelEntryName)) {
153
+ body.presence_penalty = params.presencePenalty;
154
+ }
155
+
156
+ // Handle response format
157
+ if (params?.responseFormat?.type && !MODELS_WITHOUT_JSON_RESPONSE_SUPPORT.includes(params.modelEntryName)) {
158
+ body.response_format = params.responseFormat;
159
+ }
160
+
161
+ // Handle stop sequences
162
+ if (params?.stopSequences?.length) {
163
+ body.stop = params.stopSequences;
164
+ }
165
+
166
+ // #region GPT 5 specific fields
167
+ const isGPT5ReasoningModels = params.modelEntryName?.includes('gpt-5') && params?.capabilities?.reasoning;
168
+ if (isGPT5ReasoningModels && params?.verbosity) {
169
+ body.verbosity = params.verbosity;
170
+ }
171
+
172
+ // We need to validate the `reasoningEffort` parameter for OpenAI models, since models like `qwen/qwen3-32b` and `deepseek-r1-distill-llama-70b` (available via Groq) also support this parameter but use different values, such as `none` and `default`. These values are valid in our system but not specifically for OpenAI.
173
+ if (isGPT5ReasoningModels && isValidOpenAIReasoningEffort(params.reasoningEffort)) {
174
+ body.reasoning_effort = params.reasoningEffort;
175
+ }
176
+ // #endregion GPT 5 specific fields
177
+
178
+ // Handle tools configuration
179
+ if (params?.toolsConfig?.tools && params?.toolsConfig?.tools?.length > 0) {
180
+ body.tools = params?.toolsConfig?.tools as OpenAI.ChatCompletionTool[];
181
+ body.tool_choice = params?.toolsConfig?.tool_choice;
182
+ }
183
+
184
+ return body;
185
+ }
186
+
187
+ /**
188
+ * Transform OpenAI tool definitions to ChatCompletionTool format
189
+ */
190
+ public transformToolsConfig(config: ToolConfig): OpenAI.ChatCompletionTool[] {
191
+ return config.toolDefinitions.map((tool) => {
192
+ // Handle OpenAI tool definition format
193
+ if ('parameters' in tool) {
194
+ return {
195
+ type: 'function',
196
+ function: {
197
+ name: tool.name,
198
+ description: tool.description,
199
+ parameters: tool.parameters,
200
+ },
201
+ };
202
+ }
203
+
204
+ // Handle legacy format for backward compatibility
205
+ return {
206
+ type: 'function',
207
+ function: {
208
+ name: tool.name,
209
+ description: tool.description,
210
+ parameters: {
211
+ type: 'object',
212
+ properties: tool.properties || {},
213
+ required: tool.requiredFields || [],
214
+ },
215
+ },
216
+ };
217
+ });
218
+ }
219
+
220
+ public async handleFileAttachments(
221
+ files: BinaryInput[],
222
+ agentId: string,
223
+ messages: OpenAI.ChatCompletionMessageParam[]
224
+ ): Promise<OpenAI.ChatCompletionMessageParam[]> {
225
+ if (files.length === 0) return messages;
226
+
227
+ const uploadedFiles = await this.uploadFiles(files, agentId);
228
+ const validImageFiles = this.getValidImageFiles(uploadedFiles);
229
+ const validDocumentFiles = this.getValidDocumentFiles(uploadedFiles);
230
+
231
+ // Process images and documents with Chat Completions specific formatting
232
+ const imageData = await this.processImageData(validImageFiles, agentId);
233
+ const documentData = await this.processDocumentData(validDocumentFiles, agentId);
234
+
235
+ // For Chat Completions, we modify the last user message
236
+ const messagesCopy = [...messages];
237
+ const userMessage =
238
+ Array.isArray(messagesCopy) && messagesCopy.length > 0 ? messagesCopy[messagesCopy.length - 1] : { role: 'user', content: '' };
239
+ const prompt = userMessage?.content && typeof userMessage.content === 'string' ? userMessage.content : '';
240
+
241
+ const promptData = [{ type: 'text', text: prompt || '' }, ...imageData, ...documentData];
242
+
243
+ // Replace the last message or add a new one if array was empty
244
+ if (messagesCopy.length > 0) {
245
+ messagesCopy[messagesCopy.length - 1] = { role: 'user', content: promptData };
246
+ } else {
247
+ messagesCopy.push({ role: 'user', content: promptData });
248
+ }
249
+
250
+ return messagesCopy;
251
+ }
252
+
253
+ /**
254
+ * Process the chat completions API stream format
255
+ */
256
+ private async processStream(
257
+ stream: AsyncIterable<OpenAI.ChatCompletionChunk>,
258
+ emitter: EventEmitter
259
+ ): Promise<{ toolsData: ToolData[]; finishReason: string; usageData: any[] }> {
260
+ let toolsData: ToolData[] = [];
261
+ let finishReason = 'stop';
262
+ const usageData = [];
263
+
264
+ for await (const part of stream) {
265
+ const delta = part.choices[0]?.delta;
266
+ const usage = part.usage;
267
+
268
+ // Collect usage statistics
269
+ if (usage) {
270
+ usageData.push(usage);
271
+ }
272
+
273
+ // Emit data event for delta
274
+ emitter.emit('data', delta);
275
+
276
+ // Handle content deltas
277
+ if (!delta?.tool_calls && delta?.content) {
278
+ emitter.emit('content', delta?.content, delta?.role);
279
+ }
280
+
281
+ // Handle tool calls
282
+ if (delta?.tool_calls) {
283
+ const toolCall = delta?.tool_calls?.[0];
284
+ const index = toolCall?.index;
285
+
286
+ if (!toolsData[index]) {
287
+ toolsData[index] = {
288
+ index: index || 0,
289
+ id: '',
290
+ type: 'function',
291
+ name: '',
292
+ arguments: '',
293
+ role: 'tool',
294
+ };
295
+ }
296
+
297
+ if (toolCall?.function?.name) {
298
+ toolsData[index].name = toolCall.function.name;
299
+ }
300
+ if (toolCall?.function?.arguments) {
301
+ toolsData[index].arguments += toolCall.function.arguments;
302
+ }
303
+ if (toolCall?.id) {
304
+ toolsData[index].id = toolCall.id;
305
+ }
306
+ }
307
+
308
+ // Handle finish reason
309
+ if (part.choices[0]?.finish_reason) {
310
+ finishReason = part.choices[0].finish_reason;
311
+ }
312
+ }
313
+
314
+ return { toolsData: this.extractToolCalls(toolsData), finishReason, usageData };
315
+ }
316
+
317
+ /**
318
+ * Extract and format tool calls from the accumulated data
319
+ */
320
+ private extractToolCalls(toolsData: ToolData[]): ToolData[] {
321
+ return toolsData.map((tool) => ({
322
+ index: tool.index,
323
+ name: tool.name,
324
+ arguments: tool.arguments,
325
+ id: tool.id,
326
+ type: tool.type,
327
+ role: tool.role,
328
+ }));
329
+ }
330
+
331
+ /**
332
+ * Report usage statistics
333
+ */
334
+ private reportUsageStatistics(usage_data: OpenAI.Completions.CompletionUsage[], context: ILLMRequestContext): any[] {
335
+ const reportedUsage: any[] = [];
336
+
337
+ // Report normal usage
338
+ usage_data.forEach((usage) => {
339
+ const reported = this.deps.reportUsage(usage, this.buildUsageContext(context));
340
+ reportedUsage.push(reported);
341
+ });
342
+
343
+ return reportedUsage;
344
+ }
345
+
346
+ /**
347
+ * Emit final events
348
+ */
349
+ private emitFinalEvents(emitter: EventEmitter, toolsData: ToolData[], reportedUsage: any[], finishReason: string): void {
350
+ // Emit tool info event if tools were called
351
+ if (toolsData.length > 0) {
352
+ emitter.emit(TLLMEvent.ToolInfo, toolsData);
353
+ }
354
+
355
+ // Emit interrupted event if finishReason is not 'stop'
356
+ if (finishReason !== 'stop') {
357
+ emitter.emit('interrupted', finishReason);
358
+ }
359
+
360
+ // Emit end event with setImmediate to ensure proper event ordering
361
+ setImmediate(() => {
362
+ emitter.emit('end', toolsData, reportedUsage, finishReason);
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Build usage context parameters from request context
368
+ */
369
+ private buildUsageContext(context: ILLMRequestContext) {
370
+ return {
371
+ modelEntryName: context.modelEntryName,
372
+ keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
373
+ agentId: context.agentId,
374
+ teamId: context.teamId,
375
+ };
376
+ }
377
+
378
+ /**
379
+ * Get valid image files based on supported MIME types
380
+ */
381
+ private getValidImageFiles(files: BinaryInput[]): BinaryInput[] {
382
+ return files.filter((file) => this.validImageMimeTypes.includes(file?.mimetype));
383
+ }
384
+
385
+ /**
386
+ * Get valid document files based on supported MIME types
387
+ */
388
+ private getValidDocumentFiles(files: BinaryInput[]): BinaryInput[] {
389
+ return files.filter((file) => this.validDocumentMimeTypes.includes(file?.mimetype));
390
+ }
391
+
392
+ /**
393
+ * Upload files to storage
394
+ */
395
+ private async uploadFiles(files: BinaryInput[], agentId: string): Promise<BinaryInput[]> {
396
+ const promises = files.map((file) => {
397
+ const binaryInput = BinaryInput.from(file);
398
+ return binaryInput.upload(AccessCandidate.agent(agentId)).then(() => binaryInput);
399
+ });
400
+
401
+ return Promise.all(promises);
402
+ }
403
+
404
+ /**
405
+ * Process image files with Chat Completions specific formatting
406
+ */
407
+ private async processImageData(files: BinaryInput[], agentId: string): Promise<any[]> {
408
+ if (files.length === 0) return [];
409
+
410
+ const imageData = [];
411
+ for (const file of files) {
412
+ await this.validateFileSize(file, MAX_IMAGE_SIZE, 'Image', agentId);
413
+
414
+ const bufferData = await file.readData(AccessCandidate.agent(agentId));
415
+ const base64Data = bufferData.toString('base64');
416
+ const url = `data:${file.mimetype};base64,${base64Data}`;
417
+
418
+ imageData.push({
419
+ type: 'image_url',
420
+ image_url: { url },
421
+ });
422
+ }
423
+
424
+ return imageData;
425
+ }
426
+
427
+ /**
428
+ * Process document files with Chat Completions specific formatting
429
+ */
430
+ private async processDocumentData(files: BinaryInput[], agentId: string): Promise<any[]> {
431
+ if (files.length === 0) return [];
432
+
433
+ const documentData = [];
434
+ for (const file of files) {
435
+ await this.validateFileSize(file, MAX_DOCUMENT_SIZE, 'Document', agentId);
436
+
437
+ const bufferData = await file.readData(AccessCandidate.agent(agentId));
438
+ const base64Data = bufferData.toString('base64');
439
+ const fileData = `data:${file.mimetype};base64,${base64Data}`;
440
+ const filename = await file.getName();
441
+
442
+ documentData.push({
443
+ type: 'file',
444
+ file: {
445
+ file_data: fileData,
446
+ filename,
447
+ },
448
+ });
449
+ }
450
+
451
+ return documentData;
452
+ }
453
+
454
+ /**
455
+ * Validate file size before processing
456
+ */
457
+ private async validateFileSize(file: BinaryInput, maxSize: number, fileType: string, agentId: string): Promise<void> {
458
+ await file.ready();
459
+ const fileInfo = await file.getJsonData(AccessCandidate.agent(agentId));
460
+ if (fileInfo.size > maxSize) {
461
+ throw new Error(`${fileType} file size (${fileInfo.size} bytes) exceeds maximum allowed size of ${maxSize} bytes`);
462
+ }
463
+ }
464
+
465
+ getInterfaceName(): string {
466
+ return 'chat.completions';
467
+ }
468
+
469
+ validateParameters(params: TLLMParams): boolean {
470
+ // Basic validation for Chat Completions parameters
471
+ return !!params.model && Array.isArray(params.messages);
472
+ }
473
+
474
+ /**
475
+ * Convert system messages to user messages for models that don't support system messages
476
+ */
477
+ private convertSystemMessagesToUserMessages(messages: OpenAI.ChatCompletionMessageParam[]): OpenAI.ChatCompletionMessageParam[] {
478
+ const convertedMessages: OpenAI.ChatCompletionMessageParam[] = [];
479
+ const systemMessages: string[] = [];
480
+
481
+ // Extract system messages and collect other messages
482
+ for (const message of messages) {
483
+ if (message.role === TLLMMessageRole.System) {
484
+ const content = typeof message.content === 'string' ? message.content : '';
485
+ if (content.trim()) {
486
+ systemMessages.push(content);
487
+ }
488
+ } else {
489
+ convertedMessages.push(message);
490
+ }
491
+ }
492
+
493
+ // If we have system messages, prepend them to the first user message
494
+ if (systemMessages.length > 0) {
495
+ const systemContent = systemMessages.join('\n\n');
496
+ const firstUserMessageIndex = convertedMessages.findIndex((msg) => msg.role === TLLMMessageRole.User);
497
+
498
+ if (firstUserMessageIndex !== -1) {
499
+ const userMessage = convertedMessages[firstUserMessageIndex];
500
+ const existingContent = typeof userMessage.content === 'string' ? userMessage.content : '';
501
+ convertedMessages[firstUserMessageIndex] = {
502
+ ...userMessage,
503
+ content: systemContent + '\n\n' + existingContent,
504
+ };
505
+ } else {
506
+ // If no user message exists, create one with the system content
507
+ convertedMessages.unshift({ role: TLLMMessageRole.User, content: systemContent });
508
+ }
509
+ }
510
+
511
+ return convertedMessages;
512
+ }
513
+
514
+ /**
515
+ * Prepare messages for Chat Completions API
516
+ */
517
+ private async prepareMessages(params: TLLMParams): Promise<OpenAI.ChatCompletionMessageParam[]> {
518
+ const messages = params?.messages || [];
519
+ const files: BinaryInput[] = params?.files || [];
520
+
521
+ // Handle files if present
522
+ if (files.length > 0) {
523
+ return await this.handleFileAttachments(files, params.agentId, [...messages]);
524
+ }
525
+
526
+ return messages;
527
+ }
528
+ }