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