@codemation/core-nodes 0.0.14 → 0.0.16

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,6 +1,6 @@
1
- import { AgentConfigInspector, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, ItemsInputNormalizer, RetryPolicy, WorkflowBuilder, chatModel, inject, node } from "@codemation/core";
1
+ import { AgentConfigInspector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, ItemsInputNormalizer, NodeBackedToolConfig, RetryPolicy, WorkflowBuilder, chatModel, inject, injectable, node } from "@codemation/core";
2
2
  import { ChatOpenAI } from "@langchain/openai";
3
- import { HumanMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
3
+ import { AIMessage, HumanMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
4
4
  import { DynamicStructuredTool } from "@langchain/core/tools";
5
5
  import { CredentialResolverFactory } from "@codemation/core/bootstrap";
6
6
 
@@ -68,12 +68,18 @@ const openAiChatModelPresets = new OpenAiChatModelPresets();
68
68
  //#endregion
69
69
  //#region src/nodes/AgentMessageFactory.ts
70
70
  var AgentMessageFactory = class {
71
+ static createPromptMessages(messages) {
72
+ return messages.map((message) => this.createPromptMessage(message));
73
+ }
71
74
  static createSystemPrompt(systemMessage) {
72
75
  return new SystemMessage(systemMessage);
73
76
  }
74
77
  static createUserPrompt(prompt) {
75
78
  return new HumanMessage(prompt);
76
79
  }
80
+ static createAssistantPrompt(prompt) {
81
+ return new AIMessage(prompt);
82
+ }
77
83
  static createToolMessage(toolCallId, content) {
78
84
  return new ToolMessage({
79
85
  tool_call_id: toolCallId,
@@ -105,6 +111,11 @@ var AgentMessageFactory = class {
105
111
  static isRecord(value) {
106
112
  return typeof value === "object" && value !== null;
107
113
  }
114
+ static createPromptMessage(message) {
115
+ if (message.role === "system") return this.createSystemPrompt(message.content);
116
+ if (message.role === "assistant") return this.createAssistantPrompt(message.content);
117
+ return this.createUserPrompt(message.content);
118
+ }
108
119
  };
109
120
 
110
121
  //#endregion
@@ -160,12 +171,31 @@ var ConnectionCredentialExecutionContextFactory = class {
160
171
  };
161
172
 
162
173
  //#endregion
163
- //#region src/nodes/aiAgentSupport.types.ts
164
- var AgentItemPortMap = class {
165
- static fromItem(item) {
166
- return { in: [item] };
174
+ //#region src/nodes/AIAgentExecutionHelpersFactory.ts
175
+ let AIAgentExecutionHelpersFactory = class AIAgentExecutionHelpersFactory$1 {
176
+ createConnectionCredentialExecutionContextFactory(credentialSessions) {
177
+ return new ConnectionCredentialExecutionContextFactory(credentialSessions);
178
+ }
179
+ createDynamicStructuredTool(entry, toolCredentialContext, item, itemIndex, items) {
180
+ return new DynamicStructuredTool({
181
+ name: entry.config.name,
182
+ description: entry.config.description ?? entry.runtime.defaultDescription,
183
+ schema: entry.runtime.inputSchema,
184
+ func: async (input) => {
185
+ const result = await entry.runtime.execute({
186
+ config: entry.config,
187
+ input,
188
+ ctx: toolCredentialContext,
189
+ item,
190
+ itemIndex,
191
+ items
192
+ });
193
+ return JSON.stringify(result);
194
+ }
195
+ });
167
196
  }
168
197
  };
198
+ AIAgentExecutionHelpersFactory = __decorate([injectable()], AIAgentExecutionHelpersFactory);
169
199
 
170
200
  //#endregion
171
201
  //#region \0@oxc-project+runtime@0.95.0/helpers/decorateMetadata.js
@@ -182,55 +212,164 @@ function __decorateParam(paramIndex, decorator) {
182
212
  }
183
213
 
184
214
  //#endregion
185
- //#region src/nodes/AIAgentNodeFactory.ts
215
+ //#region src/nodes/NodeBackedToolRuntime.ts
216
+ let NodeBackedToolRuntime = class NodeBackedToolRuntime$1 {
217
+ constructor(nodeResolver) {
218
+ this.nodeResolver = nodeResolver;
219
+ }
220
+ async execute(config, args) {
221
+ const nodeInput = config.toNodeItem({
222
+ input: args.input,
223
+ item: args.item,
224
+ itemIndex: args.itemIndex,
225
+ items: args.items,
226
+ ctx: args.ctx,
227
+ node: config.node
228
+ });
229
+ const nodeCtx = {
230
+ ...args.ctx,
231
+ config: config.node
232
+ };
233
+ const resolvedNode = this.nodeResolver.resolve(config.node.type);
234
+ const outputs = await this.executeResolvedNode(resolvedNode, nodeInput, nodeCtx);
235
+ return config.toToolOutput({
236
+ input: args.input,
237
+ item: args.item,
238
+ itemIndex: args.itemIndex,
239
+ items: args.items,
240
+ ctx: args.ctx,
241
+ node: config.node,
242
+ outputs
243
+ });
244
+ }
245
+ async executeResolvedNode(resolvedNode, nodeInput, ctx) {
246
+ if (this.isMultiInputNode(resolvedNode)) return await resolvedNode.executeMulti({ in: [nodeInput] }, ctx);
247
+ if (this.isNode(resolvedNode)) return await resolvedNode.execute([nodeInput], ctx);
248
+ throw new Error(`Node-backed tool expected a runnable node instance for "${ctx.config.name ?? ctx.nodeId}".`);
249
+ }
250
+ isNode(value) {
251
+ return typeof value === "object" && value !== null && "execute" in value;
252
+ }
253
+ isMultiInputNode(value) {
254
+ return typeof value === "object" && value !== null && "executeMulti" in value;
255
+ }
256
+ };
257
+ NodeBackedToolRuntime = __decorate([
258
+ injectable(),
259
+ __decorateParam(0, inject(CoreTokens.NodeResolver)),
260
+ __decorateMetadata("design:paramtypes", [Object])
261
+ ], NodeBackedToolRuntime);
262
+
263
+ //#endregion
264
+ //#region src/nodes/aiAgentSupport.types.ts
265
+ var AgentItemPortMap = class {
266
+ static fromItem(item) {
267
+ return { in: [item] };
268
+ }
269
+ };
270
+
271
+ //#endregion
272
+ //#region src/nodes/AIAgentNode.ts
273
+ var _ref, _ref2;
186
274
  let AIAgentNode = class AIAgentNode$1 {
187
275
  kind = "node";
188
276
  outputPorts = ["main"];
189
277
  connectionCredentialExecutionContextFactory;
190
- constructor(nodeResolver, credentialSessions) {
278
+ constructor(nodeResolver, credentialSessions, nodeBackedToolRuntime, executionHelpers) {
191
279
  this.nodeResolver = nodeResolver;
192
- this.connectionCredentialExecutionContextFactory = new ConnectionCredentialExecutionContextFactory(credentialSessions);
280
+ this.nodeBackedToolRuntime = nodeBackedToolRuntime;
281
+ this.executionHelpers = executionHelpers;
282
+ this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
193
283
  }
194
284
  async execute(items, ctx) {
285
+ const prepared = await this.prepareExecution(ctx);
286
+ const out = [];
287
+ for (let i = 0; i < items.length; i++) out.push(await this.runAgentForItem(prepared, items[i], i, items));
288
+ return { main: out };
289
+ }
290
+ /**
291
+ * Resolves the chat model and tools once, then returns shared state for every item in the batch.
292
+ */
293
+ async prepareExecution(ctx) {
195
294
  const chatModelFactory = this.nodeResolver.resolve(ctx.config.chatModel.type);
196
295
  const languageModelCredentialContext = this.connectionCredentialExecutionContextFactory.forConnectionNode(ctx, {
197
296
  connectionNodeId: ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId),
198
297
  getCredentialRequirements: () => ctx.config.chatModel.getCredentialRequirements?.() ?? []
199
298
  });
200
- const model = await Promise.resolve(chatModelFactory.create({
201
- config: ctx.config.chatModel,
202
- ctx: languageModelCredentialContext
203
- }));
204
- const resolvedTools = this.resolveTools(ctx.config.tools ?? []);
205
- const out = [];
206
- for (let i = 0; i < items.length; i++) {
207
- const item = items[i];
208
- const prompt = ctx.config.userMessageFormatter(item, i, items, ctx);
209
- const itemInputsByPort = AgentItemPortMap.fromItem(item);
210
- const itemScopedTools = this.createItemScopedTools(resolvedTools, ctx, item, i, items);
211
- const firstResponse = await this.invokeModel(itemScopedTools.length > 0 && model.bindTools ? model.bindTools(itemScopedTools.map((entry) => entry.langChainTool)) : model, ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId), [AgentMessageFactory.createSystemPrompt(ctx.config.systemMessage), AgentMessageFactory.createUserPrompt(prompt)], ctx, itemInputsByPort);
212
- const toolCalls = AgentMessageFactory.extractToolCalls(firstResponse);
213
- if (toolCalls.length === 0) {
214
- out.push(AgentOutputFactory.replaceJson(item, AgentOutputFactory.fromAgentContent(AgentMessageFactory.extractContent(firstResponse))));
215
- continue;
299
+ return {
300
+ ctx,
301
+ model: await Promise.resolve(chatModelFactory.create({
302
+ config: ctx.config.chatModel,
303
+ ctx: languageModelCredentialContext
304
+ })),
305
+ resolvedTools: this.resolveTools(ctx.config.tools ?? []),
306
+ guardrails: this.resolveGuardrails(ctx.config.guardrails),
307
+ languageModelConnectionNodeId: ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId)
308
+ };
309
+ }
310
+ /**
311
+ * One item: build prompts, optionally bind tools, run the multi-turn loop, map the final model message to workflow JSON.
312
+ */
313
+ async runAgentForItem(prepared, item, itemIndex, items) {
314
+ const { ctx } = prepared;
315
+ const itemInputsByPort = AgentItemPortMap.fromItem(item);
316
+ const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, itemIndex, items);
317
+ const conversation = [...this.createPromptMessages(item, itemIndex, items, ctx)];
318
+ const modelWithTools = this.bindToolsToModel(prepared.model, itemScopedTools);
319
+ const finalResponse = await this.runTurnLoopUntilFinalAnswer({
320
+ prepared,
321
+ itemInputsByPort,
322
+ itemScopedTools,
323
+ conversation,
324
+ modelWithTools
325
+ });
326
+ return this.buildOutputItem(item, finalResponse);
327
+ }
328
+ /**
329
+ * Repeatedly invokes the model until it returns without tool calls, or guardrails end the loop.
330
+ */
331
+ async runTurnLoopUntilFinalAnswer(args) {
332
+ const { prepared, itemInputsByPort, itemScopedTools, conversation, modelWithTools } = args;
333
+ const { ctx, guardrails, languageModelConnectionNodeId } = prepared;
334
+ let finalResponse;
335
+ for (let turn = 1; turn <= guardrails.maxTurns; turn++) {
336
+ const response = await this.invokeModel(modelWithTools, languageModelConnectionNodeId, conversation, ctx, itemInputsByPort, guardrails.modelInvocationOptions);
337
+ finalResponse = response;
338
+ const toolCalls = AgentMessageFactory.extractToolCalls(response);
339
+ if (toolCalls.length === 0) break;
340
+ if (this.cannotExecuteAnotherToolRound(turn, guardrails)) {
341
+ this.finishOrThrowWhenTurnCapHitWithToolCalls(ctx, guardrails);
342
+ break;
216
343
  }
217
344
  const plannedToolCalls = this.planToolCalls(itemScopedTools, toolCalls, ctx.nodeId);
218
345
  await this.markQueuedTools(plannedToolCalls, ctx);
219
346
  const executedToolCalls = await this.executeToolCalls(plannedToolCalls, ctx);
220
- const finalResponse = await this.invokeModel(itemScopedTools.length > 0 && model.bindTools ? model.bindTools(itemScopedTools.map((entry) => entry.langChainTool)) : model, ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId), [
221
- AgentMessageFactory.createSystemPrompt(ctx.config.systemMessage),
222
- AgentMessageFactory.createUserPrompt(prompt),
223
- firstResponse,
224
- ...executedToolCalls.map((toolCall) => AgentMessageFactory.createToolMessage(toolCall.toolCallId, toolCall.serialized))
225
- ], ctx, itemInputsByPort);
226
- out.push(AgentOutputFactory.replaceJson(item, AgentOutputFactory.fromAgentContent(AgentMessageFactory.extractContent(finalResponse))));
347
+ this.appendAssistantAndToolMessages(conversation, response, executedToolCalls);
227
348
  }
228
- return { main: out };
349
+ if (!finalResponse) throw new Error(`AIAgent "${ctx.config.name ?? ctx.nodeId}" did not produce a model response.`);
350
+ return finalResponse;
351
+ }
352
+ cannotExecuteAnotherToolRound(turn, guardrails) {
353
+ return turn >= guardrails.maxTurns;
354
+ }
355
+ finishOrThrowWhenTurnCapHitWithToolCalls(ctx, guardrails) {
356
+ if (guardrails.onTurnLimitReached === "respondWithLastMessage") return;
357
+ throw new Error(`AIAgent "${ctx.config.name ?? ctx.nodeId}" reached maxTurns=${guardrails.maxTurns} before producing a final response.`);
358
+ }
359
+ appendAssistantAndToolMessages(conversation, assistantMessage, executedToolCalls) {
360
+ conversation.push(assistantMessage, ...executedToolCalls.map((toolCall) => AgentMessageFactory.createToolMessage(toolCall.toolCallId, toolCall.serialized)));
361
+ }
362
+ buildOutputItem(item, finalResponse) {
363
+ return AgentOutputFactory.replaceJson(item, AgentOutputFactory.fromAgentContent(AgentMessageFactory.extractContent(finalResponse)));
364
+ }
365
+ bindToolsToModel(model, itemScopedTools) {
366
+ if (itemScopedTools.length === 0 || !model.bindTools) return model;
367
+ return model.bindTools(itemScopedTools.map((entry) => entry.langChainTool));
229
368
  }
230
369
  resolveTools(toolConfigs) {
231
370
  const resolvedTools = toolConfigs.map((config) => ({
232
371
  config,
233
- tool: this.nodeResolver.resolve(config.type)
372
+ runtime: this.resolveToolRuntime(config)
234
373
  }));
235
374
  const names = /* @__PURE__ */ new Set();
236
375
  for (const entry of resolvedTools) {
@@ -245,29 +384,14 @@ let AIAgentNode = class AIAgentNode$1 {
245
384
  connectionNodeId: ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
246
385
  getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
247
386
  });
248
- const langChainTool = new DynamicStructuredTool({
249
- name: entry.config.name,
250
- description: entry.config.description ?? entry.tool.defaultDescription,
251
- schema: entry.tool.inputSchema,
252
- func: async (input) => {
253
- const result = await entry.tool.execute({
254
- config: entry.config,
255
- input,
256
- ctx: toolCredentialContext,
257
- item,
258
- itemIndex,
259
- items
260
- });
261
- return JSON.stringify(result);
262
- }
263
- });
387
+ const langChainTool = this.executionHelpers.createDynamicStructuredTool(entry, toolCredentialContext, item, itemIndex, items);
264
388
  return {
265
389
  config: entry.config,
266
390
  langChainTool
267
391
  };
268
392
  });
269
393
  }
270
- async invokeModel(model, nodeId, messages, ctx, inputsByPort) {
394
+ async invokeModel(model, nodeId, messages, ctx, inputsByPort, options) {
271
395
  await ctx.nodeState?.markQueued({
272
396
  nodeId,
273
397
  activationId: ctx.activationId,
@@ -279,7 +403,7 @@ let AIAgentNode = class AIAgentNode$1 {
279
403
  inputsByPort
280
404
  });
281
405
  try {
282
- const response = await model.invoke(messages);
406
+ const response = await model.invoke(messages, options);
283
407
  await ctx.nodeState?.markCompleted({
284
408
  nodeId,
285
409
  activationId: ctx.activationId,
@@ -413,12 +537,49 @@ let AIAgentNode = class AIAgentNode$1 {
413
537
  const json = JSON.stringify(value);
414
538
  return JSON.parse(json);
415
539
  }
540
+ createPromptMessages(item, itemIndex, items, ctx) {
541
+ return AgentMessageFactory.createPromptMessages(AgentMessageConfigNormalizer.normalize(ctx.config, {
542
+ item,
543
+ itemIndex,
544
+ items,
545
+ ctx
546
+ }));
547
+ }
548
+ resolveToolRuntime(config) {
549
+ if (config instanceof NodeBackedToolConfig) return {
550
+ defaultDescription: `Run workflow node "${config.node.name ?? config.name}" as an AI tool.`,
551
+ inputSchema: config.getInputSchema(),
552
+ execute: async (args) => await this.nodeBackedToolRuntime.execute(config, args)
553
+ };
554
+ const tool = this.nodeResolver.resolve(config.type);
555
+ return {
556
+ defaultDescription: tool.defaultDescription,
557
+ inputSchema: tool.inputSchema,
558
+ execute: async (args) => await Promise.resolve(tool.execute(args))
559
+ };
560
+ }
561
+ resolveGuardrails(guardrails) {
562
+ const maxTurns = guardrails?.maxTurns ?? AgentGuardrailDefaults.maxTurns;
563
+ if (!Number.isInteger(maxTurns) || maxTurns < 1) throw new Error(`AIAgent maxTurns must be a positive integer. Received: ${String(maxTurns)}`);
564
+ return {
565
+ maxTurns,
566
+ onTurnLimitReached: guardrails?.onTurnLimitReached ?? AgentGuardrailDefaults.onTurnLimitReached,
567
+ modelInvocationOptions: guardrails?.modelInvocationOptions
568
+ };
569
+ }
416
570
  };
417
571
  AIAgentNode = __decorate([
418
572
  node({ packageName: "@codemation/core-nodes" }),
419
573
  __decorateParam(0, inject(CoreTokens.NodeResolver)),
420
574
  __decorateParam(1, inject(CoreTokens.CredentialSessionService)),
421
- __decorateMetadata("design:paramtypes", [Object, Object])
575
+ __decorateParam(2, inject(NodeBackedToolRuntime)),
576
+ __decorateParam(3, inject(AIAgentExecutionHelpersFactory)),
577
+ __decorateMetadata("design:paramtypes", [
578
+ Object,
579
+ Object,
580
+ typeof (_ref = typeof NodeBackedToolRuntime !== "undefined" && NodeBackedToolRuntime) === "function" ? _ref : Object,
581
+ typeof (_ref2 = typeof AIAgentExecutionHelpersFactory !== "undefined" && AIAgentExecutionHelpersFactory) === "function" ? _ref2 : Object
582
+ ])
422
583
  ], AIAgentNode);
423
584
 
424
585
  //#endregion
@@ -432,14 +593,21 @@ var AIAgent = class {
432
593
  type = AIAgentNode;
433
594
  execution = { hint: "local" };
434
595
  icon = "lucide:bot";
435
- constructor(name, systemMessage, userMessageFormatter, chatModel$1, tools = [], id, retryPolicy = RetryPolicy.defaultForAiAgent) {
436
- this.name = name;
437
- this.systemMessage = systemMessage;
438
- this.userMessageFormatter = userMessageFormatter;
439
- this.chatModel = chatModel$1;
440
- this.tools = tools;
441
- this.id = id;
442
- this.retryPolicy = retryPolicy;
596
+ name;
597
+ messages;
598
+ chatModel;
599
+ tools;
600
+ id;
601
+ retryPolicy;
602
+ guardrails;
603
+ constructor(options) {
604
+ this.name = options.name;
605
+ this.messages = options.messages;
606
+ this.chatModel = options.chatModel;
607
+ this.tools = options.tools ?? [];
608
+ this.id = options.id;
609
+ this.retryPolicy = options.retryPolicy ?? RetryPolicy.defaultForAiAgent;
610
+ this.guardrails = options.guardrails;
443
611
  }
444
612
  };
445
613
 
@@ -1113,5 +1281,5 @@ var ConnectionCredentialNodeConfigFactory = class {
1113
1281
  };
1114
1282
 
1115
1283
  //#endregion
1116
- export { AIAgent, AIAgentConnectionWorkflowExpander, AIAgentNode, AgentItemPortMap, AgentMessageFactory, AgentOutputFactory, AgentToolCallPortMap, Callback, CallbackNode, CallbackResultNormalizer, ConnectionCredentialExecutionContextFactory, ConnectionCredentialNode, ConnectionCredentialNodeConfig, ConnectionCredentialNodeConfigFactory, HttpRequest, HttpRequestNode, If, IfNode, ManualTrigger, ManualTriggerNode, MapData, MapDataNode, Merge, MergeNode, NoOp, NoOpNode, OpenAIChatModelConfig, OpenAIChatModelFactory, OpenAiChatModelPresets, SubWorkflow, SubWorkflowNode, Wait, WaitDuration, WaitNode, WebhookRespondNowAndContinueError, WebhookRespondNowError, WebhookTrigger, WebhookTriggerNode, createWorkflowBuilder, openAiChatModelPresets, registerCoreNodes };
1284
+ export { AIAgent, AIAgentConnectionWorkflowExpander, AIAgentExecutionHelpersFactory, AIAgentNode, AgentItemPortMap, AgentMessageFactory, AgentOutputFactory, AgentToolCallPortMap, Callback, CallbackNode, CallbackResultNormalizer, ConnectionCredentialExecutionContextFactory, ConnectionCredentialNode, ConnectionCredentialNodeConfig, ConnectionCredentialNodeConfigFactory, HttpRequest, HttpRequestNode, If, IfNode, ManualTrigger, ManualTriggerNode, MapData, MapDataNode, Merge, MergeNode, NoOp, NoOpNode, OpenAIChatModelConfig, OpenAIChatModelFactory, OpenAiChatModelPresets, SubWorkflow, SubWorkflowNode, Wait, WaitDuration, WaitNode, WebhookRespondNowAndContinueError, WebhookRespondNowError, WebhookTrigger, WebhookTriggerNode, createWorkflowBuilder, openAiChatModelPresets, registerCoreNodes };
1117
1285
  //# sourceMappingURL=index.js.map