@atom8n/n8n-nodes-langchain 2.5.6 → 2.5.7

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.
@@ -131,6 +131,14 @@
131
131
  "className": "LmChatCursorAgent",
132
132
  "sourcePath": "dist/nodes/llms/LmChatCursorAgent/LmChatCursorAgent.node.js"
133
133
  },
134
+ "lmChatCodexCli": {
135
+ "className": "LmChatCodexCli",
136
+ "sourcePath": "dist/nodes/llms/LmChatCodexCli/LmChatCodexCli.node.js"
137
+ },
138
+ "lmChatOpenCodeCli": {
139
+ "className": "LmChatOpenCodeCli",
140
+ "sourcePath": "dist/nodes/llms/LmChatOpenCodeCli/LmChatOpenCodeCli.node.js"
141
+ },
134
142
  "lmChatDeepSeek": {
135
143
  "className": "LmChatDeepSeek",
136
144
  "sourcePath": "dist/nodes/llms/LmChatDeepSeek/LmChatDeepSeek.node.js"
@@ -0,0 +1,456 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var LmChatCodexCli_node_exports = {};
20
+ __export(LmChatCodexCli_node_exports, {
21
+ LmChatCodexCli: () => LmChatCodexCli
22
+ });
23
+ module.exports = __toCommonJS(LmChatCodexCli_node_exports);
24
+ var import_chat_models = require("@langchain/core/language_models/chat_models");
25
+ var import_messages = require("@langchain/core/messages");
26
+ var import_n8n_workflow = require("n8n-workflow");
27
+ var import_sharedFields = require("../../../utils/sharedFields");
28
+ var import_N8nLlmTracing = require("../N8nLlmTracing");
29
+ var import_child_process = require("child_process");
30
+ var import_fs = require("fs");
31
+ const TOOL_CALL_SYSTEM_PROMPT = `You have access to the following tools. When you need to call a tool, respond ONLY with a JSON block in this exact format (no other text before or after):
32
+
33
+ \`\`\`tool_calls
34
+ [{"id": "call_1", "name": "tool_name", "args": {"param": "value"}}]
35
+ \`\`\`
36
+
37
+ When you do NOT need to call a tool, respond normally with text. Never mix tool calls and text in the same response.
38
+
39
+ Available tools:
40
+ `;
41
+ class ChatCodexCLI extends import_chat_models.BaseChatModel {
42
+ constructor(fields) {
43
+ super({});
44
+ this.boundTools = [];
45
+ this.model = fields.model;
46
+ this.binaryPath = fields.binaryPath;
47
+ this.workingDirectory = fields.workingDirectory;
48
+ this.sandboxMode = fields.sandboxMode;
49
+ }
50
+ _llmType() {
51
+ return "codex-cli";
52
+ }
53
+ bindTools(tools, kwargs) {
54
+ const clone = new ChatCodexCLI({
55
+ model: this.model,
56
+ binaryPath: this.binaryPath,
57
+ workingDirectory: this.workingDirectory,
58
+ sandboxMode: this.sandboxMode
59
+ });
60
+ clone.boundTools = tools;
61
+ clone.callbacks = this.callbacks;
62
+ if (kwargs) {
63
+ return clone.bind(kwargs);
64
+ }
65
+ return clone;
66
+ }
67
+ async _generate(messages, _options, _runManager) {
68
+ console.log("[LmChatCodexCli] _generate called", {
69
+ messageCount: messages.length,
70
+ boundToolCount: this.boundTools.length,
71
+ model: this.model
72
+ });
73
+ const processedMessages = [...messages];
74
+ if (this.boundTools.length > 0) {
75
+ const toolDescriptions = this.boundTools.map((tool) => {
76
+ const t = tool;
77
+ const name = t.name ?? "";
78
+ const description = t.description ?? "";
79
+ const schema = t.parameters ?? t.schema ?? {};
80
+ return `- ${name}: ${description}
81
+ Parameters: ${JSON.stringify(schema)}`;
82
+ }).join("\n\n");
83
+ const systemPrompt = TOOL_CALL_SYSTEM_PROMPT + toolDescriptions;
84
+ processedMessages.unshift(new import_messages.SystemMessage(systemPrompt));
85
+ }
86
+ const prompt = processedMessages.map((m) => {
87
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
88
+ if (m instanceof import_messages.SystemMessage) return `[system]: ${content}`;
89
+ if (m instanceof import_messages.HumanMessage) return `[user]: ${content}`;
90
+ if (m instanceof import_messages.AIMessage) return `[assistant]: ${content}`;
91
+ return `[${m._getType()}]: ${content}`;
92
+ }).join("\n\n");
93
+ console.log("[LmChatCodexCli] prompt built, length:", prompt.length);
94
+ const rawResponse = await this.executeCodexCli(prompt);
95
+ console.log("[LmChatCodexCli] raw response received, length:", rawResponse.length);
96
+ if (this.boundTools.length > 0) {
97
+ const toolCalls = this.extractToolCalls(rawResponse);
98
+ if (toolCalls.length > 0) {
99
+ console.log("[LmChatCodexCli] extracted tool calls:", toolCalls.length);
100
+ const aiMessage2 = new import_messages.AIMessage({
101
+ content: "",
102
+ tool_calls: toolCalls.map((tc) => ({
103
+ id: tc.id,
104
+ name: tc.name,
105
+ args: tc.args,
106
+ type: "tool_call"
107
+ }))
108
+ });
109
+ return {
110
+ generations: [{ message: aiMessage2, text: "" }]
111
+ };
112
+ }
113
+ }
114
+ console.log("[LmChatCodexCli] returning text response");
115
+ const aiMessage = new import_messages.AIMessage({ content: rawResponse });
116
+ return {
117
+ generations: [{ message: aiMessage, text: rawResponse }]
118
+ };
119
+ }
120
+ extractToolCalls(text) {
121
+ const toolCallRegex = /```tool_calls\s*\n([\s\S]*?)\n```/;
122
+ const match = toolCallRegex.exec(text);
123
+ if (!match) return [];
124
+ try {
125
+ const parsed = JSON.parse(match[1]);
126
+ if (!Array.isArray(parsed)) return [];
127
+ return parsed.map((tc, i) => ({
128
+ id: tc.id ?? `call_${i}`,
129
+ name: tc.name,
130
+ args: tc.args ?? {}
131
+ }));
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+ async executeCodexCli(prompt) {
137
+ const args = ["exec", "--json", "--skip-git-repo-check", "--full-auto"];
138
+ if (this.sandboxMode) {
139
+ args.push("--sandbox", this.sandboxMode);
140
+ }
141
+ if (this.model && this.model !== "auto") {
142
+ args.push("--model", this.model);
143
+ }
144
+ const cwd = this.workingDirectory?.trim() || void 0;
145
+ if (cwd) {
146
+ args.push("--cd", cwd);
147
+ }
148
+ args.push("-");
149
+ console.log("[LmChatCodexCli] spawning codex exec", {
150
+ binaryPath: this.binaryPath,
151
+ args,
152
+ model: this.model,
153
+ cwd,
154
+ sandboxMode: this.sandboxMode
155
+ });
156
+ return await new Promise((resolve, reject) => {
157
+ const child = (0, import_child_process.spawn)(this.binaryPath, args, {
158
+ // Codex uses --cd for working directory, so we don't set cwd on spawn
159
+ stdio: ["pipe", "pipe", "pipe"],
160
+ env: { ...process.env }
161
+ });
162
+ let stdout = "";
163
+ let stderr = "";
164
+ child.stdout.on("data", (data) => {
165
+ stdout += data.toString();
166
+ });
167
+ child.stderr.on("data", (data) => {
168
+ stderr += data.toString();
169
+ });
170
+ child.on("error", (err) => {
171
+ console.error("[LmChatCodexCli] spawn error:", err.message);
172
+ reject(
173
+ new Error(
174
+ `Failed to spawn codex: ${err.message}. Make sure the Codex CLI is installed (npm install -g @openai/codex) and accessible.`
175
+ )
176
+ );
177
+ });
178
+ child.on("close", (code) => {
179
+ console.log("[LmChatCodexCli] codex exec exited", {
180
+ code,
181
+ stdoutLength: stdout.length,
182
+ stderrLength: stderr.length
183
+ });
184
+ const parseResult = this.parseJsonlOutput(stdout);
185
+ if (parseResult.assistantText) {
186
+ console.log(
187
+ "[LmChatCodexCli] parsed assistant content, length:",
188
+ parseResult.assistantText.length
189
+ );
190
+ resolve(parseResult.assistantText);
191
+ return;
192
+ }
193
+ if (parseResult.errorMessage) {
194
+ console.error("[LmChatCodexCli] codex returned error:", parseResult.errorMessage);
195
+ reject(new Error(`Codex CLI error: ${parseResult.errorMessage}`));
196
+ return;
197
+ }
198
+ if (code !== 0) {
199
+ const stderrMsg = stderr.trim();
200
+ const errorMsg = stderrMsg || `codex exec exited with code ${code}`;
201
+ console.error("[LmChatCodexCli] codex exec failed with code", code, ":", errorMsg);
202
+ reject(new Error(errorMsg));
203
+ return;
204
+ }
205
+ console.error(
206
+ "[LmChatCodexCli] no assistant response parsed from output, stdout preview:",
207
+ stdout.substring(0, 500)
208
+ );
209
+ reject(new Error("No assistant response received from codex exec"));
210
+ });
211
+ if (child.stdin) {
212
+ child.stdin.write(prompt);
213
+ child.stdin.end();
214
+ }
215
+ });
216
+ }
217
+ /**
218
+ * Parse JSONL output from `codex exec --json`.
219
+ *
220
+ * Actual event types from codex exec --json (verified empirically):
221
+ * - {"type":"thread.started","thread_id":"..."}
222
+ * - {"type":"turn.started"}
223
+ * - {"type":"item.started","item":{"type":"agent_message",...}}
224
+ * - {"type":"item.completed","item":{"type":"agent_message","text":"..."}}
225
+ * - {"type":"turn.completed"}
226
+ * - {"type":"error","message":"..."}
227
+ * - {"type":"turn.failed","error":{"message":"..."}}
228
+ *
229
+ * Returns both assistant text and any error messages found.
230
+ */
231
+ parseJsonlOutput(output) {
232
+ const lines = output.split("\n").filter((line) => line.trim());
233
+ const assistantParts = [];
234
+ const errorParts = [];
235
+ console.log("[LmChatCodexCli] parsing JSONL output, line count:", lines.length);
236
+ for (const line of lines) {
237
+ try {
238
+ const parsed = JSON.parse(line);
239
+ const eventType = parsed.type;
240
+ console.log(
241
+ "[LmChatCodexCli] JSONL event:",
242
+ eventType,
243
+ "| keys:",
244
+ Object.keys(parsed).join(",")
245
+ );
246
+ if (eventType === "item.completed") {
247
+ const item = parsed.item;
248
+ if (item?.type === "agent_message" && typeof item.text === "string") {
249
+ console.log(
250
+ "[LmChatCodexCli] found agent_message text, length:",
251
+ item.text.length
252
+ );
253
+ assistantParts.push(item.text);
254
+ }
255
+ }
256
+ if (eventType === "message" && parsed.role === "assistant") {
257
+ const content = parsed.content;
258
+ if (Array.isArray(content)) {
259
+ for (const c of content) {
260
+ if (c.type === "text" && typeof c.text === "string") {
261
+ assistantParts.push(c.text);
262
+ }
263
+ }
264
+ } else if (typeof content === "string") {
265
+ assistantParts.push(content);
266
+ }
267
+ }
268
+ if (eventType === "assistant") {
269
+ const message = parsed.message;
270
+ if (message?.content) {
271
+ if (Array.isArray(message.content)) {
272
+ for (const c of message.content) {
273
+ if (c.type === "text" && typeof c.text === "string") {
274
+ assistantParts.push(c.text);
275
+ }
276
+ }
277
+ } else if (typeof message.content === "string") {
278
+ assistantParts.push(message.content);
279
+ }
280
+ }
281
+ }
282
+ if (eventType === "error" && typeof parsed.message === "string") {
283
+ errorParts.push(parsed.message);
284
+ }
285
+ if (eventType === "turn.failed") {
286
+ const error = parsed.error;
287
+ if (error && typeof error.message === "string") {
288
+ errorParts.push(error.message);
289
+ }
290
+ }
291
+ } catch {
292
+ }
293
+ }
294
+ return {
295
+ assistantText: assistantParts.join(""),
296
+ errorMessage: errorParts.join("; ")
297
+ };
298
+ }
299
+ }
300
+ class LmChatCodexCli {
301
+ constructor() {
302
+ this.description = {
303
+ displayName: "Codex CLI Chat Model",
304
+ name: "lmChatCodexCli",
305
+ icon: "file:codexCli.svg",
306
+ group: ["transform"],
307
+ version: [1],
308
+ description: "Chat model powered by the OpenAI Codex CLI. Requires codex to be installed locally (npm install -g @openai/codex).",
309
+ defaults: {
310
+ name: "Codex CLI Chat Model"
311
+ },
312
+ codex: {
313
+ categories: ["AI"],
314
+ subcategories: {
315
+ AI: ["Language Models", "Root Nodes"],
316
+ "Language Models": ["Chat Models (Recommended)"]
317
+ },
318
+ resources: {}
319
+ },
320
+ inputs: [],
321
+ outputs: [import_n8n_workflow.NodeConnectionTypes.AiLanguageModel],
322
+ outputNames: ["Model"],
323
+ properties: [
324
+ (0, import_sharedFields.getConnectionHintNoticeField)([import_n8n_workflow.NodeConnectionTypes.AiChain, import_n8n_workflow.NodeConnectionTypes.AiAgent]),
325
+ {
326
+ displayName: "Model",
327
+ name: "model",
328
+ type: "options",
329
+ description: "The model to use via codex CLI",
330
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
331
+ options: [
332
+ { name: "Auto (Default)", value: "auto" },
333
+ // GPT-5 series (supported by Codex CLI)
334
+ { name: "GPT-5.5", value: "gpt-5.5" },
335
+ { name: "GPT-5.5 Fast", value: "gpt-5.5-fast" },
336
+ { name: "GPT-5.4", value: "gpt-5.4" },
337
+ { name: "GPT-5.4 Fast", value: "gpt-5.4-fast" },
338
+ { name: "GPT-5.4 Mini", value: "gpt-5.4-mini" },
339
+ { name: "GPT-5.3 Codex", value: "gpt-5.3-codex" },
340
+ { name: "GPT-5.3 Codex Spark", value: "gpt-5.3-codex-spark" },
341
+ { name: "GPT-5.2", value: "gpt-5.2" }
342
+ ],
343
+ default: "auto"
344
+ },
345
+ {
346
+ displayName: "Options",
347
+ name: "options",
348
+ placeholder: "Add Option",
349
+ description: "Additional options to configure",
350
+ type: "collection",
351
+ default: {},
352
+ options: [
353
+ {
354
+ displayName: "Binary Path",
355
+ name: "binaryPath",
356
+ default: "codex",
357
+ description: 'Path to the codex binary. Defaults to "codex" (must be in PATH).',
358
+ type: "string"
359
+ },
360
+ {
361
+ displayName: "Working Directory",
362
+ name: "workingDirectory",
363
+ default: "",
364
+ description: "Working directory for the codex process. Leave empty to use the default.",
365
+ type: "string"
366
+ },
367
+ {
368
+ displayName: "Sandbox Mode",
369
+ name: "sandboxMode",
370
+ type: "options",
371
+ default: "read-only",
372
+ description: "Sandbox policy for executing model-generated shell commands",
373
+ options: [
374
+ {
375
+ name: "Read Only",
376
+ value: "read-only",
377
+ description: "Only allow read operations (safest)"
378
+ },
379
+ {
380
+ name: "Workspace Write",
381
+ value: "workspace-write",
382
+ description: "Allow writes within the workspace directory"
383
+ },
384
+ {
385
+ name: "Full Access (Dangerous)",
386
+ value: "danger-full-access",
387
+ description: "Full filesystem access \u2014 use with extreme caution"
388
+ }
389
+ ]
390
+ }
391
+ ]
392
+ }
393
+ ]
394
+ };
395
+ }
396
+ async supplyData(itemIndex) {
397
+ const modelName = this.getNodeParameter("model", itemIndex);
398
+ const binaryPath = this.getNodeParameter("options.binaryPath", itemIndex, "codex");
399
+ const rawWorkingDirectory = this.getNodeParameter("options.workingDirectory", itemIndex, "", {
400
+ rawExpressions: true
401
+ });
402
+ const workingDirectory = this.getNodeParameter("options.workingDirectory", itemIndex, "");
403
+ const normalizedWorkingDirectory = (workingDirectory ?? "").trim();
404
+ const rawWorkingDirectoryValue = rawWorkingDirectory ?? "";
405
+ const isWorkingDirectoryExpression = rawWorkingDirectoryValue.startsWith("=") || rawWorkingDirectoryValue.includes("{{") || rawWorkingDirectoryValue.includes("$workspace");
406
+ const sandboxMode = this.getNodeParameter(
407
+ "options.sandboxMode",
408
+ itemIndex,
409
+ "read-only"
410
+ );
411
+ console.log("[LmChatCodexCli] resolved Codex CLI options", {
412
+ itemIndex,
413
+ modelName,
414
+ binaryPath,
415
+ rawWorkingDirectory,
416
+ workingDirectory: normalizedWorkingDirectory,
417
+ sandboxMode
418
+ });
419
+ if (isWorkingDirectoryExpression && !normalizedWorkingDirectory) {
420
+ throw new import_n8n_workflow.ApplicationError(
421
+ `Codex CLI working directory expression resolved to an empty value: ${rawWorkingDirectoryValue}`
422
+ );
423
+ }
424
+ if (normalizedWorkingDirectory.includes("{{") || normalizedWorkingDirectory.includes("$workspace")) {
425
+ throw new import_n8n_workflow.ApplicationError(
426
+ `Codex CLI working directory was not resolved before execution: ${normalizedWorkingDirectory}`
427
+ );
428
+ }
429
+ if (normalizedWorkingDirectory && (!(0, import_fs.existsSync)(normalizedWorkingDirectory) || !(0, import_fs.statSync)(normalizedWorkingDirectory).isDirectory())) {
430
+ throw new import_n8n_workflow.ApplicationError(
431
+ `Codex CLI working directory does not exist or is not a directory: ${normalizedWorkingDirectory}`
432
+ );
433
+ }
434
+ console.log("[LmChatCodexCli] creating ChatCodexCLI instance", {
435
+ model: modelName,
436
+ binaryPath,
437
+ workingDirectory: normalizedWorkingDirectory,
438
+ sandboxMode
439
+ });
440
+ const model = new ChatCodexCLI({
441
+ model: modelName,
442
+ binaryPath,
443
+ workingDirectory: normalizedWorkingDirectory,
444
+ sandboxMode
445
+ });
446
+ model.callbacks = [new import_N8nLlmTracing.N8nLlmTracing(this)];
447
+ return {
448
+ response: model
449
+ };
450
+ }
451
+ }
452
+ // Annotate the CommonJS export names for ESM import in node:
453
+ 0 && (module.exports = {
454
+ LmChatCodexCli
455
+ });
456
+ //# sourceMappingURL=LmChatCodexCli.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../nodes/llms/LmChatCodexCli/LmChatCodexCli.node.ts"],"sourcesContent":["import { BaseChatModel } from '@langchain/core/language_models/chat_models';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { AIMessage, HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport type { ChatResult } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BindToolsInput } from '@langchain/core/language_models/chat_models';\nimport {\n\tApplicationError,\n\tNodeConnectionTypes,\n\ttype INodeType,\n\ttype INodeTypeDescription,\n\ttype ISupplyDataFunctions,\n\ttype SupplyData,\n} from 'n8n-workflow';\n\nimport { getConnectionHintNoticeField } from '@utils/sharedFields';\n\nimport { N8nLlmTracing } from '../N8nLlmTracing';\nimport { spawn } from 'child_process';\nimport { existsSync, statSync } from 'fs';\n\ninterface CodexCliFields {\n\tmodel: string;\n\tbinaryPath: string;\n\tworkingDirectory: string;\n\tsandboxMode: string;\n}\n\ninterface ParsedToolCall {\n\tid: string;\n\tname: string;\n\targs: Record<string, unknown>;\n}\n\ninterface ParsedJsonlResult {\n\tassistantText: string;\n\terrorMessage: string;\n}\n\nconst TOOL_CALL_SYSTEM_PROMPT = `You have access to the following tools. When you need to call a tool, respond ONLY with a JSON block in this exact format (no other text before or after):\n\n\\`\\`\\`tool_calls\n[{\"id\": \"call_1\", \"name\": \"tool_name\", \"args\": {\"param\": \"value\"}}]\n\\`\\`\\`\n\nWhen you do NOT need to call a tool, respond normally with text. Never mix tool calls and text in the same response.\n\nAvailable tools:\n`;\n\n/**\n * Custom LangChain chat model that wraps the OpenAI Codex CLI binary.\n * Uses `codex exec --json` for non-interactive execution.\n * Supports tool calling by injecting tool schemas into the prompt\n * and parsing structured JSON responses for tool calls.\n */\nclass ChatCodexCLI extends BaseChatModel {\n\tmodel: string;\n\n\tbinaryPath: string;\n\n\tworkingDirectory: string;\n\n\tsandboxMode: string;\n\n\tboundTools: BindToolsInput[] = [];\n\n\tconstructor(fields: CodexCliFields) {\n\t\tsuper({});\n\t\tthis.model = fields.model;\n\t\tthis.binaryPath = fields.binaryPath;\n\t\tthis.workingDirectory = fields.workingDirectory;\n\t\tthis.sandboxMode = fields.sandboxMode;\n\t}\n\n\t_llmType(): string {\n\t\treturn 'codex-cli';\n\t}\n\n\toverride bindTools(tools: BindToolsInput[], kwargs?: Partial<this['ParsedCallOptions']>) {\n\t\tconst clone = new ChatCodexCLI({\n\t\t\tmodel: this.model,\n\t\t\tbinaryPath: this.binaryPath,\n\t\t\tworkingDirectory: this.workingDirectory,\n\t\t\tsandboxMode: this.sandboxMode,\n\t\t});\n\t\tclone.boundTools = tools;\n\t\tclone.callbacks = this.callbacks;\n\t\tif (kwargs) {\n\t\t\treturn (\n\t\t\t\tclone as unknown as {\n\t\t\t\t\tbind: (kwargs: Record<string, unknown>) => ChatCodexCLI;\n\t\t\t\t}\n\t\t\t).bind(kwargs as Record<string, unknown>);\n\t\t}\n\t\treturn clone;\n\t}\n\n\tasync _generate(\n\t\tmessages: BaseMessage[],\n\t\t_options: this['ParsedCallOptions'],\n\t\t_runManager?: CallbackManagerForLLMRun,\n\t): Promise<ChatResult> {\n\t\tconsole.log('[LmChatCodexCli] _generate called', {\n\t\t\tmessageCount: messages.length,\n\t\t\tboundToolCount: this.boundTools.length,\n\t\t\tmodel: this.model,\n\t\t});\n\n\t\t// If tools are bound, inject tool schemas into a system message\n\t\tconst processedMessages = [...messages];\n\t\tif (this.boundTools.length > 0) {\n\t\t\tconst toolDescriptions = this.boundTools\n\t\t\t\t.map((tool) => {\n\t\t\t\t\tconst t = tool as Record<string, unknown>;\n\t\t\t\t\tconst name = (t.name as string) ?? '';\n\t\t\t\t\tconst description = (t.description as string) ?? '';\n\t\t\t\t\tconst schema = t.parameters ?? t.schema ?? {};\n\t\t\t\t\treturn `- ${name}: ${description}\\n Parameters: ${JSON.stringify(schema)}`;\n\t\t\t\t})\n\t\t\t\t.join('\\n\\n');\n\n\t\t\tconst systemPrompt = TOOL_CALL_SYSTEM_PROMPT + toolDescriptions;\n\t\t\tprocessedMessages.unshift(new SystemMessage(systemPrompt));\n\t\t}\n\n\t\t// Build prompt from messages\n\t\tconst prompt = processedMessages\n\t\t\t.map((m) => {\n\t\t\t\tconst content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);\n\t\t\t\tif (m instanceof SystemMessage) return `[system]: ${content}`;\n\t\t\t\tif (m instanceof HumanMessage) return `[user]: ${content}`;\n\t\t\t\tif (m instanceof AIMessage) return `[assistant]: ${content}`;\n\t\t\t\treturn `[${m._getType()}]: ${content}`;\n\t\t\t})\n\t\t\t.join('\\n\\n');\n\n\t\tconsole.log('[LmChatCodexCli] prompt built, length:', prompt.length);\n\n\t\t// Execute codex CLI\n\t\tconst rawResponse = await this.executeCodexCli(prompt);\n\n\t\tconsole.log('[LmChatCodexCli] raw response received, length:', rawResponse.length);\n\n\t\t// Check for tool calls in response\n\t\tif (this.boundTools.length > 0) {\n\t\t\tconst toolCalls = this.extractToolCalls(rawResponse);\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tconsole.log('[LmChatCodexCli] extracted tool calls:', toolCalls.length);\n\t\t\t\tconst aiMessage = new AIMessage({\n\t\t\t\t\tcontent: '',\n\t\t\t\t\ttool_calls: toolCalls.map((tc) => ({\n\t\t\t\t\t\tid: tc.id,\n\t\t\t\t\t\tname: tc.name,\n\t\t\t\t\t\targs: tc.args,\n\t\t\t\t\t\ttype: 'tool_call' as const,\n\t\t\t\t\t})),\n\t\t\t\t});\n\n\t\t\t\treturn {\n\t\t\t\t\tgenerations: [{ message: aiMessage, text: '' }],\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Normal text response\n\t\tconsole.log('[LmChatCodexCli] returning text response');\n\t\tconst aiMessage = new AIMessage({ content: rawResponse });\n\t\treturn {\n\t\t\tgenerations: [{ message: aiMessage, text: rawResponse }],\n\t\t};\n\t}\n\n\tprivate extractToolCalls(text: string): ParsedToolCall[] {\n\t\t// Look for tool_calls JSON block\n\t\tconst toolCallRegex = /```tool_calls\\s*\\n([\\s\\S]*?)\\n```/;\n\t\tconst match = toolCallRegex.exec(text);\n\t\tif (!match) return [];\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(match[1]) as Array<{\n\t\t\t\tid?: string;\n\t\t\t\tname: string;\n\t\t\t\targs: Record<string, unknown>;\n\t\t\t}>;\n\t\t\tif (!Array.isArray(parsed)) return [];\n\n\t\t\treturn parsed.map((tc, i) => ({\n\t\t\t\tid: tc.id ?? `call_${i}`,\n\t\t\t\tname: tc.name,\n\t\t\t\targs: tc.args ?? {},\n\t\t\t}));\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate async executeCodexCli(prompt: string): Promise<string> {\n\t\t// Build args: codex exec --json --skip-git-repo-check --full-auto [--sandbox <mode>] [--model <model>] [--cd <dir>] -\n\t\tconst args = ['exec', '--json', '--skip-git-repo-check', '--full-auto'];\n\n\t\tif (this.sandboxMode) {\n\t\t\targs.push('--sandbox', this.sandboxMode);\n\t\t}\n\n\t\tif (this.model && this.model !== 'auto') {\n\t\t\targs.push('--model', this.model);\n\t\t}\n\n\t\tconst cwd = this.workingDirectory?.trim() || undefined;\n\t\tif (cwd) {\n\t\t\targs.push('--cd', cwd);\n\t\t}\n\n\t\t// Use `-` to read prompt from stdin\n\t\targs.push('-');\n\n\t\tconsole.log('[LmChatCodexCli] spawning codex exec', {\n\t\t\tbinaryPath: this.binaryPath,\n\t\t\targs,\n\t\t\tmodel: this.model,\n\t\t\tcwd,\n\t\t\tsandboxMode: this.sandboxMode,\n\t\t});\n\n\t\treturn await new Promise<string>((resolve, reject) => {\n\t\t\tconst child = spawn(this.binaryPath, args, {\n\t\t\t\t// Codex uses --cd for working directory, so we don't set cwd on spawn\n\t\t\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t\t\tenv: { ...process.env },\n\t\t\t});\n\n\t\t\tlet stdout = '';\n\t\t\tlet stderr = '';\n\n\t\t\tchild.stdout.on('data', (data: Buffer) => {\n\t\t\t\tstdout += data.toString();\n\t\t\t});\n\n\t\t\tchild.stderr.on('data', (data: Buffer) => {\n\t\t\t\tstderr += data.toString();\n\t\t\t});\n\n\t\t\tchild.on('error', (err: Error) => {\n\t\t\t\tconsole.error('[LmChatCodexCli] spawn error:', err.message);\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`Failed to spawn codex: ${err.message}. Make sure the Codex CLI is installed (npm install -g @openai/codex) and accessible.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t});\n\n\t\t\tchild.on('close', (code: number | null) => {\n\t\t\t\tconsole.log('[LmChatCodexCli] codex exec exited', {\n\t\t\t\t\tcode,\n\t\t\t\t\tstdoutLength: stdout.length,\n\t\t\t\t\tstderrLength: stderr.length,\n\t\t\t\t});\n\n\t\t\t\t// Parse the JSONL output — even on non-zero exit code, stdout may contain\n\t\t\t\t// useful JSONL events (e.g. error messages from the Codex API)\n\t\t\t\tconst parseResult = this.parseJsonlOutput(stdout);\n\n\t\t\t\tif (parseResult.assistantText) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t'[LmChatCodexCli] parsed assistant content, length:',\n\t\t\t\t\t\tparseResult.assistantText.length,\n\t\t\t\t\t);\n\t\t\t\t\tresolve(parseResult.assistantText);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// No assistant response — build a meaningful error from available info\n\t\t\t\tif (parseResult.errorMessage) {\n\t\t\t\t\tconsole.error('[LmChatCodexCli] codex returned error:', parseResult.errorMessage);\n\t\t\t\t\treject(new Error(`Codex CLI error: ${parseResult.errorMessage}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0) {\n\t\t\t\t\tconst stderrMsg = stderr.trim();\n\t\t\t\t\tconst errorMsg = stderrMsg || `codex exec exited with code ${code}`;\n\t\t\t\t\tconsole.error('[LmChatCodexCli] codex exec failed with code', code, ':', errorMsg);\n\t\t\t\t\treject(new Error(errorMsg));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconsole.error(\n\t\t\t\t\t'[LmChatCodexCli] no assistant response parsed from output, stdout preview:',\n\t\t\t\t\tstdout.substring(0, 500),\n\t\t\t\t);\n\t\t\t\treject(new Error('No assistant response received from codex exec'));\n\t\t\t});\n\n\t\t\tif (child.stdin) {\n\t\t\t\tchild.stdin.write(prompt);\n\t\t\t\tchild.stdin.end();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Parse JSONL output from `codex exec --json`.\n\t *\n\t * Actual event types from codex exec --json (verified empirically):\n\t * - {\"type\":\"thread.started\",\"thread_id\":\"...\"}\n\t * - {\"type\":\"turn.started\"}\n\t * - {\"type\":\"item.started\",\"item\":{\"type\":\"agent_message\",...}}\n\t * - {\"type\":\"item.completed\",\"item\":{\"type\":\"agent_message\",\"text\":\"...\"}}\n\t * - {\"type\":\"turn.completed\"}\n\t * - {\"type\":\"error\",\"message\":\"...\"}\n\t * - {\"type\":\"turn.failed\",\"error\":{\"message\":\"...\"}}\n\t *\n\t * Returns both assistant text and any error messages found.\n\t */\n\tprivate parseJsonlOutput(output: string): ParsedJsonlResult {\n\t\tconst lines = output.split('\\n').filter((line) => line.trim());\n\t\tconst assistantParts: string[] = [];\n\t\tconst errorParts: string[] = [];\n\n\t\tconsole.log('[LmChatCodexCli] parsing JSONL output, line count:', lines.length);\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(line) as Record<string, unknown>;\n\t\t\t\tconst eventType = parsed.type as string | undefined;\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t'[LmChatCodexCli] JSONL event:',\n\t\t\t\t\teventType,\n\t\t\t\t\t'| keys:',\n\t\t\t\t\tObject.keys(parsed).join(','),\n\t\t\t\t);\n\n\t\t\t\t// item.completed with agent_message => assistant text\n\t\t\t\tif (eventType === 'item.completed') {\n\t\t\t\t\tconst item = parsed.item as Record<string, unknown> | undefined;\n\t\t\t\t\tif (item?.type === 'agent_message' && typeof item.text === 'string') {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t'[LmChatCodexCli] found agent_message text, length:',\n\t\t\t\t\t\t\t(item.text as string).length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tassistantParts.push(item.text as string);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Direct message events (fallback for other versions)\n\t\t\t\tif (eventType === 'message' && parsed.role === 'assistant') {\n\t\t\t\t\tconst content = parsed.content;\n\t\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\t\tfor (const c of content as Array<Record<string, unknown>>) {\n\t\t\t\t\t\t\tif (c.type === 'text' && typeof c.text === 'string') {\n\t\t\t\t\t\t\t\tassistantParts.push(c.text as string);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (typeof content === 'string') {\n\t\t\t\t\t\tassistantParts.push(content);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Nested assistant message (cursor-agent compatibility)\n\t\t\t\tif (eventType === 'assistant') {\n\t\t\t\t\tconst message = parsed.message as Record<string, unknown> | undefined;\n\t\t\t\t\tif (message?.content) {\n\t\t\t\t\t\tif (Array.isArray(message.content)) {\n\t\t\t\t\t\t\tfor (const c of message.content as Array<Record<string, unknown>>) {\n\t\t\t\t\t\t\t\tif (c.type === 'text' && typeof c.text === 'string') {\n\t\t\t\t\t\t\t\t\tassistantParts.push(c.text as string);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (typeof message.content === 'string') {\n\t\t\t\t\t\t\tassistantParts.push(message.content);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Error events\n\t\t\t\tif (eventType === 'error' && typeof parsed.message === 'string') {\n\t\t\t\t\terrorParts.push(parsed.message);\n\t\t\t\t}\n\n\t\t\t\t// turn.failed with error\n\t\t\t\tif (eventType === 'turn.failed') {\n\t\t\t\t\tconst error = parsed.error as Record<string, unknown> | undefined;\n\t\t\t\t\tif (error && typeof error.message === 'string') {\n\t\t\t\t\t\terrorParts.push(error.message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip non-JSON lines (e.g. progress output)\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tassistantText: assistantParts.join(''),\n\t\t\terrorMessage: errorParts.join('; '),\n\t\t};\n\t}\n}\n\nexport class LmChatCodexCli implements INodeType {\n\tdescription: INodeTypeDescription = {\n\t\tdisplayName: 'Codex CLI Chat Model',\n\n\t\tname: 'lmChatCodexCli',\n\t\ticon: 'file:codexCli.svg',\n\t\tgroup: ['transform'],\n\t\tversion: [1],\n\t\tdescription:\n\t\t\t'Chat model powered by the OpenAI Codex CLI. Requires codex to be installed locally (npm install -g @openai/codex).',\n\t\tdefaults: {\n\t\t\tname: 'Codex CLI Chat Model',\n\t\t},\n\t\tcodex: {\n\t\t\tcategories: ['AI'],\n\t\t\tsubcategories: {\n\t\t\t\tAI: ['Language Models', 'Root Nodes'],\n\t\t\t\t'Language Models': ['Chat Models (Recommended)'],\n\t\t\t},\n\t\t\tresources: {},\n\t\t},\n\n\t\tinputs: [],\n\n\t\toutputs: [NodeConnectionTypes.AiLanguageModel],\n\t\toutputNames: ['Model'],\n\t\tproperties: [\n\t\t\tgetConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),\n\t\t\t{\n\t\t\t\tdisplayName: 'Model',\n\t\t\t\tname: 'model',\n\t\t\t\ttype: 'options',\n\t\t\t\tdescription: 'The model to use via codex CLI',\n\t\t\t\t// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items\n\t\t\t\toptions: [\n\t\t\t\t\t{ name: 'Auto (Default)', value: 'auto' },\n\t\t\t\t\t// GPT-5 series (supported by Codex CLI)\n\t\t\t\t\t{ name: 'GPT-5.5', value: 'gpt-5.5' },\n\t\t\t\t\t{ name: 'GPT-5.5 Fast', value: 'gpt-5.5-fast' },\n\t\t\t\t\t{ name: 'GPT-5.4', value: 'gpt-5.4' },\n\t\t\t\t\t{ name: 'GPT-5.4 Fast', value: 'gpt-5.4-fast' },\n\t\t\t\t\t{ name: 'GPT-5.4 Mini', value: 'gpt-5.4-mini' },\n\t\t\t\t\t{ name: 'GPT-5.3 Codex', value: 'gpt-5.3-codex' },\n\t\t\t\t\t{ name: 'GPT-5.3 Codex Spark', value: 'gpt-5.3-codex-spark' },\n\t\t\t\t\t{ name: 'GPT-5.2', value: 'gpt-5.2' },\n\t\t\t\t],\n\t\t\t\tdefault: 'auto',\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Options',\n\t\t\t\tname: 'options',\n\t\t\t\tplaceholder: 'Add Option',\n\t\t\t\tdescription: 'Additional options to configure',\n\t\t\t\ttype: 'collection',\n\t\t\t\tdefault: {},\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Binary Path',\n\t\t\t\t\t\tname: 'binaryPath',\n\t\t\t\t\t\tdefault: 'codex',\n\t\t\t\t\t\tdescription: 'Path to the codex binary. Defaults to \"codex\" (must be in PATH).',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Working Directory',\n\t\t\t\t\t\tname: 'workingDirectory',\n\t\t\t\t\t\tdefault: '',\n\t\t\t\t\t\tdescription: 'Working directory for the codex process. Leave empty to use the default.',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Sandbox Mode',\n\t\t\t\t\t\tname: 'sandboxMode',\n\t\t\t\t\t\ttype: 'options',\n\t\t\t\t\t\tdefault: 'read-only',\n\t\t\t\t\t\tdescription: 'Sandbox policy for executing model-generated shell commands',\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'Read Only',\n\t\t\t\t\t\t\t\tvalue: 'read-only',\n\t\t\t\t\t\t\t\tdescription: 'Only allow read operations (safest)',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'Workspace Write',\n\t\t\t\t\t\t\t\tvalue: 'workspace-write',\n\t\t\t\t\t\t\t\tdescription: 'Allow writes within the workspace directory',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'Full Access (Dangerous)',\n\t\t\t\t\t\t\t\tvalue: 'danger-full-access',\n\t\t\t\t\t\t\t\tdescription: 'Full filesystem access — use with extreme caution',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tasync supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {\n\t\tconst modelName = this.getNodeParameter('model', itemIndex) as string;\n\n\t\tconst binaryPath = this.getNodeParameter('options.binaryPath', itemIndex, 'codex') as string;\n\t\tconst rawWorkingDirectory = this.getNodeParameter('options.workingDirectory', itemIndex, '', {\n\t\t\trawExpressions: true,\n\t\t}) as string | undefined;\n\t\tconst workingDirectory = this.getNodeParameter('options.workingDirectory', itemIndex, '') as\n\t\t\t| string\n\t\t\t| undefined;\n\t\tconst normalizedWorkingDirectory = (workingDirectory ?? '').trim();\n\t\tconst rawWorkingDirectoryValue = rawWorkingDirectory ?? '';\n\t\tconst isWorkingDirectoryExpression =\n\t\t\trawWorkingDirectoryValue.startsWith('=') ||\n\t\t\trawWorkingDirectoryValue.includes('{{') ||\n\t\t\trawWorkingDirectoryValue.includes('$workspace');\n\n\t\tconst sandboxMode = this.getNodeParameter(\n\t\t\t'options.sandboxMode',\n\t\t\titemIndex,\n\t\t\t'read-only',\n\t\t) as string;\n\n\t\tconsole.log('[LmChatCodexCli] resolved Codex CLI options', {\n\t\t\titemIndex,\n\t\t\tmodelName,\n\t\t\tbinaryPath,\n\t\t\trawWorkingDirectory,\n\t\t\tworkingDirectory: normalizedWorkingDirectory,\n\t\t\tsandboxMode,\n\t\t});\n\n\t\tif (isWorkingDirectoryExpression && !normalizedWorkingDirectory) {\n\t\t\tthrow new ApplicationError(\n\t\t\t\t`Codex CLI working directory expression resolved to an empty value: ${rawWorkingDirectoryValue}`,\n\t\t\t);\n\t\t}\n\n\t\tif (\n\t\t\tnormalizedWorkingDirectory.includes('{{') ||\n\t\t\tnormalizedWorkingDirectory.includes('$workspace')\n\t\t) {\n\t\t\tthrow new ApplicationError(\n\t\t\t\t`Codex CLI working directory was not resolved before execution: ${normalizedWorkingDirectory}`,\n\t\t\t);\n\t\t}\n\n\t\tif (\n\t\t\tnormalizedWorkingDirectory &&\n\t\t\t(!existsSync(normalizedWorkingDirectory) ||\n\t\t\t\t!statSync(normalizedWorkingDirectory).isDirectory())\n\t\t) {\n\t\t\tthrow new ApplicationError(\n\t\t\t\t`Codex CLI working directory does not exist or is not a directory: ${normalizedWorkingDirectory}`,\n\t\t\t);\n\t\t}\n\n\t\tconsole.log('[LmChatCodexCli] creating ChatCodexCLI instance', {\n\t\t\tmodel: modelName,\n\t\t\tbinaryPath,\n\t\t\tworkingDirectory: normalizedWorkingDirectory,\n\t\t\tsandboxMode,\n\t\t});\n\n\t\tconst model = new ChatCodexCLI({\n\t\t\tmodel: modelName,\n\t\t\tbinaryPath,\n\t\t\tworkingDirectory: normalizedWorkingDirectory,\n\t\t\tsandboxMode,\n\t\t});\n\n\t\tmodel.callbacks = [new N8nLlmTracing(this)];\n\n\t\treturn {\n\t\t\tresponse: model,\n\t\t};\n\t}\n}\n"],"mappings":"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","names":["aiMessage"]}
@@ -0,0 +1 @@
1
+ <svg fill="none" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><rect width="512" height="512" rx="80" fill="#0a0a0a"/><rect x="4" y="4" width="504" height="504" rx="76" stroke="#edecec" stroke-opacity=".15" stroke-width="8"/><path d="M256 108c-81.6 0-148 66.4-148 148s66.4 148 148 148 148-66.4 148-148-66.4-148-148-148zm0 24c68.4 0 124 55.6 124 124s-55.6 124-124 124-124-55.6-124-124 55.6-124 124-124z" fill="#10a37f"/><path d="M256 172c-46.4 0-84 37.6-84 84s37.6 84 84 84 84-37.6 84-84-37.6-84-84-84zm0 24c33.1 0 60 26.9 60 60s-26.9 60-60 60-60-26.9-60-60 26.9-60 60-60z" fill="#10a37f"/><circle cx="256" cy="256" r="24" fill="#10a37f"/><path d="M256 108v40M256 364v40M108 256h40M364 256h40M152.8 152.8l28.3 28.3M330.9 330.9l28.3 28.3M152.8 359.2l28.3-28.3M330.9 181.1l28.3-28.3" stroke="#10a37f" stroke-width="12" stroke-linecap="round"/></svg>
@@ -27,6 +27,7 @@ var import_n8n_workflow = require("n8n-workflow");
27
27
  var import_sharedFields = require("../../../utils/sharedFields");
28
28
  var import_N8nLlmTracing = require("../N8nLlmTracing");
29
29
  var import_child_process = require("child_process");
30
+ var import_fs = require("fs");
30
31
  const TOOL_CALL_SYSTEM_PROMPT = `You have access to the following tools. When you need to call a tool, respond ONLY with a JSON block in this exact format (no other text before or after):
31
32
 
32
33
  \`\`\`tool_calls
@@ -126,9 +127,15 @@ class ChatCursorAgentCLI extends import_chat_models.BaseChatModel {
126
127
  if (this.model && this.model !== "auto") {
127
128
  args.push("--model", this.model);
128
129
  }
130
+ const cwd = this.workingDirectory?.trim() || void 0;
131
+ console.log("[LmChatCursorAgent] spawning cursor-agent", {
132
+ binaryPath: this.binaryPath,
133
+ model: this.model,
134
+ cwd
135
+ });
129
136
  return await new Promise((resolve, reject) => {
130
137
  const child = (0, import_child_process.spawn)(this.binaryPath, args, {
131
- cwd: this.workingDirectory || void 0,
138
+ cwd,
132
139
  stdio: ["pipe", "pipe", "pipe"],
133
140
  env: { ...process.env }
134
141
  });
@@ -143,7 +150,7 @@ class ChatCursorAgentCLI extends import_chat_models.BaseChatModel {
143
150
  child.on("error", (err) => {
144
151
  reject(
145
152
  new Error(
146
- `Failed to spawn cursor-agent: ${err.message}. Make sure cursor-agent CLI is installed and accessible.`
153
+ `Failed to spawn cursor-agent: ${err.message}. Make sure cursor-agent CLI is installed and accessible. Working directory: ${cwd ?? "<default>"}`
147
154
  )
148
155
  );
149
156
  });
@@ -214,7 +221,7 @@ class LmChatCursorAgent {
214
221
  displayName: "Model",
215
222
  name: "model",
216
223
  type: "options",
217
- description: "The model to use via cursor-agent CLI.",
224
+ description: "The model to use via cursor-agent CLI",
218
225
  // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
219
226
  options: [
220
227
  { name: "Auto", value: "auto" },
@@ -344,11 +351,44 @@ class LmChatCursorAgent {
344
351
  }
345
352
  async supplyData(itemIndex) {
346
353
  const modelName = this.getNodeParameter("model", itemIndex);
347
- const options = this.getNodeParameter("options", itemIndex, {});
354
+ const binaryPath = this.getNodeParameter(
355
+ "options.binaryPath",
356
+ itemIndex,
357
+ "cursor-agent"
358
+ );
359
+ const rawWorkingDirectory = this.getNodeParameter("options.workingDirectory", itemIndex, "", {
360
+ rawExpressions: true
361
+ });
362
+ const workingDirectory = this.getNodeParameter("options.workingDirectory", itemIndex, "");
363
+ const normalizedWorkingDirectory = (workingDirectory ?? "").trim();
364
+ const rawWorkingDirectoryValue = rawWorkingDirectory ?? "";
365
+ const isWorkingDirectoryExpression = rawWorkingDirectoryValue.startsWith("=") || rawWorkingDirectoryValue.includes("{{") || rawWorkingDirectoryValue.includes("$workspace");
366
+ console.log("[LmChatCursorAgent] resolved Cursor Agent options", {
367
+ itemIndex,
368
+ modelName,
369
+ binaryPath,
370
+ rawWorkingDirectory,
371
+ workingDirectory: normalizedWorkingDirectory
372
+ });
373
+ if (isWorkingDirectoryExpression && !normalizedWorkingDirectory) {
374
+ throw new import_n8n_workflow.ApplicationError(
375
+ `Cursor Agent working directory expression resolved to an empty value: ${rawWorkingDirectoryValue}`
376
+ );
377
+ }
378
+ if (normalizedWorkingDirectory.includes("{{") || normalizedWorkingDirectory.includes("$workspace")) {
379
+ throw new import_n8n_workflow.ApplicationError(
380
+ `Cursor Agent working directory was not resolved before execution: ${normalizedWorkingDirectory}`
381
+ );
382
+ }
383
+ if (normalizedWorkingDirectory && (!(0, import_fs.existsSync)(normalizedWorkingDirectory) || !(0, import_fs.statSync)(normalizedWorkingDirectory).isDirectory())) {
384
+ throw new import_n8n_workflow.ApplicationError(
385
+ `Cursor Agent working directory does not exist or is not a directory: ${normalizedWorkingDirectory}`
386
+ );
387
+ }
348
388
  const model = new ChatCursorAgentCLI({
349
389
  model: modelName,
350
- binaryPath: options.binaryPath ?? "cursor-agent",
351
- workingDirectory: options.workingDirectory ?? ""
390
+ binaryPath,
391
+ workingDirectory: normalizedWorkingDirectory
352
392
  });
353
393
  model.callbacks = [new import_N8nLlmTracing.N8nLlmTracing(this)];
354
394
  return {