@igniter-js/agents 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var core = require('@igniter-js/core');
3
+ var common = require('@igniter-js/common');
4
4
  var mcp = require('@ai-sdk/mcp');
5
5
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
6
6
  var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
7
7
  var ai = require('ai');
8
+ var zod = require('zod');
8
9
  var promises = require('fs/promises');
9
10
  var path = require('path');
10
11
 
@@ -32,7 +33,7 @@ var IgniterAgentErrorCode = /* @__PURE__ */ ((IgniterAgentErrorCode3) => {
32
33
  IgniterAgentErrorCode3["ADAPTER_NOT_INITIALIZED"] = "IGNITER_AGENT_ADAPTER_NOT_INITIALIZED";
33
34
  return IgniterAgentErrorCode3;
34
35
  })(IgniterAgentErrorCode || {});
35
- var IgniterAgentError = class extends core.IgniterError {
36
+ var IgniterAgentError = class extends common.IgniterError {
36
37
  /**
37
38
  * Creates a new IgniterAgentError.
38
39
  *
@@ -388,7 +389,12 @@ var IgniterAgentManagerCore = class _IgniterAgentManagerCore {
388
389
  * @example
389
390
  * ```typescript
390
391
  * const agent = manager.get('support');
391
- * const response = await agent.generate({ messages: [...] });
392
+ * const response = await agent.generate({
393
+ * chatId: 'chat_123',
394
+ * userId: 'user_123',
395
+ * context: {},
396
+ * message: { role: 'user', content: 'Hello!' }
397
+ * });
392
398
  * ```
393
399
  */
394
400
  get(name) {
@@ -663,6 +669,244 @@ var IgniterAgentMemoryCore = class {
663
669
  }
664
670
  };
665
671
 
672
+ // src/utils/strings.ts
673
+ var IgniterAgentStringUtils = class {
674
+ /**
675
+ * Generates a unique identifier.
676
+ */
677
+ static generateId(prefix) {
678
+ const random = Math.random().toString(36).substring(2, 12);
679
+ return prefix ? `${prefix}_${random}` : random;
680
+ }
681
+ /**
682
+ * Truncates a string to a maximum length.
683
+ */
684
+ static truncate(str, maxLength, suffix = "...") {
685
+ if (str.length <= maxLength) return str;
686
+ return str.slice(0, maxLength - suffix.length) + suffix;
687
+ }
688
+ /**
689
+ * Converts a string to snake_case.
690
+ */
691
+ static toSnakeCase(str) {
692
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
693
+ }
694
+ /**
695
+ * Converts a string to camelCase.
696
+ */
697
+ static toCamelCase(str) {
698
+ return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^(.)/, (char) => char.toLowerCase());
699
+ }
700
+ };
701
+
702
+ // src/utils/objects.ts
703
+ var IgniterAgentObjectUtils = class _IgniterAgentObjectUtils {
704
+ /**
705
+ * Deep merges two objects.
706
+ */
707
+ static deepMerge(target, source) {
708
+ const result = { ...target };
709
+ for (const key in source) {
710
+ const sourceValue = source[key];
711
+ const targetValue = target[key];
712
+ if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) {
713
+ result[key] = _IgniterAgentObjectUtils.deepMerge(
714
+ targetValue,
715
+ sourceValue
716
+ );
717
+ } else {
718
+ result[key] = sourceValue;
719
+ }
720
+ }
721
+ return result;
722
+ }
723
+ /**
724
+ * Picks specified keys from an object.
725
+ */
726
+ static pick(obj, keys) {
727
+ const result = {};
728
+ for (const key of keys) {
729
+ if (key in obj) {
730
+ result[key] = obj[key];
731
+ }
732
+ }
733
+ return result;
734
+ }
735
+ /**
736
+ * Omits specified keys from an object.
737
+ */
738
+ static omit(obj, keys) {
739
+ const result = { ...obj };
740
+ for (const key of keys) {
741
+ delete result[key];
742
+ }
743
+ return result;
744
+ }
745
+ };
746
+
747
+ // src/utils/async.ts
748
+ var IgniterAgentAsyncUtils = class _IgniterAgentAsyncUtils {
749
+ /**
750
+ * Delays execution for a specified duration.
751
+ */
752
+ static delay(ms) {
753
+ return new Promise((resolve) => setTimeout(resolve, ms));
754
+ }
755
+ /**
756
+ * Retries an async function with exponential backoff.
757
+ */
758
+ static async retry(fn, options = {}) {
759
+ const {
760
+ maxAttempts = 3,
761
+ baseDelay = 1e3,
762
+ maxDelay = 3e4,
763
+ onRetry
764
+ } = options;
765
+ let lastError;
766
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
767
+ try {
768
+ return await fn();
769
+ } catch (error) {
770
+ lastError = error instanceof Error ? error : new Error(String(error));
771
+ if (attempt === maxAttempts) {
772
+ throw lastError;
773
+ }
774
+ onRetry?.(attempt, lastError);
775
+ const delayMs = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
776
+ await _IgniterAgentAsyncUtils.delay(delayMs);
777
+ }
778
+ }
779
+ throw lastError;
780
+ }
781
+ };
782
+
783
+ // src/utils/validation.ts
784
+ var IgniterAgentValidationUtils = class {
785
+ /**
786
+ * Checks if a value is defined (not null or undefined).
787
+ */
788
+ static isDefined(value) {
789
+ return value !== null && value !== void 0;
790
+ }
791
+ /**
792
+ * Checks if a value is a non-empty string.
793
+ */
794
+ static isNonEmptyString(value) {
795
+ return typeof value === "string" && value.length > 0;
796
+ }
797
+ /**
798
+ * Checks if a value is a plain object.
799
+ */
800
+ static isPlainObject(value) {
801
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
802
+ }
803
+ };
804
+
805
+ // src/utils/context.ts
806
+ var IgniterAgentContext = class {
807
+ /**
808
+ * Private constructor to prevent instantiation.
809
+ * This is a static-only utility class.
810
+ */
811
+ constructor() {
812
+ }
813
+ /**
814
+ * Creates an execution context to pass to AI SDK's experimental_context.
815
+ *
816
+ * Your context object is spread at the top level, merged with writer and metadata.
817
+ * This means you can access your fields directly without a wrapper.
818
+ *
819
+ * @template TContext - Your custom context type (must be an object)
820
+ * @param options - The context creation options
821
+ * @returns The merged execution context ready for AI SDK
822
+ *
823
+ * @example Basic usage
824
+ * ```typescript
825
+ * const context = IgniterAgentContext.create({
826
+ * context: { userId: '123', db: database, permissions: ['read', 'write'] },
827
+ * writer: streamWriter
828
+ * });
829
+ * // Access in tools: executionOptions.experimental_context.userId
830
+ * ```
831
+ *
832
+ * @example With typed context
833
+ * ```typescript
834
+ * interface MyAppContext {
835
+ * tenant: string;
836
+ * workspace: string;
837
+ * features: string[];
838
+ * }
839
+ *
840
+ * const context = IgniterAgentContext.create<MyAppContext>({
841
+ * context: { tenant: 'acme', workspace: 'main', features: ['analytics'] },
842
+ * writer: streamWriter
843
+ * });
844
+ * // Access in tools: executionOptions.experimental_context.tenant
845
+ * ```
846
+ *
847
+ * @example With metadata for tracing
848
+ * ```typescript
849
+ * const context = IgniterAgentContext.create({
850
+ * context: { userId: '123', tenantId: 'acme' },
851
+ * writer: streamWriter,
852
+ * metadata: { agent: 'reports', requestId: 'req_123' }
853
+ * });
854
+ * ```
855
+ */
856
+ static create(options) {
857
+ return {
858
+ ...options.context,
859
+ writer: options.writer,
860
+ metadata: {
861
+ startTime: /* @__PURE__ */ new Date(),
862
+ ...options.metadata
863
+ }
864
+ };
865
+ }
866
+ /**
867
+ * Gets your custom context from execution options.
868
+ *
869
+ * Your context fields are available directly in experimental_context (no wrapper).
870
+ * This helper provides type-safe access.
871
+ *
872
+ * @template T - Your custom context type (object)
873
+ * @param executionOptions - Tool execution options from AI SDK
874
+ * @returns Your custom context or undefined if not available
875
+ *
876
+ * @example Direct access (no helper needed)
877
+ * ```typescript
878
+ * export const myTool = tool({
879
+ * execute: async (params, executionOptions) => {
880
+ * // Access fields directly
881
+ * const userId = executionOptions.experimental_context.userId;
882
+ * const db = executionOptions.experimental_context.db;
883
+ * }
884
+ * });
885
+ * ```
886
+ *
887
+ * @example With typed helper
888
+ * ```typescript
889
+ * interface AppContext {
890
+ * userId: string;
891
+ * tenantId: string;
892
+ * db: Database;
893
+ * }
894
+ *
895
+ * export const myTool = tool({
896
+ * execute: async (params, executionOptions) => {
897
+ * const ctx = IgniterAgentContext.get<AppContext>(executionOptions);
898
+ * if (ctx) {
899
+ * const user = await ctx.db.users.findOne(ctx.userId);
900
+ * }
901
+ * }
902
+ * });
903
+ * ```
904
+ */
905
+ static get(executionOptions) {
906
+ return executionOptions?.experimental_context;
907
+ }
908
+ };
909
+
666
910
  // src/core/agent.ts
667
911
  var IgniterAgentCore = class {
668
912
  constructor(agent) {
@@ -916,101 +1160,107 @@ var IgniterAgentCore = class {
916
1160
  }
917
1161
  }
918
1162
  /**
919
- * Generates a response from the agent.
920
- */
921
- async generate(input) {
922
- const startTime = Date.now();
923
- const attributes = {
924
- "ctx.agent.name": this.getName(),
925
- "ctx.generation.inputMessages": Array.isArray(input.messages) ? input.messages.length : void 0,
926
- "ctx.generation.streamed": false
927
- };
928
- this.logger?.debug("IgniterAgent.generate started", attributes);
929
- this.telemetry?.emit("igniter.agent.generation.generate.started", {
930
- level: "debug",
931
- attributes
1163
+ * Generates a response for the given message or messages.
1164
+ *
1165
+ * @description
1166
+ * Accepts either a single `message` or an array of `messages`.
1167
+ * When both are provided, `message` takes precedence.
1168
+ *
1169
+ * @param params - The parameters for the generate call.
1170
+ * @returns The generated response.
1171
+ */
1172
+ async generate(params) {
1173
+ const { message, messages } = this.resolveMessageInput(params);
1174
+ const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
1175
+ const historyMessages = await this.initializeChatMemory({
1176
+ chatId: params.chatId,
1177
+ userId: params.userId,
1178
+ agentId: this.getName(),
1179
+ message
932
1180
  });
1181
+ const agent = this.prepare({
1182
+ chatId: params.chatId,
1183
+ userId: params.userId,
1184
+ agentId: this.getName(),
1185
+ message,
1186
+ requestId,
1187
+ context: params.context,
1188
+ streamed: false
1189
+ });
1190
+ const inputMessages = params.message ? [message] : messages;
1191
+ const resolvedMessages = historyMessages ? [...historyMessages, ...inputMessages] : inputMessages;
933
1192
  try {
934
- const agent = this.getAgentInstanceWithContext(
935
- input.options
936
- );
937
- const result = await agent.generate(input);
938
- const durationMs = Date.now() - startTime;
939
- this.telemetry?.emit("igniter.agent.generation.generate.success", {
940
- level: "debug",
941
- attributes: {
942
- ...attributes,
943
- "ctx.generation.durationMs": durationMs
944
- }
1193
+ return await agent.generate({
1194
+ abortSignal: params.abortSignal,
1195
+ options: params.options,
1196
+ messages: resolvedMessages,
1197
+ prompt: params.prompt
945
1198
  });
946
- this.logger?.success?.("IgniterAgent.generate success", {
947
- ...attributes,
948
- durationMs
949
- });
950
- return result;
951
1199
  } catch (error) {
952
1200
  const err = error instanceof Error ? error : new Error(String(error));
953
1201
  this.telemetry?.emit("igniter.agent.generation.generate.error", {
954
1202
  level: "error",
955
1203
  attributes: {
956
- ...attributes,
1204
+ "ctx.agent.name": this.getName(),
1205
+ "ctx.agent.chatId": params.chatId,
1206
+ "ctx.agent.userId": params.userId,
1207
+ "ctx.generation.streamed": false,
957
1208
  ...this.getErrorAttributes(err, "generation.generate")
958
1209
  }
959
1210
  });
960
- this.logger?.error("IgniterAgent.generate failed", err);
961
1211
  throw err;
962
1212
  }
963
1213
  }
964
1214
  /**
965
- * Streams a response from the agent.
966
- */
967
- async stream(input) {
968
- const startTime = Date.now();
969
- const attributes = {
970
- "ctx.agent.name": this.getName(),
971
- "ctx.generation.inputMessages": Array.isArray(input.messages) ? input.messages.length : void 0,
972
- "ctx.generation.streamed": true
973
- };
974
- this.logger?.debug("IgniterAgent.stream started", attributes);
975
- this.telemetry?.emit("igniter.agent.generation.stream.started", {
976
- level: "debug",
977
- attributes
1215
+ * Streams a response for the given message or messages.
1216
+ *
1217
+ * @description
1218
+ * Accepts either a single `message` or an array of `messages`.
1219
+ * When both are provided, `message` takes precedence.
1220
+ *
1221
+ * @param params - The parameters for the stream call.
1222
+ * @returns The streaming response.
1223
+ */
1224
+ async stream(params) {
1225
+ const { message, messages } = this.resolveMessageInput(params);
1226
+ const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
1227
+ const historyMessages = await this.initializeChatMemory({
1228
+ chatId: params.chatId,
1229
+ userId: params.userId,
1230
+ agentId: this.getName(),
1231
+ message
1232
+ });
1233
+ const agent = this.prepare({
1234
+ chatId: params.chatId,
1235
+ userId: params.userId,
1236
+ agentId: this.getName(),
1237
+ message,
1238
+ requestId,
1239
+ context: params.context,
1240
+ streamed: true
978
1241
  });
1242
+ const inputMessages = params.message ? [message] : messages;
1243
+ const resolvedMessages = historyMessages ? [...historyMessages, ...inputMessages] : inputMessages;
979
1244
  try {
980
- const agent = this.getAgentInstanceWithContext(
981
- input.options
982
- );
983
- const result = await agent.stream(input);
984
- const durationMs = Date.now() - startTime;
985
- const emitChunk = () => {
986
- this.telemetry?.emit("igniter.agent.generation.stream.chunk", {
987
- level: "debug",
988
- attributes
989
- });
990
- };
991
- const wrapped = this.wrapStreamResult(result, emitChunk);
992
- this.telemetry?.emit("igniter.agent.generation.stream.success", {
993
- level: "debug",
994
- attributes: {
995
- ...attributes,
996
- "ctx.generation.durationMs": durationMs
997
- }
998
- });
999
- this.logger?.success?.("IgniterAgent.stream success", {
1000
- ...attributes,
1001
- durationMs
1245
+ return await agent.stream({
1246
+ abortSignal: params.abortSignal,
1247
+ experimental_transform: params.experimental_transform,
1248
+ options: params.options,
1249
+ messages: resolvedMessages,
1250
+ prompt: params.prompt
1002
1251
  });
1003
- return wrapped;
1004
1252
  } catch (error) {
1005
1253
  const err = error instanceof Error ? error : new Error(String(error));
1006
1254
  this.telemetry?.emit("igniter.agent.generation.stream.error", {
1007
1255
  level: "error",
1008
1256
  attributes: {
1009
- ...attributes,
1257
+ "ctx.agent.name": this.getName(),
1258
+ "ctx.agent.chatId": params.chatId,
1259
+ "ctx.agent.userId": params.userId,
1260
+ "ctx.generation.streamed": true,
1010
1261
  ...this.getErrorAttributes(err, "generation.stream")
1011
1262
  }
1012
1263
  });
1013
- this.logger?.error("IgniterAgent.stream failed", err);
1014
1264
  throw err;
1015
1265
  }
1016
1266
  }
@@ -1042,109 +1292,16 @@ var IgniterAgentCore = class {
1042
1292
  * Gets all registered tools from all toolsets.
1043
1293
  */
1044
1294
  getTools() {
1045
- const toolsets = this.getToolsets();
1046
- const allTools = {};
1047
- for (const toolset of Object.values(toolsets)) {
1048
- for (const [toolName, tool2] of Object.entries(toolset.tools)) {
1049
- const wrapped = this.wrapToolExecution(toolset.name, toolName, tool2);
1050
- allTools[toolName] = wrapped;
1051
- }
1052
- }
1295
+ const allTools = this.initializeTools({
1296
+ chatId: "",
1297
+ userId: "",
1298
+ agentId: this.getName()
1299
+ });
1053
1300
  return allTools;
1054
1301
  }
1055
- wrapToolExecution(toolsetName, toolName, tool2) {
1056
- if (!tool2 || !tool2.execute || typeof tool2.execute !== "function") {
1057
- return tool2;
1058
- }
1059
- const execute = tool2.execute;
1060
- const fullName = `${toolsetName}.${toolName}`;
1061
- const agentName = this.getName();
1062
- return {
1063
- ...tool2,
1064
- execute: async (input, options) => {
1065
- const startTime = Date.now();
1066
- this.hooks.onToolCallStart?.(agentName, fullName, input);
1067
- this.telemetry?.emit("igniter.agent.tool.execute.started", {
1068
- level: "debug",
1069
- attributes: {
1070
- "ctx.agent.name": agentName,
1071
- "ctx.tool.toolset": toolsetName,
1072
- "ctx.tool.name": toolName,
1073
- "ctx.tool.fullName": fullName
1074
- }
1075
- });
1076
- this.logger?.debug("IgniterAgent.tool.execute started", {
1077
- agent: agentName,
1078
- tool: fullName
1079
- });
1080
- try {
1081
- const result = await execute(input, options);
1082
- const durationMs = Date.now() - startTime;
1083
- this.hooks.onToolCallEnd?.(agentName, fullName, result);
1084
- this.telemetry?.emit("igniter.agent.tool.execute.success", {
1085
- level: "debug",
1086
- attributes: {
1087
- "ctx.agent.name": agentName,
1088
- "ctx.tool.toolset": toolsetName,
1089
- "ctx.tool.name": toolName,
1090
- "ctx.tool.fullName": fullName,
1091
- "ctx.tool.durationMs": durationMs
1092
- }
1093
- });
1094
- this.logger?.success?.("IgniterAgent.tool.execute success", {
1095
- agent: agentName,
1096
- tool: fullName,
1097
- durationMs
1098
- });
1099
- return result;
1100
- } catch (error) {
1101
- const err = error instanceof Error ? error : new Error(String(error));
1102
- this.hooks.onToolCallError?.(agentName, fullName, err);
1103
- this.telemetry?.emit("igniter.agent.tool.execute.error", {
1104
- level: "error",
1105
- attributes: {
1106
- "ctx.agent.name": agentName,
1107
- "ctx.tool.toolset": toolsetName,
1108
- "ctx.tool.name": toolName,
1109
- "ctx.tool.fullName": fullName,
1110
- ...this.getErrorAttributes(err, "tool.execute")
1111
- }
1112
- });
1113
- this.logger?.error("IgniterAgent.tool.execute failed", err);
1114
- throw err;
1115
- }
1116
- }
1117
- };
1118
- }
1119
- wrapStreamResult(result, onChunk) {
1120
- if (!result) {
1121
- return result;
1122
- }
1123
- if (typeof result[Symbol.asyncIterator] === "function") {
1124
- const iterable = result;
1125
- return {
1126
- [Symbol.asyncIterator]: async function* () {
1127
- for await (const chunk of iterable) {
1128
- onChunk();
1129
- yield chunk;
1130
- }
1131
- }
1132
- };
1133
- }
1134
- const maybeTextStream = result.textStream;
1135
- if (maybeTextStream && typeof maybeTextStream[Symbol.asyncIterator] === "function") {
1136
- return {
1137
- ...result,
1138
- textStream: (async function* () {
1139
- for await (const chunk of maybeTextStream) {
1140
- onChunk();
1141
- yield chunk;
1142
- }
1143
- })()
1144
- };
1145
- }
1146
- return result;
1147
- }
1302
+ /**
1303
+ * Gets error attributes for telemetry.
1304
+ */
1148
1305
  getErrorAttributes(error, operation) {
1149
1306
  return {
1150
1307
  "ctx.error.code": error.code ?? error.name ?? "IGNITER_AGENT_UNKNOWN_ERROR" /* UNKNOWN */,
@@ -1153,41 +1310,345 @@ var IgniterAgentCore = class {
1153
1310
  "ctx.error.component": "agent"
1154
1311
  };
1155
1312
  }
1156
- getAgentInstanceWithContext(context) {
1157
- const tools = this.getTools();
1313
+ /**
1314
+ * Gets an agent instance with the given context.
1315
+ */
1316
+ prepare(params) {
1317
+ const tools = this.initializeTools({
1318
+ chatId: params.chatId,
1319
+ userId: params.userId,
1320
+ agentId: this._agent.name
1321
+ });
1158
1322
  if (!this._agent.model) {
1159
1323
  throw new IgniterAgentConfigError({
1160
1324
  message: "Model is required. Call withModel() before build()",
1161
1325
  field: "model"
1162
1326
  });
1163
1327
  }
1164
- if (this._agent.schema !== void 0) {
1165
- const parseResult = this._agent.schema.safeParse(context);
1166
- if (parseResult.success) {
1167
- context = parseResult.data;
1168
- } else {
1169
- throw new IgniterAgentError({
1170
- message: "Invalid context schema",
1171
- code: "IGNITER_AGENT_CONTEXT_SCHEMA_INVALID" /* AGENT_CONTEXT_SCHEMA_INVALID */
1328
+ return new ai.ToolLoopAgent({
1329
+ id: this._agent.name,
1330
+ model: this._agent.model,
1331
+ instructions: this._agent.instructions.getTemplate(),
1332
+ tools,
1333
+ callOptionsSchema: this._agent.schema,
1334
+ prepareCall: async (options) => {
1335
+ const isStreamed = Boolean(params.streamed);
1336
+ const eventPrefix = isStreamed ? "stream" : "generate";
1337
+ this.logger?.debug(`IgniterAgent.${eventPrefix} started`, {
1338
+ "ctx.agent.name": this.getName(),
1339
+ "ctx.agent.chatId": params.chatId,
1340
+ "ctx.agent.userId": params.userId,
1341
+ "ctx.generation.inputMessages": options.messages?.length || 0,
1342
+ "ctx.generation.streamed": isStreamed
1172
1343
  });
1344
+ this.telemetry?.emit(`igniter.agent.generation.${eventPrefix}.started`, {
1345
+ level: "debug",
1346
+ attributes: {
1347
+ "ctx.agent.name": this.getName(),
1348
+ "ctx.agent.chatId": params.chatId,
1349
+ "ctx.agent.userId": params.userId,
1350
+ "ctx.generation.inputMessages": options.messages?.length || 0,
1351
+ "ctx.generation.streamed": isStreamed
1352
+ }
1353
+ });
1354
+ if (this._agent.instructions && options.options) {
1355
+ if (this._agent.memory?.working?.enabled && this._agent.memory.working.template) {
1356
+ this._agent.instructions = this._agent.instructions.addAppended(
1357
+ "memory",
1358
+ this._agent.memory.working.template
1359
+ );
1360
+ }
1361
+ options.instructions = this._agent.instructions.build(options.options);
1362
+ }
1363
+ if (params.prepareCall) {
1364
+ return params.prepareCall(options);
1365
+ }
1366
+ return options;
1367
+ },
1368
+ onStepFinish: async (step) => {
1369
+ if (params.streamed) {
1370
+ return;
1371
+ }
1372
+ this.logger?.debug("IgniterAgent.generate step", {
1373
+ "ctx.agent.name": this.getName(),
1374
+ "ctx.chatId": params.chatId,
1375
+ "ctx.userId": params.userId,
1376
+ "ctx.generation.usage.inputTokens": step.usage.inputTokens,
1377
+ "ctx.generation.usage.outputTokens": step.usage.outputTokens,
1378
+ "ctx.generation.usage.totalTokens": step.usage.totalTokens,
1379
+ "ctx.generation.streamed": false
1380
+ });
1381
+ this.telemetry?.emit("igniter.agent.generation.generate.step", {
1382
+ level: "debug",
1383
+ attributes: {
1384
+ "ctx.agent.name": this.getName(),
1385
+ "ctx.chatId": params.chatId,
1386
+ "ctx.userId": params.userId,
1387
+ "ctx.generation.usage.inputTokens": step.usage.inputTokens,
1388
+ "ctx.generation.usage.outputTokens": step.usage.outputTokens,
1389
+ "ctx.generation.usage.totalTokens": step.usage.totalTokens,
1390
+ "ctx.generation.streamed": false
1391
+ }
1392
+ });
1393
+ },
1394
+ onFinish: (result) => {
1395
+ if (params.streamed) {
1396
+ return;
1397
+ }
1398
+ this.logger?.debug("IgniterAgent.generate success", {
1399
+ "ctx.agent.name": this.getName(),
1400
+ "ctx.chatId": params.chatId,
1401
+ "ctx.userId": params.userId,
1402
+ "ctx.generation.usage.inputTokens": result.usage.inputTokens,
1403
+ "ctx.generation.usage.outputTokens": result.usage.outputTokens,
1404
+ "ctx.generation.usage.totalTokens": result.usage.totalTokens,
1405
+ "ctx.generation.streamed": false
1406
+ });
1407
+ this.telemetry?.emit("igniter.agent.generation.generate.success", {
1408
+ level: "debug",
1409
+ attributes: {
1410
+ "ctx.agent.name": this.getName(),
1411
+ "ctx.chatId": params.chatId,
1412
+ "ctx.userId": params.userId,
1413
+ "ctx.generation.usage.inputTokens": result.usage.inputTokens,
1414
+ "ctx.generation.usage.outputTokens": result.usage.outputTokens,
1415
+ "ctx.generation.usage.totalTokens": result.usage.totalTokens,
1416
+ "ctx.generation.streamed": false
1417
+ }
1418
+ });
1419
+ },
1420
+ activeTools: params.activeTools,
1421
+ experimental_context: IgniterAgentContext.create({
1422
+ context: params.context,
1423
+ memory: this._agent.memory,
1424
+ metadata: {
1425
+ agent: this.getName(),
1426
+ chatId: params.chatId,
1427
+ userId: params.userId,
1428
+ requestId: params.requestId,
1429
+ startTime: /* @__PURE__ */ new Date()
1430
+ }
1431
+ }),
1432
+ experimental_download: params.experimental_download,
1433
+ experimental_repairToolCall: params.experimental_repairToolCall,
1434
+ experimental_telemetry: params.experimental_telemetry,
1435
+ frequencyPenalty: params.frequencyPenalty,
1436
+ maxOutputTokens: params.maxOutputTokens,
1437
+ maxRetries: params.maxRetries,
1438
+ output: params.output,
1439
+ prepareStep: params.prepareStep,
1440
+ presencePenalty: params.presencePenalty,
1441
+ providerOptions: params.providerOptions,
1442
+ seed: params.seed,
1443
+ stopSequences: params.stopSequences,
1444
+ stopWhen: params.stopWhen,
1445
+ temperature: params.temperature,
1446
+ toolChoice: params.toolChoice,
1447
+ topK: params.topK,
1448
+ topP: params.topP,
1449
+ headers: params.headers
1450
+ });
1451
+ }
1452
+ async initializeChatMemory({
1453
+ chatId,
1454
+ userId,
1455
+ agentId,
1456
+ message
1457
+ }) {
1458
+ if (!this.memory || !this._agent.memory?.chats?.enabled) {
1459
+ return;
1460
+ }
1461
+ let chat = await this.memory.getChat(chatId);
1462
+ const { generateSuggestions, generateTitle } = this._agent.memory.chats;
1463
+ if (!chat) {
1464
+ let title = "New conversation";
1465
+ if (generateTitle?.enabled) {
1466
+ const response = await ai.generateText({
1467
+ model: generateTitle.model || this._agent.model,
1468
+ messages: [message],
1469
+ system: generateTitle.instructions || "Analyze the chat history and generate a title for the chat",
1470
+ output: ai.Output.object({
1471
+ schema: zod.z.object({
1472
+ title: zod.z.string().min(1).max(100)
1473
+ })
1474
+ })
1475
+ });
1476
+ title = response.output.title;
1173
1477
  }
1478
+ await this.memory.saveChat({
1479
+ chatId,
1480
+ createdAt: /* @__PURE__ */ new Date(),
1481
+ updatedAt: /* @__PURE__ */ new Date(),
1482
+ messageCount: 0,
1483
+ title
1484
+ });
1485
+ chat = await this.memory.getChat(chatId);
1174
1486
  }
1175
- const serializableTools = {};
1176
- for (const [toolName, tool2] of Object.entries(tools)) {
1177
- if (!tool2) continue;
1178
- const { execute, ...toolWithoutExecute } = tool2;
1179
- serializableTools[toolName] = {
1180
- ...toolWithoutExecute,
1181
- execute: tool2.execute
1182
- // Add back the function reference, not serialized
1183
- };
1487
+ if (!chat) {
1488
+ throw new IgniterAgentError({
1489
+ code: "IGNITER_AGENT_MISSING_REQUIRED" /* MISSING_REQUIRED */,
1490
+ message: "Chat not found and could not be created",
1491
+ metadata: {
1492
+ chatId,
1493
+ userId,
1494
+ agentId
1495
+ }
1496
+ });
1184
1497
  }
1185
- return new ai.ToolLoopAgent({
1186
- model: this._agent.model,
1187
- instructions: this._agent.instructions ? this._agent.instructions.build(context) : "",
1188
- tools: serializableTools,
1189
- callOptionsSchema: this._agent.schema
1498
+ if (!this._agent.memory?.history?.enabled) {
1499
+ return;
1500
+ }
1501
+ const messages = await this.memory.getMessages({
1502
+ chatId,
1503
+ limit: this._agent.memory?.history?.limit
1190
1504
  });
1505
+ return messages;
1506
+ }
1507
+ resolveMessageInput(params) {
1508
+ const message = params.message ?? params.messages?.[params.messages.length - 1];
1509
+ if (!message) {
1510
+ throw new IgniterAgentError({
1511
+ code: "IGNITER_AGENT_MISSING_REQUIRED" /* MISSING_REQUIRED */,
1512
+ message: "Either 'message' or 'messages' must be provided",
1513
+ metadata: {
1514
+ missing: "message"
1515
+ }
1516
+ });
1517
+ }
1518
+ const messages = params.message ? [message] : params.messages || [];
1519
+ return { message, messages };
1520
+ }
1521
+ initializeTools({
1522
+ chatId,
1523
+ userId,
1524
+ agentId
1525
+ }) {
1526
+ const toolsets = this.getToolsets();
1527
+ const allTools = {};
1528
+ for (const toolset of Object.values(toolsets)) {
1529
+ for (const [toolName, tool3] of Object.entries(toolset.tools)) {
1530
+ allTools[toolName] = {
1531
+ ...tool3,
1532
+ execute: async (args, options) => {
1533
+ if (!tool3.execute) {
1534
+ throw new IgniterAgentError({
1535
+ code: "IGNITER_AGENT_MISSING_REQUIRED" /* MISSING_REQUIRED */,
1536
+ message: "Tool does not have an execute function",
1537
+ metadata: { toolName }
1538
+ });
1539
+ }
1540
+ try {
1541
+ const startTime = Date.now();
1542
+ const toolsetName = toolset.name ?? "unknown";
1543
+ const toolAttributes = {
1544
+ "ctx.agent.name": this.getName(),
1545
+ "ctx.tool.toolset": toolsetName,
1546
+ "ctx.tool.name": toolName,
1547
+ "ctx.tool.fullName": `${toolsetName}.${toolName}`
1548
+ };
1549
+ this.hooks.onToolCallStart?.(this.getName(), toolName, args);
1550
+ this.telemetry?.emit("igniter.agent.tool.execute.started", {
1551
+ level: "debug",
1552
+ attributes: toolAttributes
1553
+ });
1554
+ const result = await tool3.execute(args, {
1555
+ ...options,
1556
+ experimental_context: {
1557
+ ...options.experimental_context || {},
1558
+ chatId,
1559
+ userId,
1560
+ agentId
1561
+ }
1562
+ });
1563
+ this.telemetry?.emit("igniter.agent.tool.execute.success", {
1564
+ level: "debug",
1565
+ attributes: {
1566
+ ...toolAttributes,
1567
+ "ctx.tool.durationMs": Date.now() - startTime
1568
+ }
1569
+ });
1570
+ this.hooks.onToolCallEnd?.(this.getName(), toolName, result);
1571
+ return {
1572
+ success: true,
1573
+ result
1574
+ };
1575
+ } catch (error) {
1576
+ const err = error instanceof Error ? error : new Error(String(error));
1577
+ const toolsetName = toolset.name ?? "unknown";
1578
+ this.telemetry?.emit("igniter.agent.tool.execute.error", {
1579
+ level: "error",
1580
+ attributes: {
1581
+ "ctx.agent.name": this.getName(),
1582
+ "ctx.tool.toolset": toolsetName,
1583
+ "ctx.tool.name": toolName,
1584
+ "ctx.tool.fullName": `${toolsetName}.${toolName}`,
1585
+ ...this.getErrorAttributes(err, "tool.execute")
1586
+ }
1587
+ });
1588
+ this.hooks.onToolCallError?.(this.getName(), toolName, err);
1589
+ throw err;
1590
+ }
1591
+ }
1592
+ };
1593
+ }
1594
+ }
1595
+ if (this._agent.memory?.working?.enabled) {
1596
+ const scope = this._agent.memory?.working.scope;
1597
+ const isChatMemory = scope === "chat";
1598
+ const identifier = isChatMemory ? chatId : userId;
1599
+ allTools["internal__update_working_memory"] = ai.tool({
1600
+ description: "Remember important information for later in the conversation",
1601
+ inputSchema: zod.z.object({
1602
+ content: zod.z.string().describe("Updated working memory following the template structure")
1603
+ }),
1604
+ execute: async (args, options) => {
1605
+ const { content } = args;
1606
+ if (this.memory) {
1607
+ await this.memory.updateWorkingMemory({
1608
+ scope,
1609
+ identifier,
1610
+ content
1611
+ });
1612
+ } else {
1613
+ await this._agent.memory?.provider.updateWorkingMemory({
1614
+ scope,
1615
+ identifier,
1616
+ content
1617
+ });
1618
+ }
1619
+ return {
1620
+ success: true
1621
+ };
1622
+ }
1623
+ });
1624
+ if (this._agent.memory?.history?.enabled) {
1625
+ allTools["internal__search_on_chat_history"] = ai.tool({
1626
+ description: "Search on chat history",
1627
+ inputSchema: zod.z.object({
1628
+ content: zod.z.string().describe("Search query"),
1629
+ limit: zod.z.number().optional().describe("Limit number of results"),
1630
+ dateFrom: zod.z.date().optional().describe("Search from date"),
1631
+ dateTo: zod.z.date().optional().describe("Search to date")
1632
+ }),
1633
+ execute: async (args, options) => {
1634
+ const { content, limit, dateFrom, dateTo } = args;
1635
+ const result = await this._agent.memory?.provider?.search?.({
1636
+ chatId,
1637
+ userId,
1638
+ limit,
1639
+ search: content,
1640
+ dateFrom,
1641
+ dateTo
1642
+ });
1643
+ return {
1644
+ success: true,
1645
+ result
1646
+ };
1647
+ }
1648
+ });
1649
+ }
1650
+ }
1651
+ return allTools;
1191
1652
  }
1192
1653
  async initializeMCPClient(mcpConfig) {
1193
1654
  if (this._agent.toolsets[mcpConfig.name]) {
@@ -1367,8 +1828,9 @@ var resolvePath = (value, path) => {
1367
1828
  }, value);
1368
1829
  };
1369
1830
  var IgniterAgentPromptBuilder = class _IgniterAgentPromptBuilder {
1370
- constructor(template) {
1831
+ constructor(template, appended) {
1371
1832
  this.template = template;
1833
+ this.appended = appended;
1372
1834
  }
1373
1835
  /**
1374
1836
  * Creates a new prompt builder.
@@ -1376,9 +1838,10 @@ var IgniterAgentPromptBuilder = class _IgniterAgentPromptBuilder {
1376
1838
  * @param template - Prompt template string with {{placeholders}}
1377
1839
  * @returns A new prompt builder instance
1378
1840
  */
1379
- static create(template) {
1841
+ static create(template, appended = {}) {
1380
1842
  return new _IgniterAgentPromptBuilder(
1381
- template
1843
+ template,
1844
+ appended
1382
1845
  );
1383
1846
  }
1384
1847
  /**
@@ -1388,10 +1851,53 @@ var IgniterAgentPromptBuilder = class _IgniterAgentPromptBuilder {
1388
1851
  * @returns The resolved prompt string
1389
1852
  */
1390
1853
  build(context) {
1391
- return this.template.replace(TEMPLATE_PATTERN, (_match, path) => {
1854
+ const resolveTemplate = (template) => template.replace(TEMPLATE_PATTERN, (_match, path) => {
1392
1855
  const value = resolvePath(context, String(path));
1393
1856
  return value === void 0 || value === null ? "" : String(value);
1394
1857
  });
1858
+ const appendedTemplates = Object.values(this.appended).map((prompt) => {
1859
+ if (typeof prompt === "string") {
1860
+ return resolveTemplate(prompt);
1861
+ }
1862
+ if (prompt && typeof prompt === "object" && "build" in prompt) {
1863
+ return prompt.build(context);
1864
+ }
1865
+ return "";
1866
+ });
1867
+ const appendedString = appendedTemplates.filter(Boolean).join("\n\n");
1868
+ const templateString = resolveTemplate(this.template);
1869
+ if (!appendedString) {
1870
+ return templateString;
1871
+ }
1872
+ return templateString + "\n\n" + appendedString;
1873
+ }
1874
+ /**
1875
+ * Appends another prompt template to this one.
1876
+ */
1877
+ addAppended(key, prompt) {
1878
+ return new _IgniterAgentPromptBuilder(
1879
+ this.template,
1880
+ {
1881
+ ...this.appended,
1882
+ [key]: prompt
1883
+ }
1884
+ );
1885
+ }
1886
+ /**
1887
+ * Removes an appended prompt template from this one.
1888
+ */
1889
+ removeAppended(key) {
1890
+ const { [key]: _, ...rest } = this.appended;
1891
+ return new _IgniterAgentPromptBuilder(
1892
+ this.template,
1893
+ rest
1894
+ );
1895
+ }
1896
+ /**
1897
+ * Returns the appended prompts.
1898
+ */
1899
+ getAppended() {
1900
+ return this.appended;
1395
1901
  }
1396
1902
  /**
1397
1903
  * Returns the raw prompt template string.
@@ -1667,13 +2173,13 @@ var IgniterAgentBuilder = class _IgniterAgentBuilder {
1667
2173
  *
1668
2174
  * // Context is type-safe when calling generate
1669
2175
  * await agent.generate({
1670
- * messages: [...],
1671
- * options: {
1672
- * userId: 'user_123',
1673
- * chatId: 'chat_456',
2176
+ * chatId: 'chat_456',
2177
+ * userId: 'user_123',
2178
+ * context: {
1674
2179
  * userRole: 'admin',
1675
2180
  * preferences: { language: 'pt' }
1676
- * }
2181
+ * },
2182
+ * message: { role: 'user', content: 'Hello!' }
1677
2183
  * });
1678
2184
  * ```
1679
2185
  *
@@ -1803,7 +2309,10 @@ var IgniterAgentBuilder = class _IgniterAgentBuilder {
1803
2309
  *
1804
2310
  * // Generate a response
1805
2311
  * const result = await agent.generate({
1806
- * messages: [{ role: 'user', content: 'Hello!' }]
2312
+ * chatId: 'chat_123',
2313
+ * userId: 'user_123',
2314
+ * context: {},
2315
+ * message: { role: 'user', content: 'Hello!' }
1807
2316
  * });
1808
2317
  *
1809
2318
  * // Access configuration
@@ -3736,144 +4245,12 @@ var IgniterAgentJSONFileAdapter = class _IgniterAgentJSONFileAdapter {
3736
4245
  }
3737
4246
  };
3738
4247
 
3739
- // src/utils/strings.ts
3740
- var IgniterAgentStringUtils = class {
3741
- /**
3742
- * Generates a unique identifier.
3743
- */
3744
- static generateId(prefix) {
3745
- const random = Math.random().toString(36).substring(2, 12);
3746
- return prefix ? `${prefix}_${random}` : random;
3747
- }
3748
- /**
3749
- * Truncates a string to a maximum length.
3750
- */
3751
- static truncate(str, maxLength, suffix = "...") {
3752
- if (str.length <= maxLength) return str;
3753
- return str.slice(0, maxLength - suffix.length) + suffix;
3754
- }
3755
- /**
3756
- * Converts a string to snake_case.
3757
- */
3758
- static toSnakeCase(str) {
3759
- return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
3760
- }
3761
- /**
3762
- * Converts a string to camelCase.
3763
- */
3764
- static toCamelCase(str) {
3765
- return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^(.)/, (char) => char.toLowerCase());
3766
- }
3767
- };
3768
-
3769
- // src/utils/objects.ts
3770
- var IgniterAgentObjectUtils = class _IgniterAgentObjectUtils {
3771
- /**
3772
- * Deep merges two objects.
3773
- */
3774
- static deepMerge(target, source) {
3775
- const result = { ...target };
3776
- for (const key in source) {
3777
- const sourceValue = source[key];
3778
- const targetValue = target[key];
3779
- if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) {
3780
- result[key] = _IgniterAgentObjectUtils.deepMerge(
3781
- targetValue,
3782
- sourceValue
3783
- );
3784
- } else {
3785
- result[key] = sourceValue;
3786
- }
3787
- }
3788
- return result;
3789
- }
3790
- /**
3791
- * Picks specified keys from an object.
3792
- */
3793
- static pick(obj, keys) {
3794
- const result = {};
3795
- for (const key of keys) {
3796
- if (key in obj) {
3797
- result[key] = obj[key];
3798
- }
3799
- }
3800
- return result;
3801
- }
3802
- /**
3803
- * Omits specified keys from an object.
3804
- */
3805
- static omit(obj, keys) {
3806
- const result = { ...obj };
3807
- for (const key of keys) {
3808
- delete result[key];
3809
- }
3810
- return result;
3811
- }
3812
- };
3813
-
3814
- // src/utils/async.ts
3815
- var IgniterAgentAsyncUtils = class _IgniterAgentAsyncUtils {
3816
- /**
3817
- * Delays execution for a specified duration.
3818
- */
3819
- static delay(ms) {
3820
- return new Promise((resolve) => setTimeout(resolve, ms));
3821
- }
3822
- /**
3823
- * Retries an async function with exponential backoff.
3824
- */
3825
- static async retry(fn, options = {}) {
3826
- const {
3827
- maxAttempts = 3,
3828
- baseDelay = 1e3,
3829
- maxDelay = 3e4,
3830
- onRetry
3831
- } = options;
3832
- let lastError;
3833
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
3834
- try {
3835
- return await fn();
3836
- } catch (error) {
3837
- lastError = error instanceof Error ? error : new Error(String(error));
3838
- if (attempt === maxAttempts) {
3839
- throw lastError;
3840
- }
3841
- onRetry?.(attempt, lastError);
3842
- const delayMs = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
3843
- await _IgniterAgentAsyncUtils.delay(delayMs);
3844
- }
3845
- }
3846
- throw lastError;
3847
- }
3848
- };
3849
-
3850
- // src/utils/validation.ts
3851
- var IgniterAgentValidationUtils = class {
3852
- /**
3853
- * Checks if a value is defined (not null or undefined).
3854
- */
3855
- static isDefined(value) {
3856
- return value !== null && value !== void 0;
3857
- }
3858
- /**
3859
- * Checks if a value is a non-empty string.
3860
- */
3861
- static isNonEmptyString(value) {
3862
- return typeof value === "string" && value.length > 0;
3863
- }
3864
- /**
3865
- * Checks if a value is a plain object.
3866
- */
3867
- static isPlainObject(value) {
3868
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
3869
- }
3870
- };
3871
-
3872
4248
  exports.IgniterAgent = IgniterAgent;
3873
4249
  exports.IgniterAgentAdapterError = IgniterAgentAdapterError;
3874
4250
  exports.IgniterAgentAsyncUtils = IgniterAgentAsyncUtils;
3875
4251
  exports.IgniterAgentBuilder = IgniterAgentBuilder;
3876
4252
  exports.IgniterAgentConfigError = IgniterAgentConfigError;
4253
+ exports.IgniterAgentContext = IgniterAgentContext;
3877
4254
  exports.IgniterAgentCore = IgniterAgentCore;
3878
4255
  exports.IgniterAgentError = IgniterAgentError;
3879
4256
  exports.IgniterAgentErrorCode = IgniterAgentErrorCode;