@smythos/sre 1.5.32 → 1.5.34

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.
@@ -1,11 +1,11 @@
1
- import { IAgent as Agent } from '@sre/types/Agent.types';
1
+ import { IAgent } from '@sre/types/Agent.types';
2
2
  import Joi from 'joi';
3
3
  import { Component } from './Component.class';
4
4
  export declare class ImageGenerator extends Component {
5
5
  protected configSchema: Joi.ObjectSchema<any>;
6
6
  constructor();
7
7
  init(): void;
8
- process(input: any, config: any, agent: Agent): Promise<{
8
+ process(input: any, config: any, agent: IAgent): Promise<{
9
9
  _error: string;
10
10
  _debug: string;
11
11
  Output?: undefined;
@@ -48,6 +48,7 @@ export declare const SUPPORTED_MIME_TYPES_MAP: {
48
48
  };
49
49
  GoogleAI: {
50
50
  image: string[];
51
+ imageGen: string[];
51
52
  video: string[];
52
53
  audio: string[];
53
54
  document: string[];
@@ -165,7 +165,7 @@ export * from './subsystems/LLMManager/LLM.service/connectors/Groq.class';
165
165
  export * from './subsystems/LLMManager/LLM.service/connectors/OpenAI.class';
166
166
  export * from './subsystems/LLMManager/LLM.service/connectors/Perplexity.class';
167
167
  export * from './subsystems/LLMManager/LLM.service/connectors/VertexAI.class';
168
- export * from './subsystems/LLMManager/LLM.service/connectors/xAI';
168
+ export * from './subsystems/LLMManager/LLM.service/connectors/xAI.class';
169
169
  export * from './subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class';
170
170
  export * from './subsystems/MemoryManager/Cache.service/connectors/LocalStorageCache.class';
171
171
  export * from './subsystems/MemoryManager/Cache.service/connectors/RAMCache.class';
@@ -12,6 +12,8 @@ export declare class GoogleAIConnector extends LLMConnector {
12
12
  protected request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse>;
13
13
  protected streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter>;
14
14
  protected webSearchRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<any>;
15
+ protected imageGenRequest({ body, context }: ILLMRequestFuncParams): Promise<any>;
16
+ protected imageEditRequest({ body, context }: ILLMRequestFuncParams): Promise<any>;
15
17
  protected reqBodyAdapter(params: TLLMParams): Promise<TGoogleAIRequestBody>;
16
18
  protected reportUsage(usage: UsageMetadataWithThoughtsToken, metadata: {
17
19
  modelEntryName: string;
@@ -49,6 +51,7 @@ export declare class GoogleAIConnector extends LLMConnector {
49
51
  private prepareMessagesWithFiles;
50
52
  private prepareMessagesWithTools;
51
53
  private prepareMessagesWithTextQuery;
54
+ private prepareBodyForImageGenRequest;
52
55
  private sanitizeFunctionName;
53
56
  private uploadFile;
54
57
  private getValidFiles;
@@ -0,0 +1,121 @@
1
+ import EventEmitter from 'events';
2
+ import { TLLMParams, ToolData, TLLMMessageBlock, TLLMToolResultMessageBlock, TLLMMessageRole, APIKeySource, ILLMRequestFuncParams, TLLMChatResponse } from '@sre/types/LLM.types';
3
+ import { LLMConnector } from '../LLMConnector';
4
+ type ChatCompletionParams = {
5
+ model: string;
6
+ messages: any[];
7
+ max_tokens?: number;
8
+ temperature?: number;
9
+ top_p?: number;
10
+ frequency_penalty?: number;
11
+ presence_penalty?: number;
12
+ stop?: string | string[];
13
+ response_format?: {
14
+ type: string;
15
+ };
16
+ stream?: boolean;
17
+ stream_options?: {
18
+ include_usage: boolean;
19
+ };
20
+ tools?: any[];
21
+ tool_choice?: any;
22
+ search_parameters?: {
23
+ mode?: 'auto' | 'on' | 'off';
24
+ return_citations?: boolean;
25
+ max_search_results?: number;
26
+ sources?: Array<{
27
+ type: 'web' | 'x' | 'news' | 'rss';
28
+ country?: string;
29
+ excluded_websites?: string[];
30
+ allowed_websites?: string[];
31
+ safe_search?: boolean;
32
+ included_x_handles?: string[];
33
+ excluded_x_handles?: string[];
34
+ post_favorite_count?: number;
35
+ post_view_count?: number;
36
+ links?: string[];
37
+ }>;
38
+ from_date?: string;
39
+ to_date?: string;
40
+ };
41
+ };
42
+ type TUsage = {
43
+ prompt_tokens: number;
44
+ completion_tokens: number;
45
+ total_tokens: number;
46
+ prompt_tokens_details?: {
47
+ text_tokens?: number;
48
+ audio_tokens?: number;
49
+ image_tokens?: number;
50
+ cached_tokens?: number;
51
+ };
52
+ completion_tokens_details?: {
53
+ reasoning_tokens?: number;
54
+ audio_tokens?: number;
55
+ accepted_prediction_tokens?: number;
56
+ rejected_prediction_tokens?: number;
57
+ };
58
+ reasoning_tokens?: number;
59
+ num_sources_used?: number;
60
+ };
61
+ export declare class xAIConnector extends LLMConnector {
62
+ name: string;
63
+ private getClient;
64
+ protected request({ acRequest, body, context }: ILLMRequestFuncParams): Promise<TLLMChatResponse>;
65
+ protected streamRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter>;
66
+ protected webSearchRequest({ acRequest, body, context }: ILLMRequestFuncParams): Promise<EventEmitter>;
67
+ protected reqBodyAdapter(params: TLLMParams): Promise<ChatCompletionParams>;
68
+ protected reportUsage(usage: TUsage, metadata: {
69
+ modelEntryName: string;
70
+ keySource: APIKeySource;
71
+ agentId: string;
72
+ teamId: string;
73
+ }): {
74
+ sourceId: string;
75
+ input_tokens: number;
76
+ output_tokens: number;
77
+ input_tokens_cache_write: number;
78
+ input_tokens_cache_read: number;
79
+ reasoning_tokens: number;
80
+ keySource: APIKeySource;
81
+ agentId: string;
82
+ teamId: string;
83
+ };
84
+ formatToolsConfig({ type, toolDefinitions, toolChoice }: {
85
+ type?: string;
86
+ toolDefinitions: any;
87
+ toolChoice?: string;
88
+ }): {
89
+ tools: any[];
90
+ tool_choice: string;
91
+ } | {
92
+ tools?: undefined;
93
+ tool_choice?: undefined;
94
+ };
95
+ transformToolMessageBlocks({ messageBlock, toolsData, }: {
96
+ messageBlock: TLLMMessageBlock;
97
+ toolsData: ToolData[];
98
+ }): TLLMToolResultMessageBlock[];
99
+ getConsistentMessages(messages: any): {
100
+ role: TLLMMessageRole;
101
+ content?: string | {
102
+ text: string;
103
+ }[] | Array<import("@anthropic-ai/sdk/resources").TextBlockParam | import("@anthropic-ai/sdk/resources").ImageBlockParam | import("@anthropic-ai/sdk/resources").ToolUseBlockParam | import("@anthropic-ai/sdk/resources").ToolResultBlockParam>;
104
+ parts?: {
105
+ text?: string;
106
+ functionCall?: {
107
+ name: string;
108
+ args: string;
109
+ };
110
+ functionResponse?: {
111
+ name: string;
112
+ response: {
113
+ name: string;
114
+ content: string;
115
+ };
116
+ };
117
+ }[];
118
+ tool_calls?: ToolData[];
119
+ }[];
120
+ }
121
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smythos/sre",
3
- "version": "1.5.32",
3
+ "version": "1.5.34",
4
4
  "description": "Smyth Runtime Environment",
5
5
  "author": "Alaa-eddine KADDOURI",
6
6
  "license": "MIT",
@@ -51,6 +51,7 @@
51
51
  "@faker-js/faker": "^9.8.0",
52
52
  "@google-cloud/vertexai": "^1.7.0",
53
53
  "@google/generative-ai": "^0.14.1",
54
+ "@google/genai": "^1.10.0",
54
55
  "@huggingface/inference": "^2.8.0",
55
56
  "@modelcontextprotocol/sdk": "^1.10.1",
56
57
  "@pinecone-database/pinecone": "^3.0.0",
@@ -17,7 +17,8 @@ class LLMInference {
17
17
 
18
18
  export class DataSourceLookup extends Component {
19
19
  protected configSchema = Joi.object({
20
- topK: Joi.string()
20
+ topK: Joi.alternatives([Joi.string(), Joi.number()]) // Value is now a number; keep string fallback for backward compatibility.
21
+
21
22
  .custom(validateInteger({ min: 0 }), 'custom range validation')
22
23
  .label('Result Count'),
23
24
  model: Joi.string().valid('gpt-4o-mini', 'gpt-4', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k').optional(),
@@ -5,7 +5,7 @@ import { OpenAI } from 'openai';
5
5
 
6
6
  import { TemplateString } from '@sre/helpers/TemplateString.helper';
7
7
  import { LLMInference } from '@sre/LLMManager/LLM.inference';
8
- import { IAgent as Agent } from '@sre/types/Agent.types';
8
+ import { IAgent } from '@sre/types/Agent.types';
9
9
  import { APIKeySource, GenerateImageConfig } from '@sre/types/LLM.types';
10
10
  import Joi from 'joi';
11
11
  import { Component } from './Component.class';
@@ -13,7 +13,6 @@ import { Component } from './Component.class';
13
13
  import { SystemEvents } from '@sre/Core/SystemEvents';
14
14
  import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
15
15
 
16
- import appConfig from '@sre/config';
17
16
  import { BUILT_IN_MODEL_PREFIX, SUPPORTED_MIME_TYPES_MAP } from '@sre/constants';
18
17
  import { BinaryInput } from '@sre/helpers/BinaryInput.helper';
19
18
  import { normalizeImageInput } from '@sre/utils/data.utils';
@@ -45,6 +44,12 @@ const IMAGE_GEN_COST_MAP = {
45
44
  },
46
45
  };
47
46
 
47
+ // Imagen 4 cost map - fixed cost per image
48
+ const IMAGEN_4_COST_MAP = {
49
+ 'imagen-4': 0.04, // Standard Imagen 4
50
+ 'imagen-4-ultra': 0.06, // Imagen 4 Ultra
51
+ };
52
+
48
53
  export class ImageGenerator extends Component {
49
54
  protected configSchema = Joi.object({
50
55
  model: Joi.string().max(100).required(),
@@ -73,12 +78,17 @@ export class ImageGenerator extends Component {
73
78
  // #region GPT model
74
79
  size: Joi.string().optional().allow('').max(100).label('Size'),
75
80
  // #endregion
81
+
82
+ // #region Google AI model
83
+ aspectRatio: Joi.string().valid('1:1', '3:4', '4:3', '9:16', '16:9').optional().allow('').label('Aspect Ratio'),
84
+ personGeneration: Joi.string().valid('dont_allow', 'allow_adult', 'allow_all').optional().allow('').label('Person Generation'),
85
+ // #endregion
76
86
  });
77
87
  constructor() {
78
88
  super();
79
89
  }
80
90
  init() {}
81
- async process(input, config, agent: Agent) {
91
+ async process(input, config, agent: IAgent) {
82
92
  await super.process(input, config, agent);
83
93
 
84
94
  const logger = this.createComponentLogger(agent, config);
@@ -134,6 +144,7 @@ enum MODEL_FAMILY {
134
144
  GPT = 'gpt',
135
145
  RUNWARE = 'runware',
136
146
  DALL_E = 'dall-e',
147
+ IMAGEN = 'imagen',
137
148
  }
138
149
 
139
150
  const imageGenerator = {
@@ -319,6 +330,75 @@ const imageGenerator = {
319
330
  await runware.disconnect();
320
331
  }
321
332
  },
333
+ [MODEL_FAMILY.IMAGEN]: async ({ model, prompt, config, logger, agent, input }) => {
334
+ try {
335
+ const llmInference: LLMInference = await LLMInference.getInstance(model, AccessCandidate.agent(agent.id));
336
+
337
+ // if the llm is undefined, then it means we removed the model from our system
338
+ if (!llmInference.connector) {
339
+ return {
340
+ _error: `The model '${model}' is not available. Please try a different one.`,
341
+ _debug: logger.output,
342
+ };
343
+ }
344
+
345
+ const files: any[] = parseFiles(input, config);
346
+
347
+ // Imagen models only support image generation, not image editing
348
+ if (files.length > 0) {
349
+ throw new Error('Google AI Image Generation Error: Image editing is not supported. Imagen models only support image generation.');
350
+ }
351
+
352
+ let args: GenerateImageConfig & {
353
+ aspectRatio?: string;
354
+ numberOfImages?: number;
355
+ personGeneration?: string;
356
+ } = {
357
+ model,
358
+ aspectRatio: config?.data?.aspectRatio || config?.data?.size || '1:1',
359
+ numberOfImages: config?.data?.numberOfImages || 1,
360
+ personGeneration: config?.data?.personGeneration || 'allow_adult',
361
+ };
362
+
363
+ const response = await llmInference.imageGenRequest({ query: prompt, params: { ...args, agentId: agent.id } });
364
+
365
+ // Calculate fixed cost for Imagen 4
366
+ const modelName = model.replace(BUILT_IN_MODEL_PREFIX, '');
367
+ const cost = IMAGEN_4_COST_MAP[modelName];
368
+
369
+ if (cost && cost > 0) {
370
+ // Multiply by number of images generated
371
+ const numberOfImages = args.numberOfImages || 1;
372
+ const totalCost = cost * numberOfImages;
373
+
374
+ // Report fixed cost usage
375
+ imageGenerator.reportUsage(
376
+ { cost: totalCost },
377
+ {
378
+ modelEntryName: model,
379
+ keySource: model.startsWith(BUILT_IN_MODEL_PREFIX) ? APIKeySource.Smyth : APIKeySource.User,
380
+ agentId: agent.id,
381
+ teamId: agent.teamId,
382
+ }
383
+ );
384
+ }
385
+
386
+ let output = response?.data?.[0]?.b64_json;
387
+
388
+ if (output) {
389
+ const binaryInput = BinaryInput.from(output);
390
+ const agentId = typeof agent == 'object' && agent.id ? agent.id : agent;
391
+ const smythFile = await binaryInput.getJsonData(AccessCandidate.agent(agentId));
392
+ return { output: smythFile };
393
+ } else {
394
+ // Handle URL response format
395
+ output = response?.data?.[0]?.url;
396
+ return { output };
397
+ }
398
+ } catch (error: any) {
399
+ throw new Error(`Google AI Image Generation Error: ${error?.message || JSON.stringify(error)}`);
400
+ }
401
+ },
322
402
  reportTokenUsage(usage: TokenUsage, metadata: { modelEntryName: string; keySource: APIKeySource; agentId: string; teamId: string }) {
323
403
  // SmythOS (built-in) models have a prefix, so we need to remove it to get the model name
324
404
  const modelName = metadata.modelEntryName.replace(BUILT_IN_MODEL_PREFIX, '');
@@ -361,6 +441,7 @@ const imageGenerator = {
361
441
  enum PROVIDERS {
362
442
  OPENAI = 'OpenAI',
363
443
  RUNWARE = 'Runware',
444
+ GOOGLEAI = 'GoogleAI',
364
445
  }
365
446
 
366
447
  /**
@@ -368,10 +449,11 @@ enum PROVIDERS {
368
449
  * @param model The model identifier
369
450
  * @returns The model family or null if not recognized
370
451
  */
371
- async function getModelFamily(model: string, agent: Agent): Promise<string | null> {
452
+ async function getModelFamily(model: string, agent: IAgent): Promise<string | null> {
372
453
  if (await isGPTModel(model)) return MODEL_FAMILY.GPT;
373
454
  if (await isRunwareModel(model, agent)) return MODEL_FAMILY.RUNWARE;
374
455
  if (await isDallEModel(model)) return MODEL_FAMILY.DALL_E;
456
+ if (await isGoogleAIModel(model, agent)) return MODEL_FAMILY.IMAGEN;
375
457
 
376
458
  return null;
377
459
  }
@@ -380,15 +462,24 @@ function isGPTModel(model: string) {
380
462
  return model?.replace(BUILT_IN_MODEL_PREFIX, '')?.startsWith(MODEL_FAMILY.GPT);
381
463
  }
382
464
 
383
- async function isRunwareModel(model: string, agent: Agent): Promise<boolean> {
465
+ async function isRunwareModel(model: string, agent: IAgent): Promise<boolean> {
384
466
  const provider = await agent.modelsProvider.getProvider(model);
385
- return provider === PROVIDERS.RUNWARE || provider.toLowerCase() === PROVIDERS.RUNWARE.toLowerCase();
467
+ return provider === PROVIDERS.RUNWARE || provider?.toLowerCase() === PROVIDERS.RUNWARE.toLowerCase();
386
468
  }
387
469
 
388
470
  function isDallEModel(model: string) {
389
471
  return model?.replace(BUILT_IN_MODEL_PREFIX, '')?.startsWith(MODEL_FAMILY.DALL_E);
390
472
  }
391
473
 
474
+ async function isGoogleAIModel(model: string, agent: IAgent): Promise<boolean> {
475
+ const provider = await agent.modelsProvider.getProvider(model);
476
+ return (
477
+ provider === PROVIDERS.GOOGLEAI ||
478
+ provider?.toLowerCase() === PROVIDERS.GOOGLEAI.toLowerCase() ||
479
+ model?.replace(BUILT_IN_MODEL_PREFIX, '')?.includes('imagen')
480
+ );
481
+ }
482
+
392
483
  function parseFiles(input: any, config: any) {
393
484
  const mediaTypes = ['Image', 'Audio', 'Video', 'Binary'];
394
485
 
@@ -6,7 +6,7 @@ import { Component } from './Component.class';
6
6
  export class LogicAtLeast extends Component {
7
7
  protected configSchema = Joi.object({
8
8
  // TODO (Forhad): Need to check if min and max work instead of the custom validateInteger
9
- minSetInputs: Joi.string()
9
+ minSetInputs: Joi.alternatives([Joi.string(), Joi.number()]) // Value is now a number; keep string fallback for backward compatibility.
10
10
  .custom(validateInteger({ min: 0, max: 9 }), 'custom range validation')
11
11
  .label('Minimum Inputs'),
12
12
  });
@@ -21,7 +21,7 @@ export class LogicAtLeast extends Component {
21
21
  const logger = this.createComponentLogger(agent, config);
22
22
  const result: any = { Output: undefined };
23
23
 
24
- if (typeof config.data.minSetInputs !== 'string' || config.data.minSetInputs.trim() === '' || isNaN(Number(config.data.minSetInputs))) {
24
+ if (config.data.minSetInputs === '' || isNaN(Number(config.data.minSetInputs))) {
25
25
  return result;
26
26
  }
27
27
 
@@ -6,7 +6,7 @@ import { Component } from './Component.class';
6
6
  export class LogicAtMost extends Component {
7
7
  protected configSchema = Joi.object({
8
8
  // TODO (Forhad): Need to check if min and max work instead of the custom validateInteger
9
- maxSetInputs: Joi.string()
9
+ maxSetInputs: Joi.alternatives([Joi.string(), Joi.number()]) // Value is now a number; keep string fallback for backward compatibility.
10
10
  .custom(validateInteger({ min: 0, max: 9 }), 'custom range validation')
11
11
  .label('Maximum Inputs'),
12
12
  });
@@ -20,7 +20,7 @@ export class LogicAtMost extends Component {
20
20
  await super.process(input, config, agent);
21
21
  const result: any = { Output: undefined };
22
22
 
23
- if (typeof config.data.maxSetInputs !== 'string' || config.data.maxSetInputs.trim() === '' || isNaN(Number(config.data.maxSetInputs))) {
23
+ if (config.data.maxSetInputs === '' || isNaN(Number(config.data.maxSetInputs))) {
24
24
  return result;
25
25
  }
26
26
 
package/src/constants.ts CHANGED
@@ -76,6 +76,7 @@ export const SUPPORTED_MIME_TYPES_MAP = {
76
76
  },
77
77
  GoogleAI: {
78
78
  image: ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/heic', 'image/heif'],
79
+ imageGen: ['image/png', 'image/jpeg', 'image/jpg', 'image/webp'],
79
80
  video: [
80
81
  'video/mp4',
81
82
  'video/mpeg',