@mondaydotcomorg/atp-langchain 0.17.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/README.md +434 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/langgraph-client.d.ts +156 -0
- package/dist/langgraph-client.d.ts.map +1 -0
- package/dist/langgraph-client.js +231 -0
- package/dist/langgraph-client.js.map +1 -0
- package/dist/langgraph-tools.d.ts +79 -0
- package/dist/langgraph-tools.d.ts.map +1 -0
- package/dist/langgraph-tools.js +149 -0
- package/dist/langgraph-tools.js.map +1 -0
- package/dist/node.d.ts +29 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +49 -0
- package/dist/node.js.map +1 -0
- package/dist/tools.d.ts +39 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +52 -0
- package/dist/tools.js.map +1 -0
- package/package.json +45 -0
- package/src/index.ts +11 -0
- package/src/langgraph-client.ts +369 -0
- package/src/langgraph-tools.ts +219 -0
- package/src/node.ts +60 -0
- package/src/tools.ts +72 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACN,uBAAuB,EACvB,wBAAwB,GAGxB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,SAAiB,EACjB,OAAgC,EAChC,KAAmB;IAEnB,MAAM,MAAM,GAAG,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IAEvB,MAAM,QAAQ,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CACzB,CAAC,IAAU,EAAE,EAAE,CACd,IAAI,WAAW,CAAC;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;QACnC,IAAI,EAAE,IAAI,CAAC,IAAI;KACf,CAAC,CACH,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACpD,OAAO,KAAK,CAAC,GAAG,CACf,CAAC,IAAU,EAAE,EAAE,CACd,IAAI,WAAW,CAAC;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;QACnC,IAAI,EAAE,IAAI,CAAC,IAAI;KACf,CAAC,CACH,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mondaydotcomorg/atp-langchain",
|
|
3
|
+
"version": "0.17.14",
|
|
4
|
+
"description": "LangChain integration for Agent Tool Protocol",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
21
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"lint": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"agent",
|
|
27
|
+
"protocol",
|
|
28
|
+
"langchain",
|
|
29
|
+
"ai",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@langchain/core": "^0.3.78",
|
|
35
|
+
"@mondaydotcomorg/atp-client": "0.17.14",
|
|
36
|
+
"@mondaydotcomorg/atp-protocol": "0.17.14",
|
|
37
|
+
"json-schema-to-zod": "^2.6.1",
|
|
38
|
+
"langchain": "^0.3.35",
|
|
39
|
+
"zod": "^4.1.11"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.3.3",
|
|
43
|
+
"vitest": "^1.2.1"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createATPTools as createATPToolsBasic, convertToLangChainTools } from './tools.js';
|
|
2
|
+
|
|
3
|
+
export * from './langgraph-client.js';
|
|
4
|
+
export {
|
|
5
|
+
createATPTools,
|
|
6
|
+
createSimpleATPTool,
|
|
7
|
+
type CreateATPToolsOptions,
|
|
8
|
+
type ATPToolsResult,
|
|
9
|
+
} from './langgraph-tools.js';
|
|
10
|
+
|
|
11
|
+
export * from './node.js';
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangGraph-aware ATP Client
|
|
3
|
+
*
|
|
4
|
+
* This client integrates ATP execution with LangGraph's interrupt-based
|
|
5
|
+
* human-in-the-loop (HITL) system. When ATP code calls atp.approval.request(),
|
|
6
|
+
* it triggers a LangGraph interrupt for production-ready async approval flows.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - LangGraph interrupt integration for approvals
|
|
10
|
+
* - LLM sampling via LangChain models
|
|
11
|
+
* - Checkpoint-aware state management
|
|
12
|
+
* - Production-ready async approval workflows
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { AgentToolProtocolClient, ClientCallbackError } from '@mondaydotcomorg/atp-client';
|
|
16
|
+
import type { ClientHooks } from '@mondaydotcomorg/atp-client';
|
|
17
|
+
import type { ExecutionResult, ExecutionConfig, ClientTool } from '@mondaydotcomorg/atp-protocol';
|
|
18
|
+
import { ExecutionStatus, CallbackType } from '@mondaydotcomorg/atp-protocol';
|
|
19
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
20
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
21
|
+
import { HumanMessage, SystemMessage, AIMessage } from '@langchain/core/messages';
|
|
22
|
+
import type { Embeddings } from '@langchain/core/embeddings';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Approval request that needs human decision
|
|
26
|
+
*/
|
|
27
|
+
export interface ApprovalRequest {
|
|
28
|
+
message: string;
|
|
29
|
+
context?: Record<string, unknown>;
|
|
30
|
+
executionId: string;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Approval response from human
|
|
36
|
+
*/
|
|
37
|
+
export interface ApprovalResponse {
|
|
38
|
+
approved: boolean;
|
|
39
|
+
reason?: string;
|
|
40
|
+
timestamp: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options for creating the LangGraph ATP client
|
|
45
|
+
*/
|
|
46
|
+
export interface LangGraphATPClientOptions {
|
|
47
|
+
/** Base URL of ATP server */
|
|
48
|
+
serverUrl: string;
|
|
49
|
+
/** Custom headers for authentication (e.g., { Authorization: 'Bearer token' }) */
|
|
50
|
+
headers?: Record<string, string>;
|
|
51
|
+
/** LangChain LLM for atp.llm.call() sampling */
|
|
52
|
+
llm: BaseChatModel;
|
|
53
|
+
/**
|
|
54
|
+
* LangChain embeddings model for atp.embedding.embed() and atp.embedding.search()
|
|
55
|
+
* Optional - if not provided, embedding calls will fail
|
|
56
|
+
*/
|
|
57
|
+
embeddings?: Embeddings;
|
|
58
|
+
/**
|
|
59
|
+
* Client-provided tools that execute locally (e.g., file operations, browser automation)
|
|
60
|
+
* These tools are registered with the server and can be called from server code execution
|
|
61
|
+
*/
|
|
62
|
+
tools?: ClientTool[];
|
|
63
|
+
/**
|
|
64
|
+
* Whether to use LangGraph interrupts for approvals (production mode).
|
|
65
|
+
* If false, will use a direct callback handler.
|
|
66
|
+
* Default: true
|
|
67
|
+
*/
|
|
68
|
+
useLangGraphInterrupts?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Direct approval handler (only used if useLangGraphInterrupts = false)
|
|
71
|
+
*/
|
|
72
|
+
approvalHandler?: (message: string, context?: Record<string, unknown>) => Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Hooks for intercepting and modifying client behavior
|
|
75
|
+
*/
|
|
76
|
+
hooks?: ClientHooks;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Result of ATP execution that may need approval
|
|
81
|
+
*/
|
|
82
|
+
export interface ATPExecutionResult {
|
|
83
|
+
/** Standard execution result */
|
|
84
|
+
result: ExecutionResult;
|
|
85
|
+
/** If true, execution is waiting for approval via LangGraph interrupt */
|
|
86
|
+
needsApproval: boolean;
|
|
87
|
+
/** Approval request details (if needsApproval = true) */
|
|
88
|
+
approvalRequest?: ApprovalRequest;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Exception thrown when approval is needed - this triggers LangGraph interrupt
|
|
93
|
+
*/
|
|
94
|
+
export class ApprovalRequiredException extends ClientCallbackError {
|
|
95
|
+
constructor(public readonly approvalRequest: ApprovalRequest) {
|
|
96
|
+
super(`Approval required: ${approvalRequest.message}`);
|
|
97
|
+
this.name = 'ApprovalRequiredException';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* LangGraph-aware ATP Client
|
|
103
|
+
*
|
|
104
|
+
* Integrates ATP with LangGraph's production-ready interrupt system:
|
|
105
|
+
* - atp.llm.call() → Routes to LangChain LLM (no interrupt)
|
|
106
|
+
* - atp.approval.request() → Throws ApprovalRequiredException (triggers LangGraph interrupt)
|
|
107
|
+
* - Supports checkpoint-based state persistence
|
|
108
|
+
* - Enables async approval workflows
|
|
109
|
+
*/
|
|
110
|
+
export class LangGraphATPClient {
|
|
111
|
+
private client: AgentToolProtocolClient;
|
|
112
|
+
private llm: BaseChatModel;
|
|
113
|
+
private embeddings?: Embeddings;
|
|
114
|
+
private useLangGraphInterrupts: boolean;
|
|
115
|
+
private directApprovalHandler?: (
|
|
116
|
+
message: string,
|
|
117
|
+
context?: Record<string, unknown>
|
|
118
|
+
) => Promise<boolean>;
|
|
119
|
+
|
|
120
|
+
private pendingApprovals = new Map<string, ApprovalRequest>();
|
|
121
|
+
|
|
122
|
+
constructor(options: LangGraphATPClientOptions) {
|
|
123
|
+
const {
|
|
124
|
+
serverUrl,
|
|
125
|
+
headers,
|
|
126
|
+
llm,
|
|
127
|
+
embeddings,
|
|
128
|
+
tools,
|
|
129
|
+
useLangGraphInterrupts = true,
|
|
130
|
+
approvalHandler,
|
|
131
|
+
hooks,
|
|
132
|
+
} = options;
|
|
133
|
+
|
|
134
|
+
this.client = new AgentToolProtocolClient({
|
|
135
|
+
baseUrl: serverUrl,
|
|
136
|
+
headers,
|
|
137
|
+
hooks,
|
|
138
|
+
serviceProviders: tools ? { tools } : undefined,
|
|
139
|
+
});
|
|
140
|
+
this.llm = llm;
|
|
141
|
+
this.embeddings = embeddings;
|
|
142
|
+
this.useLangGraphInterrupts = useLangGraphInterrupts;
|
|
143
|
+
this.directApprovalHandler = approvalHandler;
|
|
144
|
+
|
|
145
|
+
this.client.provideLLM({
|
|
146
|
+
call: async (prompt: string, options?: any) => {
|
|
147
|
+
return await this.handleLLMCall(prompt, options);
|
|
148
|
+
},
|
|
149
|
+
extract: async (prompt: string, schema: any, options?: any) => {
|
|
150
|
+
return await this.handleLLMExtract(prompt, schema, options);
|
|
151
|
+
},
|
|
152
|
+
classify: async (text: string, categories: string[], options?: any) => {
|
|
153
|
+
return await this.handleLLMClassify(text, categories, options);
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (this.embeddings) {
|
|
158
|
+
this.client.provideEmbedding({
|
|
159
|
+
embed: async (text: string) => {
|
|
160
|
+
return await this.handleEmbedding(text);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.client.provideApproval({
|
|
166
|
+
request: async (message: string, context?: Record<string, unknown>) => {
|
|
167
|
+
return await this.handleApprovalRequest(message, context);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Initialize the client connection
|
|
174
|
+
*/
|
|
175
|
+
async connect(): Promise<void> {
|
|
176
|
+
await this.client.init({ name: 'langgraph-atp-client', version: '1.0.0' });
|
|
177
|
+
await this.client.connect();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get TypeScript API definitions
|
|
182
|
+
*/
|
|
183
|
+
getTypeDefinitions(): string {
|
|
184
|
+
return this.client.getTypeDefinitions();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Execute ATP code with LangGraph interrupt support
|
|
189
|
+
*
|
|
190
|
+
* When approval is needed:
|
|
191
|
+
* - If useLangGraphInterrupts=true: Throws ApprovalRequiredException
|
|
192
|
+
* - If useLangGraphInterrupts=false: Uses direct approval handler
|
|
193
|
+
*
|
|
194
|
+
* @throws ApprovalRequiredException when approval is needed (interrupt mode)
|
|
195
|
+
*/
|
|
196
|
+
async execute(code: string, config?: Partial<ExecutionConfig>): Promise<ATPExecutionResult> {
|
|
197
|
+
const result = await this.client.execute(code, config);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
result,
|
|
201
|
+
needsApproval: false,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Resume execution after approval decision
|
|
207
|
+
*
|
|
208
|
+
* Call this after LangGraph resumes from interrupt with approval decision.
|
|
209
|
+
*/
|
|
210
|
+
async resumeWithApproval(
|
|
211
|
+
executionId: string,
|
|
212
|
+
approved: boolean,
|
|
213
|
+
reason?: string
|
|
214
|
+
): Promise<ExecutionResult> {
|
|
215
|
+
const approvalResponse: ApprovalResponse = {
|
|
216
|
+
approved,
|
|
217
|
+
reason,
|
|
218
|
+
timestamp: Date.now(),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
this.pendingApprovals.delete(executionId);
|
|
222
|
+
|
|
223
|
+
return await this.client.resume(executionId, approvalResponse);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get pending approval request for an execution
|
|
228
|
+
*/
|
|
229
|
+
getPendingApproval(executionId: string): ApprovalRequest | undefined {
|
|
230
|
+
return this.pendingApprovals.get(executionId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Handle LLM call - route to LangChain LLM
|
|
235
|
+
*/
|
|
236
|
+
private async handleLLMCall(prompt: string, options?: any): Promise<string> {
|
|
237
|
+
const messages: BaseMessage[] = [];
|
|
238
|
+
|
|
239
|
+
if (options?.systemPrompt) {
|
|
240
|
+
messages.push(new SystemMessage(options.systemPrompt));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
messages.push(new HumanMessage(prompt));
|
|
244
|
+
|
|
245
|
+
const response = await this.llm.invoke(messages);
|
|
246
|
+
|
|
247
|
+
return typeof response.content === 'string'
|
|
248
|
+
? response.content
|
|
249
|
+
: JSON.stringify(response.content);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Handle LLM extract - route to LangChain LLM with structured output
|
|
254
|
+
*/
|
|
255
|
+
private async handleLLMExtract(prompt: string, schema: any, options?: any): Promise<any> {
|
|
256
|
+
const structuredLLM = this.llm.withStructuredOutput(schema);
|
|
257
|
+
|
|
258
|
+
const messages: BaseMessage[] = [];
|
|
259
|
+
if (options?.systemPrompt) {
|
|
260
|
+
messages.push(new SystemMessage(options.systemPrompt));
|
|
261
|
+
}
|
|
262
|
+
messages.push(new HumanMessage(prompt));
|
|
263
|
+
|
|
264
|
+
const result = await structuredLLM.invoke(messages);
|
|
265
|
+
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Handle LLM classify - route to LangChain LLM
|
|
271
|
+
*/
|
|
272
|
+
private async handleLLMClassify(
|
|
273
|
+
text: string,
|
|
274
|
+
categories: string[],
|
|
275
|
+
options?: any
|
|
276
|
+
): Promise<string> {
|
|
277
|
+
const prompt = `Classify the following text into one of these categories: ${categories.join(', ')}\n\nText: ${text}\n\nCategory:`;
|
|
278
|
+
|
|
279
|
+
const messages: BaseMessage[] = [];
|
|
280
|
+
if (options?.systemPrompt) {
|
|
281
|
+
messages.push(new SystemMessage(options.systemPrompt));
|
|
282
|
+
}
|
|
283
|
+
messages.push(new HumanMessage(prompt));
|
|
284
|
+
|
|
285
|
+
const response = await this.llm.invoke(messages);
|
|
286
|
+
|
|
287
|
+
const result =
|
|
288
|
+
typeof response.content === 'string'
|
|
289
|
+
? response.content.trim()
|
|
290
|
+
: JSON.stringify(response.content).trim();
|
|
291
|
+
|
|
292
|
+
if (!categories.includes(result)) {
|
|
293
|
+
for (const category of categories) {
|
|
294
|
+
if (result.toLowerCase().includes(category.toLowerCase())) {
|
|
295
|
+
return category;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const fallback = categories[0];
|
|
299
|
+
if (!fallback) {
|
|
300
|
+
throw new Error('No categories provided for classification');
|
|
301
|
+
}
|
|
302
|
+
return fallback;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Handle embedding - route to LangChain embeddings model
|
|
310
|
+
*/
|
|
311
|
+
private async handleEmbedding(text: string): Promise<number[]> {
|
|
312
|
+
if (!this.embeddings) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'Embeddings model not provided. Pass embeddings option when creating LangGraphATPClient.'
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return await this.embeddings.embedQuery(text);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get the underlying ATP client for advanced usage
|
|
323
|
+
*/
|
|
324
|
+
getUnderlyingClient(): AgentToolProtocolClient {
|
|
325
|
+
return this.client;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private async handleApprovalRequest(
|
|
329
|
+
message: string,
|
|
330
|
+
context?: Record<string, unknown>
|
|
331
|
+
): Promise<{ approved: boolean; reason?: string; timestamp: number }> {
|
|
332
|
+
const executionId = (context as any)?.executionId;
|
|
333
|
+
const cleanContext = context ? { ...context } : undefined;
|
|
334
|
+
if (cleanContext) {
|
|
335
|
+
delete (cleanContext as any).executionId;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (this.useLangGraphInterrupts) {
|
|
339
|
+
if (typeof executionId !== 'string' || !executionId) {
|
|
340
|
+
throw new Error('executionId is missing in approval request context');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const approvalRequest: ApprovalRequest = {
|
|
344
|
+
message,
|
|
345
|
+
context: cleanContext,
|
|
346
|
+
executionId,
|
|
347
|
+
timestamp: Date.now(),
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
this.pendingApprovals.set(executionId, approvalRequest);
|
|
351
|
+
|
|
352
|
+
throw new ApprovalRequiredException(approvalRequest);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (this.directApprovalHandler) {
|
|
356
|
+
const approved = await this.directApprovalHandler(message, cleanContext);
|
|
357
|
+
return {
|
|
358
|
+
approved,
|
|
359
|
+
timestamp: Date.now(),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.warn(`Approval request rejected (no handler): ${message}`);
|
|
364
|
+
return {
|
|
365
|
+
approved: false,
|
|
366
|
+
timestamp: Date.now(),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangGraph Tools Integration for ATP
|
|
3
|
+
*
|
|
4
|
+
* Creates LangChain-compatible tools from ATP with full support for:
|
|
5
|
+
* - LangGraph interrupts for human-in-the-loop approvals
|
|
6
|
+
* - LLM sampling via LangChain models
|
|
7
|
+
* - Checkpoint-based state persistence
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Tool, DynamicTool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
11
|
+
import { type JSONSchema } from '@langchain/core/utils/json_schema';
|
|
12
|
+
import {
|
|
13
|
+
LangGraphATPClient,
|
|
14
|
+
type LangGraphATPClientOptions,
|
|
15
|
+
ApprovalRequiredException,
|
|
16
|
+
} from './langgraph-client.js';
|
|
17
|
+
import {
|
|
18
|
+
createToolsFromATPClient,
|
|
19
|
+
ToolNames,
|
|
20
|
+
type Tool as ATPTool,
|
|
21
|
+
} from '@mondaydotcomorg/atp-client';
|
|
22
|
+
import { ExecutionStatus, type ExecutionConfig } from '@mondaydotcomorg/atp-protocol';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for creating ATP tools with LangGraph integration
|
|
26
|
+
*/
|
|
27
|
+
export interface CreateATPToolsOptions extends Omit<LangGraphATPClientOptions, 'serverUrl'> {
|
|
28
|
+
/** ATP server URL */
|
|
29
|
+
serverUrl: string;
|
|
30
|
+
/**
|
|
31
|
+
* Default execution config for all ATP code executions
|
|
32
|
+
*/
|
|
33
|
+
defaultExecutionConfig?: Partial<ExecutionConfig>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Result of creating ATP tools
|
|
38
|
+
*/
|
|
39
|
+
export interface ATPToolsResult {
|
|
40
|
+
/** The LangGraph-aware ATP client */
|
|
41
|
+
client: LangGraphATPClient;
|
|
42
|
+
/** LangChain tools for agent use */
|
|
43
|
+
tools: (Tool | DynamicStructuredTool)[];
|
|
44
|
+
/**
|
|
45
|
+
* Helper to check if an error is an approval request
|
|
46
|
+
*/
|
|
47
|
+
isApprovalRequired: (error: any) => error is ApprovalRequiredException;
|
|
48
|
+
/**
|
|
49
|
+
* Helper to resume after approval
|
|
50
|
+
*/
|
|
51
|
+
resumeWithApproval: (executionId: string, approved: boolean, reason?: string) => Promise<any>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates LangChain tools from ATP server with LangGraph interrupt support
|
|
56
|
+
*
|
|
57
|
+
* Example usage with LangGraph:
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { StateGraph } from "@langchain/langgraph";
|
|
60
|
+
* import { MemorySaver } from "@langchain/langgraph";
|
|
61
|
+
* import { ChatOpenAI } from "@langchain/openai";
|
|
62
|
+
*
|
|
63
|
+
* const llm = new ChatOpenAI({ modelName: "gpt-4" });
|
|
64
|
+
* const { client, tools, isApprovalRequired, resumeWithApproval } = await createATPTools({
|
|
65
|
+
* serverUrl: 'http://localhost:3333',
|
|
66
|
+
* headers: { Authorization: 'Bearer test-key' }, // Optional
|
|
67
|
+
* llm,
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* // Use tools in LangGraph agent
|
|
71
|
+
* const graph = new StateGraph({...})
|
|
72
|
+
* .addNode("agent", agentNode)
|
|
73
|
+
* .addNode("approval", async (state) => {
|
|
74
|
+
* // Human reviews state.approvalRequest
|
|
75
|
+
* return interrupt({ value: state.approvalRequest });
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* const checkpointer = new MemorySaver();
|
|
79
|
+
* const app = graph.compile({
|
|
80
|
+
* checkpointer,
|
|
81
|
+
* interruptBefore: ["approval"]
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export async function createATPTools(options: CreateATPToolsOptions): Promise<ATPToolsResult> {
|
|
86
|
+
const { serverUrl, defaultExecutionConfig, ...clientOptions } = options;
|
|
87
|
+
|
|
88
|
+
const client = new LangGraphATPClient({
|
|
89
|
+
serverUrl,
|
|
90
|
+
...clientOptions,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await client.connect();
|
|
94
|
+
|
|
95
|
+
const atpTools = createToolsFromATPClient(client.getUnderlyingClient());
|
|
96
|
+
|
|
97
|
+
const tools = atpTools.map((atpTool: ATPTool) => {
|
|
98
|
+
if (atpTool.name === ToolNames.EXECUTE_CODE) {
|
|
99
|
+
class ATPExecuteTool extends Tool {
|
|
100
|
+
name = `atp_${atpTool.name}`;
|
|
101
|
+
description = atpTool.description || 'Execute TypeScript code in ATP sandbox';
|
|
102
|
+
|
|
103
|
+
async _call(input: string): Promise<string> {
|
|
104
|
+
try {
|
|
105
|
+
let code: string;
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(input);
|
|
108
|
+
code = parsed.code || input;
|
|
109
|
+
} catch {
|
|
110
|
+
code = input;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = await client.execute(code, defaultExecutionConfig);
|
|
114
|
+
|
|
115
|
+
if (result.result.status === ExecutionStatus.COMPLETED) {
|
|
116
|
+
return JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
success: true,
|
|
119
|
+
result: result.result.result,
|
|
120
|
+
stats: result.result.stats,
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
);
|
|
125
|
+
} else if (result.result.status === ExecutionStatus.FAILED) {
|
|
126
|
+
return JSON.stringify(
|
|
127
|
+
{
|
|
128
|
+
success: false,
|
|
129
|
+
error: result.result.error,
|
|
130
|
+
stats: result.result.stats,
|
|
131
|
+
},
|
|
132
|
+
null,
|
|
133
|
+
2
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
return JSON.stringify(
|
|
137
|
+
{
|
|
138
|
+
success: false,
|
|
139
|
+
error: 'Execution in unexpected state: ' + result.result.status,
|
|
140
|
+
},
|
|
141
|
+
null,
|
|
142
|
+
2
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
if (error instanceof ApprovalRequiredException) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
return JSON.stringify(
|
|
150
|
+
{
|
|
151
|
+
success: false,
|
|
152
|
+
error: error.message || 'Unknown error',
|
|
153
|
+
},
|
|
154
|
+
null,
|
|
155
|
+
2
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return new ATPExecuteTool();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return new DynamicStructuredTool({
|
|
165
|
+
name: `atp_${atpTool.name}`,
|
|
166
|
+
description: atpTool.description || '',
|
|
167
|
+
schema: atpTool.zodSchema || (atpTool.inputSchema as JSONSchema),
|
|
168
|
+
func: async (input: any) => {
|
|
169
|
+
try {
|
|
170
|
+
const result = await atpTool.func(input);
|
|
171
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
172
|
+
} catch (error: any) {
|
|
173
|
+
return JSON.stringify(
|
|
174
|
+
{
|
|
175
|
+
success: false,
|
|
176
|
+
error: error.message,
|
|
177
|
+
},
|
|
178
|
+
null,
|
|
179
|
+
2
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
client,
|
|
188
|
+
tools,
|
|
189
|
+
isApprovalRequired: (error: any): error is ApprovalRequiredException => {
|
|
190
|
+
return error instanceof ApprovalRequiredException;
|
|
191
|
+
},
|
|
192
|
+
resumeWithApproval: async (executionId: string, approved: boolean, reason?: string) => {
|
|
193
|
+
return await client.resumeWithApproval(executionId, approved, reason);
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Helper to create a simple ATP tool for existing LangGraph agents
|
|
200
|
+
*
|
|
201
|
+
* This creates a single tool that can execute ATP code. For more control,
|
|
202
|
+
* use createATPTools() directly.
|
|
203
|
+
*/
|
|
204
|
+
export async function createSimpleATPTool(
|
|
205
|
+
serverUrl: string,
|
|
206
|
+
llm: any,
|
|
207
|
+
headers?: Record<string, string>
|
|
208
|
+
): Promise<Tool | DynamicStructuredTool> {
|
|
209
|
+
const result = await createATPTools({
|
|
210
|
+
serverUrl,
|
|
211
|
+
headers,
|
|
212
|
+
llm,
|
|
213
|
+
});
|
|
214
|
+
const tool = result.tools.find((t) => t.name === `atp_${ToolNames.EXECUTE_CODE}`);
|
|
215
|
+
if (!tool) {
|
|
216
|
+
throw new Error('Failed to create ATP execute_code tool');
|
|
217
|
+
}
|
|
218
|
+
return tool;
|
|
219
|
+
}
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AgentToolProtocolClient } from '@mondaydotcomorg/atp-client';
|
|
2
|
+
import { ExecutionStatus } from '@mondaydotcomorg/atp-protocol';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LangGraph-compatible node for ATP execution
|
|
6
|
+
*/
|
|
7
|
+
export class ATPExecutionNode {
|
|
8
|
+
private client: AgentToolProtocolClient;
|
|
9
|
+
|
|
10
|
+
constructor(client: AgentToolProtocolClient) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Execute code from the state and update the state with results
|
|
16
|
+
*/
|
|
17
|
+
async execute(state: { code?: string; messages?: any[]; [key: string]: any }): Promise<any> {
|
|
18
|
+
if (!state.code) {
|
|
19
|
+
throw new Error('No code provided in state');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await this.client.execute(state.code);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...state,
|
|
26
|
+
executionResult: result,
|
|
27
|
+
lastExecutionStatus: result.status,
|
|
28
|
+
lastExecutionOutput:
|
|
29
|
+
result.status === ExecutionStatus.COMPLETED ? result.result : result.error,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a function suitable for LangGraph node
|
|
35
|
+
*/
|
|
36
|
+
asFunction() {
|
|
37
|
+
return this.execute.bind(this);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Helper to create a simple ATP execution node function
|
|
43
|
+
*/
|
|
44
|
+
export function createATPNode(client: AgentToolProtocolClient) {
|
|
45
|
+
return async (state: any) => {
|
|
46
|
+
if (!state.code) {
|
|
47
|
+
throw new Error('No code provided in state');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await client.execute(state.code);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...state,
|
|
54
|
+
executionResult: result,
|
|
55
|
+
lastExecutionStatus: result.status,
|
|
56
|
+
lastExecutionOutput:
|
|
57
|
+
result.status === ExecutionStatus.COMPLETED ? result.result : result.error,
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|