@smythos/sre 1.5.65 → 1.5.67

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,19 +1,14 @@
1
1
  import { Component } from './Component.class';
2
2
  import { IAgent as Agent } from '@sre/types/Agent.types';
3
+ import Joi from 'joi';
3
4
  export declare class FTimestamp extends Component {
5
+ protected configSchema: Joi.ObjectSchema<any>;
4
6
  constructor();
5
7
  init(): void;
6
8
  process(input: any, config: any, agent: Agent): Promise<{
7
- Timestamp: number;
9
+ Timestamp: string | number;
8
10
  _error: any;
9
11
  _debug: string;
10
12
  _debug_time: number;
11
- hash?: undefined;
12
- } | {
13
- hash: any;
14
- _error: any;
15
- _debug: string;
16
- _debug_time: number;
17
- Timestamp?: undefined;
18
13
  }>;
19
14
  }
@@ -0,0 +1,13 @@
1
+ import { IAgent as Agent } from '@sre/types/Agent.types';
2
+ import { Trigger } from './Trigger.class';
3
+ export declare class GmailTrigger extends Trigger {
4
+ init(): void;
5
+ process(input: any, config: any, agent: Agent): Promise<{
6
+ Payload: {};
7
+ Result: any;
8
+ _temp_result: any;
9
+ _error: any;
10
+ _in_progress: boolean;
11
+ _debug: string;
12
+ }>;
13
+ }
@@ -0,0 +1,3 @@
1
+ import { Component } from '../Component.class';
2
+ export declare class Trigger extends Component {
3
+ }
@@ -1,9 +1,10 @@
1
1
  import EventEmitter from 'events';
2
2
  import { UsageMetadata } from '@google/generative-ai';
3
- import { TLLMMessageBlock, ToolData, TLLMToolResultMessageBlock, APIKeySource, ILLMRequestFuncParams, TLLMChatResponse, TGoogleAIRequestBody, TLLMPreparedParams } from '@sre/types/LLM.types';
3
+ import { TLLMMessageBlock, ToolData, TLLMToolResultMessageBlock, APIKeySource, ILLMRequestFuncParams, TLLMChatResponse, TGoogleAIRequestBody, ILLMRequestContext, TLLMPreparedParams } from '@sre/types/LLM.types';
4
4
  import { LLMConnector } from '../LLMConnector';
5
5
  type UsageMetadataWithThoughtsToken = UsageMetadata & {
6
- thoughtsTokenCount: number;
6
+ thoughtsTokenCount?: number;
7
+ cost?: number;
7
8
  };
8
9
  export declare class GoogleAIConnector extends LLMConnector {
9
10
  name: string;
@@ -32,6 +33,18 @@ export declare class GoogleAIConnector extends LLMConnector {
32
33
  teamId: string;
33
34
  tier: string;
34
35
  };
36
+ /**
37
+ * Extract text and image tokens from Google AI usage metadata
38
+ */
39
+ private extractTokenCounts;
40
+ protected reportImageUsage({ usage, context, numberOfImages, }: {
41
+ usage: {
42
+ cost?: number;
43
+ usageMetadata?: UsageMetadataWithThoughtsToken;
44
+ };
45
+ context: ILLMRequestContext;
46
+ numberOfImages?: number;
47
+ }): void;
35
48
  formatToolsConfig({ toolDefinitions, toolChoice }: {
36
49
  toolDefinitions: any;
37
50
  toolChoice?: string;
@@ -51,6 +64,7 @@ export declare class GoogleAIConnector extends LLMConnector {
51
64
  private prepareMessagesWithTools;
52
65
  private prepareMessagesWithTextQuery;
53
66
  private prepareBodyForImageGenRequest;
67
+ private prepareImageEditBody;
54
68
  private sanitizeFunctionName;
55
69
  private uploadFile;
56
70
  private getValidFiles;
@@ -193,11 +193,29 @@ export type TLLMModel = {
193
193
  params?: TLLMParams;
194
194
  /**
195
195
  * Specifies the API interface type to use for this model
196
- * Examples: 'chat.completions', 'responses'
197
- * This determines which OpenAI API endpoint and interface implementation to use
196
+ * This determines which API endpoint and interface implementation to use
198
197
  */
199
- interface?: 'chat.completions' | 'responses';
198
+ interface?: LLMInterface;
199
+ /**
200
+ * Indicates whether this model supports image editing functionality
201
+ * Only applicable for image generation models
202
+ */
203
+ supportsEditing?: boolean;
200
204
  };
205
+ /**
206
+ * Enum for different LLM API interfaces
207
+ * Each interface represents a different API endpoint or interaction pattern
208
+ */
209
+ export declare enum LLMInterface {
210
+ /** OpenAI-style chat completions API */
211
+ ChatCompletions = "chat.completions",
212
+ /** OpenAI-style responses API */
213
+ Responses = "responses",
214
+ /** Google AI generateContent API (for text and multimodal) */
215
+ GenerateContent = "generateContent",
216
+ /** Google AI generateImages API (for traditional Imagen models) */
217
+ GenerateImages = "generateImages"
218
+ }
201
219
  export declare const BuiltinLLMProviders: {
202
220
  readonly Echo: "Echo";
203
221
  readonly OpenAI: "OpenAI";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smythos/sre",
3
- "version": "1.5.65",
3
+ "version": "1.5.67",
4
4
  "description": "Smyth Runtime Environment",
5
5
  "author": "Alaa-eddine KADDOURI",
6
6
  "license": "MIT",
@@ -1,25 +1,66 @@
1
1
  import { Component } from './Component.class';
2
2
  import { IAgent as Agent } from '@sre/types/Agent.types';
3
+ import Joi from 'joi';
4
+ import dayjs from 'dayjs';
3
5
 
4
6
  export class FTimestamp extends Component {
7
+ protected configSchema = Joi.object({
8
+ format: Joi.alternatives()
9
+ .try(Joi.string().valid('unix', 'iso', 'timestamp'), Joi.string().pattern(/^[YMDHhmsSSZzAa\s\-\/:,\.]*$/, 'custom dayjs format'))
10
+ .default('unix')
11
+ .allow(null)
12
+ .label('Timestamp Format')
13
+ .messages({
14
+ 'string.pattern.name': 'Invalid format string: {#value}',
15
+ }),
16
+ });
17
+
5
18
  constructor() {
6
19
  super();
7
20
  }
8
21
  init() {}
9
22
  async process(input, config, agent: Agent) {
10
23
  await super.process(input, config, agent);
24
+
11
25
  const logger = this.createComponentLogger(agent, config);
26
+
27
+ const validationResult = await this.validateConfig(config);
28
+ if (validationResult._error) {
29
+ return {
30
+ Timestamp: undefined,
31
+ _error: validationResult._error,
32
+ _debug: logger.output,
33
+ _debug_time: logger.elapsedTime,
34
+ };
35
+ }
12
36
  try {
13
37
  const _error = undefined;
14
- const format = config.data.format; //TODO set timestamp format
15
- const Timestamp = Date.now();
16
- logger.debug(`Timestamp : ${Timestamp}`);
38
+ const format = config.data.format || 'unix';
39
+ const now = dayjs();
40
+
41
+ let Timestamp: number | string;
42
+
43
+ switch (format) {
44
+ case 'unix':
45
+ case 'timestamp':
46
+ Timestamp = Date.now();
47
+ logger.debug(`Unix timestamp: ${Timestamp}`);
48
+ break;
49
+ case 'iso':
50
+ Timestamp = now.toISOString();
51
+ logger.debug(`ISO timestamp: ${Timestamp}`);
52
+ break;
53
+ default:
54
+ Timestamp = now.format(format);
55
+ logger.debug(`Custom formatted timestamp (${format}): ${Timestamp}`);
56
+ break;
57
+ }
17
58
 
18
59
  return { Timestamp, _error, _debug: logger.output, _debug_time: logger.elapsedTime };
19
60
  } catch (err: any) {
20
61
  const _error = err?.response?.data || err?.message || err.toString();
21
- logger.error(` Error processing data \n${_error}\n`);
22
- return { hash: undefined, _error, _debug: logger.output, _debug_time: logger.elapsedTime };
62
+ logger.error(` Error processing timestamp \n${_error}\n`);
63
+ return { Timestamp: undefined, _error, _debug: logger.output, _debug_time: logger.elapsedTime };
23
64
  }
24
65
  }
25
66
  }
@@ -44,12 +44,6 @@ const IMAGE_GEN_COST_MAP = {
44
44
  },
45
45
  };
46
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
-
53
47
  export class ImageGenerator extends Component {
54
48
  protected configSchema = Joi.object({
55
49
  model: Joi.string().max(100).required(),
@@ -344,11 +338,6 @@ const imageGenerator = {
344
338
 
345
339
  const files: any[] = parseFiles(input, config);
346
340
 
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
341
  let args: GenerateImageConfig & {
353
342
  aspectRatio?: string;
354
343
  numberOfImages?: number;
@@ -360,29 +349,21 @@ const imageGenerator = {
360
349
  personGeneration: config?.data?.personGeneration || 'allow_adult',
361
350
  };
362
351
 
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
- );
352
+ let response;
353
+
354
+ // Check if files are provided for image editing
355
+ if (files.length > 0) {
356
+ const validFiles = files.filter((file) => imageGenerator.isValidImageFile('GoogleAI', file.mimetype));
357
+ if (validFiles.length === 0) {
358
+ throw new Error('Supported image file types are: ' + SUPPORTED_MIME_TYPES_MAP.GoogleAI?.image?.join(', '));
359
+ }
360
+ response = await llmInference.imageEditRequest({ query: prompt, files: validFiles, params: { ...args, agentId: agent.id } });
361
+ } else {
362
+ response = await llmInference.imageGenRequest({ query: prompt, params: { ...args, agentId: agent.id } });
384
363
  }
385
364
 
365
+ // Usage reporting is now handled in the GoogleAI connector
366
+
386
367
  let output = response?.data?.[0]?.b64_json;
387
368
 
388
369
  if (output) {
@@ -14,9 +14,9 @@ export class MCPClient extends Component {
14
14
  model: Joi.string().optional(),
15
15
  openAiModel: Joi.string().optional(), // for backward compatibility
16
16
  mcpUrl: Joi.string().max(2048).uri().required().description('URL of the MCP'),
17
- descForModel: Joi.string().max(5000).required().allow('').label('Description for Model'),
17
+ descForModel: Joi.string().max(5000).allow('').label('Description for Model'),
18
18
  name: Joi.string().max(500).required().allow(''),
19
- desc: Joi.string().max(5000).required().allow('').label('Description'),
19
+ desc: Joi.string().max(5000).allow('').label('Description'),
20
20
  logoUrl: Joi.string().max(8192).allow(''),
21
21
  id: Joi.string().max(200),
22
22
  version: Joi.string().max(100).allow(''),
@@ -51,7 +51,7 @@ export class MCPClient extends Component {
51
51
  }
52
52
 
53
53
  // TODO [Forhad]: Need to check and validate input prompt token
54
- const { client } = await this.connectMCP(mcpUrl);
54
+ const { client } = await this.connectMCP(mcpUrl, logger);
55
55
 
56
56
  const toolsData = await client.listTools();
57
57
  const conv = new Conversation(
@@ -70,7 +70,7 @@ export class MCPClient extends Component {
70
70
  ],
71
71
  paths: {},
72
72
  },
73
- { agentId: agent?.id },
73
+ { agentId: agent?.id }
74
74
  );
75
75
 
76
76
  for (const tool of toolsData.tools) {
@@ -105,34 +105,33 @@ export class MCPClient extends Component {
105
105
  return { _error: `Error on running MCP Client!\n${error?.message || JSON.stringify(error)}`, _debug: logger.output };
106
106
  }
107
107
  }
108
- private async connectMCP(mcpUrl: string) {
108
+ private async connectMCP(mcpUrl: string, logger: any) {
109
109
  const client = new Client({ name: 'auto-client', version: '1.0.0' });
110
-
110
+
111
111
  // 1) Try Streamable HTTP first
112
112
  try {
113
- const st = new StreamableHTTPClientTransport(new URL(mcpUrl));
114
- await client.connect(st);
115
- console.debug('Connected to MCP using Streamable HTTP');
116
- return { client, transport: 'streamable' as const };
113
+ const st = new StreamableHTTPClientTransport(new URL(mcpUrl));
114
+ await client.connect(st);
115
+ logger.debug('Connected to MCP using Streamable HTTP');
116
+ return { client, transport: 'streamable' as const };
117
117
  } catch (e: any) {
118
- console.debug('Failed to connect to MCP using Streamable HTTP, falling back to SSE');
119
- // 2) If clearly unsupported, fall back to SSE
120
- const msg = String(e?.message || e);
121
- const isUnsupported =
122
- /404|405|ENOTFOUND|ECONNREFUSED|CORS/i.test(msg);
123
-
124
- // 406 means wrong/missing Accept for Streamable → retry Streamable with proper header
125
- const isAcceptProblem = /406|Not Acceptable|text\/event-stream/i.test(msg);
126
- if (isAcceptProblem) {
127
- throw new Error('Server is Streamable; include Accept: application/json, text/event-stream');
128
- }
129
-
130
- if (!isUnsupported) throw e;
131
-
132
- // SSE fallback
133
- const sse = new SSEClientTransport(new URL(mcpUrl));
134
- await client.connect(sse);
135
- return { client, transport: 'sse' as const };
118
+ logger.debug('Failed to connect to MCP using Streamable HTTP, falling back to SSE');
119
+ // 2) If clearly unsupported, fall back to SSE
120
+ const msg = String(e?.message || e);
121
+ const isUnsupported = /404|405|ENOTFOUND|ECONNREFUSED|CORS/i.test(msg);
122
+
123
+ // 406 means wrong/missing Accept for Streamable → retry Streamable with proper header
124
+ const isAcceptProblem = /406|Not Acceptable|text\/event-stream/i.test(msg);
125
+ if (isAcceptProblem) {
126
+ throw new Error('Server is Streamable; include Accept: application/json, text/event-stream');
127
+ }
128
+
129
+ if (!isUnsupported) throw e;
130
+
131
+ // SSE fallback
132
+ const sse = new SSEClientTransport(new URL(mcpUrl));
133
+ await client.connect(sse);
134
+ return { client, transport: 'sse' as const };
136
135
  }
137
- }
136
+ }
138
137
  }
@@ -281,6 +281,8 @@ export class Conversation extends EventEmitter {
281
281
  let _content = '';
282
282
  const reqMethods = this._reqMethods;
283
283
  const toolsConfig = this._toolsConfig;
284
+ //deduplicate tools
285
+ toolsConfig.tools = toolsConfig.tools.filter((tool, index, self) => self.findIndex((t) => t.name === tool.name) === index);
284
286
  const endpoints = this._endpoints;
285
287
  const baseUrl = this._baseUrl;
286
288
  const message_id = 'msg_' + randomUUID();