@codemation/core-nodes 0.0.14 → 0.0.15
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.cjs +233 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +191 -53
- package/dist/index.d.ts +191 -53
- package/dist/index.js +230 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/nodes/AIAgentConfig.ts +29 -18
- package/src/nodes/AIAgentExecutionHelpersFactory.ts +45 -0
- package/src/nodes/{AIAgentNodeFactory.ts → AIAgentNode.ts} +201 -70
- package/src/nodes/AgentMessageFactory.ts +20 -2
- package/src/nodes/NodeBackedToolRuntime.ts +70 -0
- package/src/nodes/aiAgent.ts +2 -1
- package/src/nodes/aiAgentSupport.types.ts +13 -2
- package/src/register.types.ts +2 -1
- package/src/workflows/AIAgentConnectionWorkflowExpander.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AgentGuardrailConfig,
|
|
2
3
|
AgentToolCall,
|
|
3
4
|
ChatModelConfig,
|
|
4
5
|
ChatModelFactory,
|
|
@@ -17,9 +18,12 @@ import type {
|
|
|
17
18
|
|
|
18
19
|
import type { CredentialSessionService } from "@codemation/core";
|
|
19
20
|
import {
|
|
21
|
+
AgentGuardrailDefaults,
|
|
22
|
+
AgentMessageConfigNormalizer,
|
|
20
23
|
ConnectionInvocationIdFactory,
|
|
21
24
|
ConnectionNodeIdFactory,
|
|
22
25
|
CoreTokens,
|
|
26
|
+
NodeBackedToolConfig,
|
|
23
27
|
inject,
|
|
24
28
|
node,
|
|
25
29
|
type NodeResolver,
|
|
@@ -27,13 +31,13 @@ import {
|
|
|
27
31
|
|
|
28
32
|
import { AIMessage, type BaseMessage } from "@langchain/core/messages";
|
|
29
33
|
|
|
30
|
-
import { DynamicStructuredTool } from "@langchain/core/tools";
|
|
31
|
-
|
|
32
34
|
import type { AIAgent } from "./AIAgentConfig";
|
|
35
|
+
import { AIAgentExecutionHelpersFactory } from "./AIAgentExecutionHelpersFactory";
|
|
33
36
|
import { ConnectionCredentialExecutionContextFactory } from "./ConnectionCredentialExecutionContextFactory";
|
|
34
37
|
import { AgentMessageFactory } from "./AgentMessageFactory";
|
|
35
38
|
import { AgentOutputFactory } from "./AgentOutputFactory";
|
|
36
39
|
import { AgentToolCallPortMap } from "./AgentToolCallPortMapFactory";
|
|
40
|
+
import { NodeBackedToolRuntime } from "./NodeBackedToolRuntime";
|
|
37
41
|
import {
|
|
38
42
|
AgentItemPortMap,
|
|
39
43
|
type ExecutedToolCall,
|
|
@@ -42,6 +46,18 @@ import {
|
|
|
42
46
|
type ResolvedTool,
|
|
43
47
|
} from "./aiAgentSupport.types";
|
|
44
48
|
|
|
49
|
+
type ResolvedGuardrails = Required<Pick<AgentGuardrailConfig, "maxTurns" | "onTurnLimitReached">> &
|
|
50
|
+
Pick<AgentGuardrailConfig, "modelInvocationOptions">;
|
|
51
|
+
|
|
52
|
+
/** Everything needed to run the agent loop for a workflow execution (one `execute` call). */
|
|
53
|
+
interface PreparedAgentExecution {
|
|
54
|
+
readonly ctx: NodeExecutionContext<AIAgent<any, any>>;
|
|
55
|
+
readonly model: LangChainChatModelLike;
|
|
56
|
+
readonly resolvedTools: ReadonlyArray<ResolvedTool>;
|
|
57
|
+
readonly guardrails: ResolvedGuardrails;
|
|
58
|
+
readonly languageModelConnectionNodeId: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
@node({ packageName: "@codemation/core-nodes" })
|
|
46
62
|
export class AIAgentNode implements Node<AIAgent<any, any>> {
|
|
47
63
|
kind = "node" as const;
|
|
@@ -54,13 +70,28 @@ export class AIAgentNode implements Node<AIAgent<any, any>> {
|
|
|
54
70
|
private readonly nodeResolver: NodeResolver,
|
|
55
71
|
@inject(CoreTokens.CredentialSessionService)
|
|
56
72
|
credentialSessions: CredentialSessionService,
|
|
73
|
+
@inject(NodeBackedToolRuntime)
|
|
74
|
+
private readonly nodeBackedToolRuntime: NodeBackedToolRuntime,
|
|
75
|
+
@inject(AIAgentExecutionHelpersFactory)
|
|
76
|
+
private readonly executionHelpers: AIAgentExecutionHelpersFactory,
|
|
57
77
|
) {
|
|
58
|
-
this.connectionCredentialExecutionContextFactory =
|
|
59
|
-
credentialSessions
|
|
60
|
-
);
|
|
78
|
+
this.connectionCredentialExecutionContextFactory =
|
|
79
|
+
this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
async execute(items: Items, ctx: NodeExecutionContext<AIAgent<any, any>>): Promise<NodeOutputs> {
|
|
83
|
+
const prepared = await this.prepareExecution(ctx);
|
|
84
|
+
const out: Item[] = [];
|
|
85
|
+
for (let i = 0; i < items.length; i++) {
|
|
86
|
+
out.push(await this.runAgentForItem(prepared, items[i]!, i, items));
|
|
87
|
+
}
|
|
88
|
+
return { main: out };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolves the chat model and tools once, then returns shared state for every item in the batch.
|
|
93
|
+
*/
|
|
94
|
+
private async prepareExecution(ctx: NodeExecutionContext<AIAgent<any, any>>): Promise<PreparedAgentExecution> {
|
|
64
95
|
const chatModelFactory = this.nodeResolver.resolve(ctx.config.chatModel.type) as ChatModelFactory<ChatModelConfig>;
|
|
65
96
|
const languageModelCredentialContext = this.connectionCredentialExecutionContextFactory.forConnectionNode(ctx, {
|
|
66
97
|
connectionNodeId: ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId),
|
|
@@ -69,73 +100,137 @@ export class AIAgentNode implements Node<AIAgent<any, any>> {
|
|
|
69
100
|
const model = await Promise.resolve(
|
|
70
101
|
chatModelFactory.create({ config: ctx.config.chatModel, ctx: languageModelCredentialContext }),
|
|
71
102
|
);
|
|
72
|
-
|
|
103
|
+
return {
|
|
104
|
+
ctx,
|
|
105
|
+
model,
|
|
106
|
+
resolvedTools: this.resolveTools(ctx.config.tools ?? []),
|
|
107
|
+
guardrails: this.resolveGuardrails(ctx.config.guardrails),
|
|
108
|
+
languageModelConnectionNodeId: ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
73
111
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
/**
|
|
113
|
+
* One item: build prompts, optionally bind tools, run the multi-turn loop, map the final model message to workflow JSON.
|
|
114
|
+
*/
|
|
115
|
+
private async runAgentForItem(
|
|
116
|
+
prepared: PreparedAgentExecution,
|
|
117
|
+
item: Item,
|
|
118
|
+
itemIndex: number,
|
|
119
|
+
items: Items,
|
|
120
|
+
): Promise<Item> {
|
|
121
|
+
const { ctx } = prepared;
|
|
122
|
+
const itemInputsByPort = AgentItemPortMap.fromItem(item);
|
|
123
|
+
const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, itemIndex, items);
|
|
124
|
+
const conversation: BaseMessage[] = [...this.createPromptMessages(item, itemIndex, items, ctx)];
|
|
125
|
+
const modelWithTools = this.bindToolsToModel(prepared.model, itemScopedTools);
|
|
126
|
+
const finalResponse = await this.runTurnLoopUntilFinalAnswer({
|
|
127
|
+
prepared,
|
|
128
|
+
itemInputsByPort,
|
|
129
|
+
itemScopedTools,
|
|
130
|
+
conversation,
|
|
131
|
+
modelWithTools,
|
|
132
|
+
});
|
|
133
|
+
return this.buildOutputItem(item, finalResponse);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Repeatedly invokes the model until it returns without tool calls, or guardrails end the loop.
|
|
138
|
+
*/
|
|
139
|
+
private async runTurnLoopUntilFinalAnswer(args: {
|
|
140
|
+
prepared: PreparedAgentExecution;
|
|
141
|
+
itemInputsByPort: NodeInputsByPort;
|
|
142
|
+
itemScopedTools: ReadonlyArray<ItemScopedToolBinding>;
|
|
143
|
+
conversation: BaseMessage[];
|
|
144
|
+
modelWithTools: LangChainChatModelLike;
|
|
145
|
+
}): Promise<AIMessage> {
|
|
146
|
+
const { prepared, itemInputsByPort, itemScopedTools, conversation, modelWithTools } = args;
|
|
147
|
+
const { ctx, guardrails, languageModelConnectionNodeId } = prepared;
|
|
148
|
+
|
|
149
|
+
let finalResponse: AIMessage | undefined;
|
|
150
|
+
|
|
151
|
+
for (let turn = 1; turn <= guardrails.maxTurns; turn++) {
|
|
152
|
+
const response = await this.invokeModel(
|
|
153
|
+
modelWithTools,
|
|
154
|
+
languageModelConnectionNodeId,
|
|
155
|
+
conversation,
|
|
89
156
|
ctx,
|
|
90
157
|
itemInputsByPort,
|
|
158
|
+
guardrails.modelInvocationOptions,
|
|
91
159
|
);
|
|
160
|
+
finalResponse = response;
|
|
92
161
|
|
|
93
|
-
const toolCalls = AgentMessageFactory.extractToolCalls(
|
|
162
|
+
const toolCalls = AgentMessageFactory.extractToolCalls(response);
|
|
94
163
|
if (toolCalls.length === 0) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
continue;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.cannotExecuteAnotherToolRound(turn, guardrails)) {
|
|
168
|
+
this.finishOrThrowWhenTurnCapHitWithToolCalls(ctx, guardrails);
|
|
169
|
+
break;
|
|
102
170
|
}
|
|
103
171
|
|
|
104
172
|
const plannedToolCalls = this.planToolCalls(itemScopedTools, toolCalls, ctx.nodeId);
|
|
105
173
|
await this.markQueuedTools(plannedToolCalls, ctx);
|
|
106
174
|
const executedToolCalls = await this.executeToolCalls(plannedToolCalls, ctx);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
? model.bindTools(itemScopedTools.map((entry) => entry.langChainTool))
|
|
110
|
-
: model,
|
|
111
|
-
ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId),
|
|
112
|
-
[
|
|
113
|
-
AgentMessageFactory.createSystemPrompt(ctx.config.systemMessage),
|
|
114
|
-
AgentMessageFactory.createUserPrompt(prompt),
|
|
115
|
-
firstResponse,
|
|
116
|
-
...executedToolCalls.map((toolCall) =>
|
|
117
|
-
AgentMessageFactory.createToolMessage(toolCall.toolCallId, toolCall.serialized),
|
|
118
|
-
),
|
|
119
|
-
],
|
|
120
|
-
ctx,
|
|
121
|
-
itemInputsByPort,
|
|
122
|
-
);
|
|
175
|
+
this.appendAssistantAndToolMessages(conversation, response, executedToolCalls);
|
|
176
|
+
}
|
|
123
177
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
item,
|
|
127
|
-
AgentOutputFactory.fromAgentContent(AgentMessageFactory.extractContent(finalResponse)),
|
|
128
|
-
),
|
|
129
|
-
);
|
|
178
|
+
if (!finalResponse) {
|
|
179
|
+
throw new Error(`AIAgent "${ctx.config.name ?? ctx.nodeId}" did not produce a model response.`);
|
|
130
180
|
}
|
|
181
|
+
return finalResponse;
|
|
182
|
+
}
|
|
131
183
|
|
|
132
|
-
|
|
184
|
+
private cannotExecuteAnotherToolRound(turn: number, guardrails: ResolvedGuardrails): boolean {
|
|
185
|
+
return turn >= guardrails.maxTurns;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private finishOrThrowWhenTurnCapHitWithToolCalls(
|
|
189
|
+
ctx: NodeExecutionContext<AIAgent<any, any>>,
|
|
190
|
+
guardrails: ResolvedGuardrails,
|
|
191
|
+
): void {
|
|
192
|
+
if (guardrails.onTurnLimitReached === "respondWithLastMessage") {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw new Error(
|
|
196
|
+
`AIAgent "${ctx.config.name ?? ctx.nodeId}" reached maxTurns=${guardrails.maxTurns} before producing a final response.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private appendAssistantAndToolMessages(
|
|
201
|
+
conversation: BaseMessage[],
|
|
202
|
+
assistantMessage: AIMessage,
|
|
203
|
+
executedToolCalls: ReadonlyArray<ExecutedToolCall>,
|
|
204
|
+
): void {
|
|
205
|
+
conversation.push(
|
|
206
|
+
assistantMessage,
|
|
207
|
+
...executedToolCalls.map((toolCall) =>
|
|
208
|
+
AgentMessageFactory.createToolMessage(toolCall.toolCallId, toolCall.serialized),
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private buildOutputItem(item: Item, finalResponse: AIMessage): Item {
|
|
214
|
+
return AgentOutputFactory.replaceJson(
|
|
215
|
+
item,
|
|
216
|
+
AgentOutputFactory.fromAgentContent(AgentMessageFactory.extractContent(finalResponse)),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private bindToolsToModel(
|
|
221
|
+
model: LangChainChatModelLike,
|
|
222
|
+
itemScopedTools: ReadonlyArray<ItemScopedToolBinding>,
|
|
223
|
+
): LangChainChatModelLike {
|
|
224
|
+
if (itemScopedTools.length === 0 || !model.bindTools) {
|
|
225
|
+
return model;
|
|
226
|
+
}
|
|
227
|
+
return model.bindTools(itemScopedTools.map((entry) => entry.langChainTool));
|
|
133
228
|
}
|
|
134
229
|
|
|
135
230
|
private resolveTools(toolConfigs: ReadonlyArray<ToolConfig>): ReadonlyArray<ResolvedTool> {
|
|
136
231
|
const resolvedTools = toolConfigs.map((config) => ({
|
|
137
232
|
config,
|
|
138
|
-
|
|
233
|
+
runtime: this.resolveToolRuntime(config),
|
|
139
234
|
}));
|
|
140
235
|
|
|
141
236
|
const names = new Set<string>();
|
|
@@ -158,22 +253,13 @@ export class AIAgentNode implements Node<AIAgent<any, any>> {
|
|
|
158
253
|
connectionNodeId: ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
|
|
159
254
|
getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? [],
|
|
160
255
|
});
|
|
161
|
-
const langChainTool =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
input,
|
|
169
|
-
ctx: toolCredentialContext,
|
|
170
|
-
item,
|
|
171
|
-
itemIndex,
|
|
172
|
-
items,
|
|
173
|
-
});
|
|
174
|
-
return JSON.stringify(result);
|
|
175
|
-
},
|
|
176
|
-
});
|
|
256
|
+
const langChainTool = this.executionHelpers.createDynamicStructuredTool(
|
|
257
|
+
entry,
|
|
258
|
+
toolCredentialContext,
|
|
259
|
+
item,
|
|
260
|
+
itemIndex,
|
|
261
|
+
items,
|
|
262
|
+
);
|
|
177
263
|
|
|
178
264
|
return { config: entry.config, langChainTool };
|
|
179
265
|
});
|
|
@@ -185,11 +271,12 @@ export class AIAgentNode implements Node<AIAgent<any, any>> {
|
|
|
185
271
|
messages: ReadonlyArray<BaseMessage>,
|
|
186
272
|
ctx: NodeExecutionContext<AIAgent<any, any>>,
|
|
187
273
|
inputsByPort: NodeInputsByPort,
|
|
274
|
+
options?: AgentGuardrailConfig["modelInvocationOptions"],
|
|
188
275
|
): Promise<AIMessage> {
|
|
189
276
|
await ctx.nodeState?.markQueued({ nodeId, activationId: ctx.activationId, inputsByPort });
|
|
190
277
|
await ctx.nodeState?.markRunning({ nodeId, activationId: ctx.activationId, inputsByPort });
|
|
191
278
|
try {
|
|
192
|
-
const response = (await model.invoke(messages)) as AIMessage;
|
|
279
|
+
const response = (await model.invoke(messages, options)) as AIMessage;
|
|
193
280
|
await ctx.nodeState?.markCompleted({
|
|
194
281
|
nodeId,
|
|
195
282
|
activationId: ctx.activationId,
|
|
@@ -372,4 +459,48 @@ export class AIAgentNode implements Node<AIAgent<any, any>> {
|
|
|
372
459
|
const json = JSON.stringify(value);
|
|
373
460
|
return JSON.parse(json) as JsonValue;
|
|
374
461
|
}
|
|
462
|
+
|
|
463
|
+
private createPromptMessages(
|
|
464
|
+
item: Item,
|
|
465
|
+
itemIndex: number,
|
|
466
|
+
items: Items,
|
|
467
|
+
ctx: NodeExecutionContext<AIAgent<any, any>>,
|
|
468
|
+
): ReadonlyArray<BaseMessage> {
|
|
469
|
+
return AgentMessageFactory.createPromptMessages(
|
|
470
|
+
AgentMessageConfigNormalizer.normalize(ctx.config, {
|
|
471
|
+
item,
|
|
472
|
+
itemIndex,
|
|
473
|
+
items,
|
|
474
|
+
ctx,
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private resolveToolRuntime(config: ToolConfig): ResolvedTool["runtime"] {
|
|
480
|
+
if (config instanceof NodeBackedToolConfig) {
|
|
481
|
+
return {
|
|
482
|
+
defaultDescription: `Run workflow node "${config.node.name ?? config.name}" as an AI tool.`,
|
|
483
|
+
inputSchema: config.getInputSchema(),
|
|
484
|
+
execute: async (args) => await this.nodeBackedToolRuntime.execute(config, args),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const tool = this.nodeResolver.resolve(config.type) as Tool<ToolConfig, ZodSchemaAny, ZodSchemaAny>;
|
|
488
|
+
return {
|
|
489
|
+
defaultDescription: tool.defaultDescription,
|
|
490
|
+
inputSchema: tool.inputSchema,
|
|
491
|
+
execute: async (args) => await Promise.resolve(tool.execute(args)),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private resolveGuardrails(guardrails: AgentGuardrailConfig | undefined): ResolvedGuardrails {
|
|
496
|
+
const maxTurns = guardrails?.maxTurns ?? AgentGuardrailDefaults.maxTurns;
|
|
497
|
+
if (!Number.isInteger(maxTurns) || maxTurns < 1) {
|
|
498
|
+
throw new Error(`AIAgent maxTurns must be a positive integer. Received: ${String(maxTurns)}`);
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
maxTurns,
|
|
502
|
+
onTurnLimitReached: guardrails?.onTurnLimitReached ?? AgentGuardrailDefaults.onTurnLimitReached,
|
|
503
|
+
modelInvocationOptions: guardrails?.modelInvocationOptions,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
375
506
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { AgentToolCall } from "@codemation/core";
|
|
1
|
+
import type { AgentMessageDto, AgentToolCall } from "@codemation/core";
|
|
2
2
|
|
|
3
|
-
import { HumanMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
|
|
3
|
+
import { AIMessage, HumanMessage, SystemMessage, ToolMessage, type BaseMessage } from "@langchain/core/messages";
|
|
4
4
|
|
|
5
5
|
export class AgentMessageFactory {
|
|
6
|
+
static createPromptMessages(messages: ReadonlyArray<AgentMessageDto>): ReadonlyArray<BaseMessage> {
|
|
7
|
+
return messages.map((message) => this.createPromptMessage(message));
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
static createSystemPrompt(systemMessage: string): SystemMessage {
|
|
7
11
|
return new SystemMessage(systemMessage);
|
|
8
12
|
}
|
|
@@ -11,6 +15,10 @@ export class AgentMessageFactory {
|
|
|
11
15
|
return new HumanMessage(prompt);
|
|
12
16
|
}
|
|
13
17
|
|
|
18
|
+
static createAssistantPrompt(prompt: string): AIMessage {
|
|
19
|
+
return new AIMessage(prompt);
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
static createToolMessage(toolCallId: string, content: string): ToolMessage {
|
|
15
23
|
return new ToolMessage({ tool_call_id: toolCallId, content });
|
|
16
24
|
}
|
|
@@ -48,4 +56,14 @@ export class AgentMessageFactory {
|
|
|
48
56
|
private static isRecord(value: unknown): value is Record<string, unknown> {
|
|
49
57
|
return typeof value === "object" && value !== null;
|
|
50
58
|
}
|
|
59
|
+
|
|
60
|
+
private static createPromptMessage(message: AgentMessageDto): BaseMessage {
|
|
61
|
+
if (message.role === "system") {
|
|
62
|
+
return this.createSystemPrompt(message.content);
|
|
63
|
+
}
|
|
64
|
+
if (message.role === "assistant") {
|
|
65
|
+
return this.createAssistantPrompt(message.content);
|
|
66
|
+
}
|
|
67
|
+
return this.createUserPrompt(message.content);
|
|
68
|
+
}
|
|
51
69
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MultiInputNode,
|
|
3
|
+
Node,
|
|
4
|
+
NodeExecutionContext,
|
|
5
|
+
NodeOutputs,
|
|
6
|
+
NodeResolver,
|
|
7
|
+
NodeBackedToolConfig,
|
|
8
|
+
ToolExecuteArgs,
|
|
9
|
+
ZodSchemaAny,
|
|
10
|
+
} from "@codemation/core";
|
|
11
|
+
import { CoreTokens, inject, injectable } from "@codemation/core";
|
|
12
|
+
|
|
13
|
+
@injectable()
|
|
14
|
+
export class NodeBackedToolRuntime {
|
|
15
|
+
constructor(
|
|
16
|
+
@inject(CoreTokens.NodeResolver)
|
|
17
|
+
private readonly nodeResolver: NodeResolver,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async execute(
|
|
21
|
+
config: NodeBackedToolConfig<any, ZodSchemaAny, ZodSchemaAny>,
|
|
22
|
+
args: ToolExecuteArgs,
|
|
23
|
+
): Promise<unknown> {
|
|
24
|
+
const nodeInput = config.toNodeItem({
|
|
25
|
+
input: args.input,
|
|
26
|
+
item: args.item,
|
|
27
|
+
itemIndex: args.itemIndex,
|
|
28
|
+
items: args.items,
|
|
29
|
+
ctx: args.ctx,
|
|
30
|
+
node: config.node,
|
|
31
|
+
});
|
|
32
|
+
const nodeCtx = {
|
|
33
|
+
...args.ctx,
|
|
34
|
+
config: config.node,
|
|
35
|
+
} as NodeExecutionContext<any>;
|
|
36
|
+
const resolvedNode = this.nodeResolver.resolve(config.node.type);
|
|
37
|
+
const outputs = await this.executeResolvedNode(resolvedNode, nodeInput, nodeCtx);
|
|
38
|
+
return config.toToolOutput({
|
|
39
|
+
input: args.input,
|
|
40
|
+
item: args.item,
|
|
41
|
+
itemIndex: args.itemIndex,
|
|
42
|
+
items: args.items,
|
|
43
|
+
ctx: args.ctx,
|
|
44
|
+
node: config.node,
|
|
45
|
+
outputs,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async executeResolvedNode(
|
|
50
|
+
resolvedNode: unknown,
|
|
51
|
+
nodeInput: ToolExecuteArgs["item"],
|
|
52
|
+
ctx: NodeExecutionContext<any>,
|
|
53
|
+
): Promise<NodeOutputs> {
|
|
54
|
+
if (this.isMultiInputNode(resolvedNode)) {
|
|
55
|
+
return await resolvedNode.executeMulti({ in: [nodeInput] }, ctx);
|
|
56
|
+
}
|
|
57
|
+
if (this.isNode(resolvedNode)) {
|
|
58
|
+
return await resolvedNode.execute([nodeInput], ctx);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Node-backed tool expected a runnable node instance for "${ctx.config.name ?? ctx.nodeId}".`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private isNode(value: unknown): value is Node<any> {
|
|
64
|
+
return typeof value === "object" && value !== null && "execute" in value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private isMultiInputNode(value: unknown): value is MultiInputNode<any> {
|
|
68
|
+
return typeof value === "object" && value !== null && "executeMulti" in value;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/nodes/aiAgent.ts
CHANGED
|
@@ -2,7 +2,8 @@ export { AgentMessageFactory } from "./AgentMessageFactory";
|
|
|
2
2
|
export { AgentOutputFactory } from "./AgentOutputFactory";
|
|
3
3
|
export { AgentToolCallPortMap } from "./AgentToolCallPortMapFactory";
|
|
4
4
|
export { AIAgent } from "./AIAgentConfig";
|
|
5
|
-
export {
|
|
5
|
+
export { AIAgentExecutionHelpersFactory } from "./AIAgentExecutionHelpersFactory";
|
|
6
|
+
export { AIAgentNode } from "./AIAgentNode";
|
|
6
7
|
export {
|
|
7
8
|
AgentItemPortMap,
|
|
8
9
|
type ExecutedToolCall,
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AgentToolCall,
|
|
3
|
+
Item,
|
|
4
|
+
NodeInputsByPort,
|
|
5
|
+
ToolConfig,
|
|
6
|
+
ToolExecuteArgs,
|
|
7
|
+
ZodSchemaAny,
|
|
8
|
+
} from "@codemation/core";
|
|
2
9
|
import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
3
10
|
|
|
4
11
|
export class AgentItemPortMap {
|
|
@@ -9,7 +16,11 @@ export class AgentItemPortMap {
|
|
|
9
16
|
|
|
10
17
|
export type ResolvedTool = Readonly<{
|
|
11
18
|
config: ToolConfig;
|
|
12
|
-
|
|
19
|
+
runtime: Readonly<{
|
|
20
|
+
defaultDescription: string;
|
|
21
|
+
inputSchema: ZodSchemaAny;
|
|
22
|
+
execute(args: ToolExecuteArgs<ToolConfig, unknown>): Promise<unknown>;
|
|
23
|
+
}>;
|
|
13
24
|
}>;
|
|
14
25
|
|
|
15
26
|
export type ItemScopedToolBinding = Readonly<{
|
package/src/register.types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Container } from "@codemation/core";
|
|
2
|
-
import { AIAgentNode } from "./nodes/aiAgent";
|
|
2
|
+
import { AIAgentExecutionHelpersFactory, AIAgentNode } from "./nodes/aiAgent";
|
|
3
3
|
import { CallbackNode } from "./nodes/CallbackNodeFactory";
|
|
4
4
|
import { HttpRequestNode } from "./nodes/httpRequest";
|
|
5
5
|
import { IfNode } from "./nodes/if";
|
|
@@ -31,6 +31,7 @@ export function registerCoreNodes(container: Container): void {
|
|
|
31
31
|
void SubWorkflowNode;
|
|
32
32
|
void ManualTriggerNode;
|
|
33
33
|
void AIAgentNode;
|
|
34
|
+
void AIAgentExecutionHelpersFactory;
|
|
34
35
|
void WaitNode;
|
|
35
36
|
void ConnectionCredentialNode;
|
|
36
37
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeDefinition, WorkflowDefinition, WorkflowNodeConnection } from "@codemation/core";
|
|
2
2
|
import { AgentConfigInspector, ConnectionNodeIdFactory } from "@codemation/core";
|
|
3
3
|
|
|
4
|
-
import { AIAgentNode } from "../nodes/
|
|
4
|
+
import { AIAgentNode } from "../nodes/AIAgentNode";
|
|
5
5
|
import { ConnectionCredentialNode } from "../nodes/ConnectionCredentialNode";
|
|
6
6
|
import { ConnectionCredentialNodeConfigFactory } from "../nodes/ConnectionCredentialNodeConfigFactory";
|
|
7
7
|
|