matimo-examples 0.1.0-alpha.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.
- package/.env.example +36 -0
- package/LICENSE +21 -0
- package/README.md +525 -0
- package/agents/decorator-pattern-agent.ts +368 -0
- package/agents/factory-pattern-agent.ts +253 -0
- package/agents/langchain-agent.ts +146 -0
- package/edit/edit-decorator.ts +128 -0
- package/edit/edit-factory.ts +120 -0
- package/edit/edit-langchain.ts +272 -0
- package/execute/execute-decorator.ts +49 -0
- package/execute/execute-factory.ts +46 -0
- package/execute/execute-langchain.ts +163 -0
- package/gmail/README.md +345 -0
- package/gmail/gmail-decorator.ts +216 -0
- package/gmail/gmail-factory.ts +231 -0
- package/gmail/gmail-langchain.ts +201 -0
- package/package.json +58 -0
- package/postgres/README.md +188 -0
- package/postgres/postgres-decorator.ts +198 -0
- package/postgres/postgres-factory.ts +180 -0
- package/postgres/postgres-langchain.ts +213 -0
- package/postgres/postgres-with-approval.ts +250 -0
- package/read/read-decorator.ts +107 -0
- package/read/read-factory.ts +104 -0
- package/read/read-langchain.ts +253 -0
- package/search/search-decorator.ts +154 -0
- package/search/search-factory.ts +129 -0
- package/search/search-langchain.ts +215 -0
- package/slack/README.md +339 -0
- package/slack/slack-decorator.ts +245 -0
- package/slack/slack-factory.ts +226 -0
- package/slack/slack-langchain.ts +242 -0
- package/tsconfig.json +20 -0
- package/web/web-decorator.ts +52 -0
- package/web/web-factory.ts +70 -0
- package/web/web-langchain.ts +163 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Matimo + LangChain Agent - Proper ReAct Agent Pattern
|
|
4
|
+
*
|
|
5
|
+
* This demonstrates a complete agent loop:
|
|
6
|
+
* 1. LLM decides which tool to use based on goal
|
|
7
|
+
* 2. Tool is executed via Matimo
|
|
8
|
+
* 3. Result is fed back to LLM
|
|
9
|
+
* 4. Process repeats until agent reaches conclusion
|
|
10
|
+
*
|
|
11
|
+
* Key advantages:
|
|
12
|
+
* - Shows real agent reasoning loop
|
|
13
|
+
* - Single source of truth (Matimo YAML definitions)
|
|
14
|
+
* - How to integrate Matimo with any LangChain setup
|
|
15
|
+
* - Demonstrates tool selection and execution
|
|
16
|
+
*
|
|
17
|
+
* Run: npm run agent:langchain
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import 'dotenv/config';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
24
|
+
import { BaseMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
|
|
25
|
+
import { MatimoInstance, convertToolsToLangChain, ToolDefinition } from 'matimo';
|
|
26
|
+
|
|
27
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run LangChain ReAct Agent with Matimo Tools
|
|
31
|
+
*/
|
|
32
|
+
async function runLangChainAgent() {
|
|
33
|
+
console.info('\n╔════════════════════════════════════════════════════════╗');
|
|
34
|
+
console.info('║ Matimo + LangChain Agent (ReAct Pattern) ║');
|
|
35
|
+
console.info('║ Demonstrates real agent reasoning loop ║');
|
|
36
|
+
console.info('╚════════════════════════════════════════════════════════╝\n');
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Initialize Matimo
|
|
40
|
+
console.info('🚀 Initializing Matimo...');
|
|
41
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
42
|
+
|
|
43
|
+
const matimoTools = matimo.listTools();
|
|
44
|
+
console.info(`📦 Loaded ${matimoTools.length} tools:\n`);
|
|
45
|
+
matimoTools.forEach((t) => {
|
|
46
|
+
console.info(` • ${t.name}`);
|
|
47
|
+
console.info(` ${t.description}\n`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ✅ Convert Matimo tools to LangChain tools
|
|
51
|
+
console.info('🔧 Converting Matimo tools to LangChain format...\n');
|
|
52
|
+
const langchainTools = await convertToolsToLangChain(matimoTools as ToolDefinition[], matimo);
|
|
53
|
+
|
|
54
|
+
console.info(`✅ Successfully converted ${langchainTools.length} tools!\n`);
|
|
55
|
+
|
|
56
|
+
// 🤖 Create GPT-4o-mini LLM with tool binding
|
|
57
|
+
console.info('🧠 Creating GPT-4o-mini LLM with tool binding...\n');
|
|
58
|
+
const llm = new ChatOpenAI({
|
|
59
|
+
model: 'gpt-4o-mini',
|
|
60
|
+
temperature: 0,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const llmWithTools = llm.bindTools(langchainTools as any);
|
|
64
|
+
|
|
65
|
+
// 🎯 Agent Loop - ReAct Pattern
|
|
66
|
+
console.info('🧪 Starting Agent Loop (ReAct Pattern)\n');
|
|
67
|
+
console.info('═'.repeat(60));
|
|
68
|
+
|
|
69
|
+
const userQuery = 'What is 42 plus 58?';
|
|
70
|
+
console.info(`\n❓ User Query: "${userQuery}"\n`);
|
|
71
|
+
|
|
72
|
+
const messages: BaseMessage[] = [new HumanMessage(userQuery)];
|
|
73
|
+
|
|
74
|
+
let iterationCount = 0;
|
|
75
|
+
const maxIterations = 10;
|
|
76
|
+
let continueLoop = true;
|
|
77
|
+
|
|
78
|
+
while (continueLoop && iterationCount < maxIterations) {
|
|
79
|
+
iterationCount++;
|
|
80
|
+
console.info(`\n[Iteration ${iterationCount}]`);
|
|
81
|
+
console.info('─'.repeat(60));
|
|
82
|
+
|
|
83
|
+
// Step 1: Call LLM with tools
|
|
84
|
+
console.info('🤔 LLM Thinking...');
|
|
85
|
+
const response = await llmWithTools.invoke(messages);
|
|
86
|
+
console.info(`LLM Response Content: ${response.content || '(no text content)'}`);
|
|
87
|
+
|
|
88
|
+
// Step 2: Check if LLM wants to use tools
|
|
89
|
+
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
90
|
+
// Add assistant message to conversation
|
|
91
|
+
messages.push(response);
|
|
92
|
+
|
|
93
|
+
// Step 3: Execute each tool call
|
|
94
|
+
for (const toolCall of response.tool_calls) {
|
|
95
|
+
console.info(`\n🔧 Executing Tool: ${toolCall.name}`);
|
|
96
|
+
console.info(` Input: ${JSON.stringify(toolCall.args)}`);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Execute via Matimo
|
|
100
|
+
const result = await matimo.execute(toolCall.name, toolCall.args);
|
|
101
|
+
console.info(` ✅ Result: ${JSON.stringify(result)}`);
|
|
102
|
+
|
|
103
|
+
// Add tool result to conversation
|
|
104
|
+
messages.push(
|
|
105
|
+
new ToolMessage({
|
|
106
|
+
tool_call_id: toolCall.id || '',
|
|
107
|
+
content: JSON.stringify(result),
|
|
108
|
+
name: toolCall.name,
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
} catch (toolError) {
|
|
112
|
+
const msg = toolError instanceof Error ? toolError.message : String(toolError);
|
|
113
|
+
console.info(` ❌ Error: ${msg}`);
|
|
114
|
+
|
|
115
|
+
// Add error to conversation
|
|
116
|
+
messages.push(
|
|
117
|
+
new ToolMessage({
|
|
118
|
+
tool_call_id: toolCall.id || '',
|
|
119
|
+
content: `Error: ${msg}`,
|
|
120
|
+
name: toolCall.name,
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// Step 4: No more tools - agent reached conclusion
|
|
127
|
+
console.info('\n✅ Agent Reached Conclusion');
|
|
128
|
+
console.info(`\n📝 Final Response:\n${response.content || '(no response)'}`);
|
|
129
|
+
continueLoop = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (iterationCount >= maxIterations) {
|
|
134
|
+
console.info('\n⚠️ Max iterations reached');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.info('\n' + '═'.repeat(60));
|
|
138
|
+
console.info(`\n✨ Agent Loop Complete (${iterationCount} iterations)\n`);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('❌ Agent failed:', error instanceof Error ? error.message : String(error));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Run the agent
|
|
146
|
+
runLangChainAgent().catch(console.error);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MatimoInstance,
|
|
3
|
+
setGlobalMatimoInstance,
|
|
4
|
+
tool,
|
|
5
|
+
getPathApprovalManager,
|
|
6
|
+
} from '@matimo/core';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import readline from 'readline';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// Create readline interface for interactive approval prompts
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let isReadlineClosed = false;
|
|
21
|
+
|
|
22
|
+
// Track when readline closes (e.g., piped input ends)
|
|
23
|
+
rl.on('close', () => {
|
|
24
|
+
isReadlineClosed = true;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Prompt user for approval decision
|
|
29
|
+
*/
|
|
30
|
+
async function promptForApproval(
|
|
31
|
+
filePath: string,
|
|
32
|
+
mode: 'read' | 'write' | 'search'
|
|
33
|
+
): Promise<boolean> {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
// If readline is closed (e.g., non-TTY/piped input), auto-approve
|
|
36
|
+
if (isReadlineClosed) {
|
|
37
|
+
console.info(
|
|
38
|
+
`[${mode.toUpperCase()}] Access to ${filePath} auto-approved (non-interactive mode)`
|
|
39
|
+
);
|
|
40
|
+
resolve(true);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
rl.question(`[${mode.toUpperCase()}] Approve access to ${filePath}? (y/n): `, (answer) => {
|
|
44
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Example: Edit tool using @tool decorator pattern
|
|
51
|
+
* Demonstrates class-based file editing with automatic decoration
|
|
52
|
+
*/
|
|
53
|
+
class FileEditor {
|
|
54
|
+
@tool('edit')
|
|
55
|
+
async replaceContent(
|
|
56
|
+
filePath: string,
|
|
57
|
+
operation: string,
|
|
58
|
+
content: string,
|
|
59
|
+
startLine: number,
|
|
60
|
+
endLine: number
|
|
61
|
+
): Promise<unknown> {
|
|
62
|
+
// Decorator automatically intercepts and executes via Matimo
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@tool('edit')
|
|
67
|
+
async insertContent(
|
|
68
|
+
filePath: string,
|
|
69
|
+
operation: string,
|
|
70
|
+
content: string,
|
|
71
|
+
startLine: number,
|
|
72
|
+
endLine: number
|
|
73
|
+
): Promise<unknown> {
|
|
74
|
+
// Decorator automatically intercepts and executes via Matimo
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function decoratorExample() {
|
|
80
|
+
// Set up decorator support with autoDiscover
|
|
81
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
82
|
+
setGlobalMatimoInstance(matimo);
|
|
83
|
+
|
|
84
|
+
// Set up approval callback for interactive approval
|
|
85
|
+
const approvalManager = getPathApprovalManager();
|
|
86
|
+
approvalManager.setApprovalCallback(promptForApproval);
|
|
87
|
+
|
|
88
|
+
console.info('=== Edit Tool - Decorator Pattern (Interactive Approval) ===\n');
|
|
89
|
+
|
|
90
|
+
const editor = new FileEditor();
|
|
91
|
+
|
|
92
|
+
// Create a temp file for demonstration
|
|
93
|
+
const tempFile = path.join(__dirname, 'temp-demo-decorator.txt');
|
|
94
|
+
fs.writeFileSync(tempFile, 'Original line\nAnother line\nThird line\n');
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Example 1: Replace through decorated method
|
|
98
|
+
console.info('1. Replacing content via decorator\n');
|
|
99
|
+
const result1 = await editor.replaceContent(tempFile, 'replace', 'Updated line', 1, 1);
|
|
100
|
+
console.info('Edit Result:', (result1 as any).success);
|
|
101
|
+
console.info('Lines Affected:', (result1 as any).linesAffected);
|
|
102
|
+
console.info('File content:');
|
|
103
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
104
|
+
console.info('---\n');
|
|
105
|
+
|
|
106
|
+
// Example 2: Insert through decorated method
|
|
107
|
+
console.info('2. Inserting content via decorator\n');
|
|
108
|
+
const result2 = await editor.insertContent(tempFile, 'insert', 'Inserted line', 1, 0);
|
|
109
|
+
console.info('Insert Result:', (result2 as any).success);
|
|
110
|
+
console.info('Lines Affected:', (result2 as any).linesAffected);
|
|
111
|
+
console.info('File content:');
|
|
112
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
113
|
+
console.info('---\n');
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
console.error('Error:', error.message);
|
|
116
|
+
} finally {
|
|
117
|
+
// Clean up temp file
|
|
118
|
+
if (fs.existsSync(tempFile)) {
|
|
119
|
+
fs.unlinkSync(tempFile);
|
|
120
|
+
}
|
|
121
|
+
if (!isReadlineClosed) {
|
|
122
|
+
rl.close();
|
|
123
|
+
isReadlineClosed = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
decoratorExample();
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { MatimoInstance } from '@matimo/core';
|
|
2
|
+
import { getPathApprovalManager } from '@matimo/core';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
// Reusable readline interface kept open for multiple prompts
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
let isReadlineClosed = false;
|
|
17
|
+
|
|
18
|
+
// Track when readline closes (e.g., piped input ends)
|
|
19
|
+
rl.on('close', () => {
|
|
20
|
+
isReadlineClosed = true;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Interactive prompt helper for approval decisions
|
|
25
|
+
*/
|
|
26
|
+
async function promptForApproval(
|
|
27
|
+
filePath: string,
|
|
28
|
+
mode: 'read' | 'write' | 'search'
|
|
29
|
+
): Promise<boolean> {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
// If readline is closed (e.g., non-TTY/piped input), auto-approve
|
|
32
|
+
if (isReadlineClosed) {
|
|
33
|
+
console.info(
|
|
34
|
+
`[${mode.toUpperCase()}] Access to ${filePath} auto-approved (non-interactive mode)`
|
|
35
|
+
);
|
|
36
|
+
resolve(true);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
rl.question(`[${mode.toUpperCase()}] Approve access to ${filePath}? (y/n): `, (answer) => {
|
|
40
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Example: Edit tool using factory pattern
|
|
47
|
+
* Demonstrates editing and modifying file contents with interactive approval
|
|
48
|
+
*/
|
|
49
|
+
async function editExample() {
|
|
50
|
+
// Initialize Matimo with autoDiscover to find all tools (core + providers)
|
|
51
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
52
|
+
|
|
53
|
+
// Set up approval callback for examples with INTERACTIVE PROMPTS
|
|
54
|
+
const approvalManager = getPathApprovalManager();
|
|
55
|
+
approvalManager.setApprovalCallback(promptForApproval);
|
|
56
|
+
|
|
57
|
+
console.info('=== Edit Tool - Factory Pattern (Interactive Approval) ===\n');
|
|
58
|
+
|
|
59
|
+
// Create a temp file for demonstration
|
|
60
|
+
const tempFile = path.join(__dirname, 'temp-demo.txt');
|
|
61
|
+
fs.writeFileSync(tempFile, 'Line 1\nLine 2\nLine 3\n');
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Example 1: Replace text in file (replace line 2)
|
|
65
|
+
console.info('1. Replacing content in file\n');
|
|
66
|
+
console.info('Original content:');
|
|
67
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
68
|
+
console.info('---\n');
|
|
69
|
+
|
|
70
|
+
const result = await matimo.execute('edit', {
|
|
71
|
+
filePath: tempFile,
|
|
72
|
+
operation: 'replace',
|
|
73
|
+
content: 'Line 2 (Modified)',
|
|
74
|
+
startLine: 2,
|
|
75
|
+
endLine: 2,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if ((result as any).success) {
|
|
79
|
+
console.info('Edit Result:', (result as any).success);
|
|
80
|
+
console.info('Lines Affected:', (result as any).linesAffected);
|
|
81
|
+
console.info('\nModified content:');
|
|
82
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
83
|
+
} else {
|
|
84
|
+
console.info('Edit denied:', (result as any).error);
|
|
85
|
+
}
|
|
86
|
+
console.info('---\n');
|
|
87
|
+
|
|
88
|
+
// Example 2: Insert new content
|
|
89
|
+
console.info('2. Inserting new line\n');
|
|
90
|
+
const insertResult = await matimo.execute('edit', {
|
|
91
|
+
filePath: tempFile,
|
|
92
|
+
operation: 'insert',
|
|
93
|
+
content: 'New inserted line',
|
|
94
|
+
startLine: 2,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if ((insertResult as any).success) {
|
|
98
|
+
console.info('Insert Result:', (insertResult as any).success);
|
|
99
|
+
console.info('Lines Affected:', (insertResult as any).linesAffected);
|
|
100
|
+
console.info('\nFinal content:');
|
|
101
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
102
|
+
} else {
|
|
103
|
+
console.info('Insert denied:', (insertResult as any).error);
|
|
104
|
+
}
|
|
105
|
+
console.info('---\n');
|
|
106
|
+
} catch (error: any) {
|
|
107
|
+
console.error('Error editing file:', error.message);
|
|
108
|
+
} finally {
|
|
109
|
+
// Clean up temp file
|
|
110
|
+
if (fs.existsSync(tempFile)) {
|
|
111
|
+
fs.unlinkSync(tempFile);
|
|
112
|
+
}
|
|
113
|
+
if (!isReadlineClosed) {
|
|
114
|
+
rl.close();
|
|
115
|
+
isReadlineClosed = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
editExample();
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* EDIT TOOL - LANGCHAIN AI AGENT EXAMPLE
|
|
5
|
+
* ============================================================================
|
|
6
|
+
*
|
|
7
|
+
* PATTERN: True AI Agent with OpenAI + LangChain
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* This is a REAL AI agent that:
|
|
10
|
+
* 1. Takes natural language user requests
|
|
11
|
+
* 2. Uses OpenAI LLM (GPT-4o-mini) to decide which edit operations to use
|
|
12
|
+
* 3. Generates appropriate parameters based on context
|
|
13
|
+
* 4. Executes tools autonomously
|
|
14
|
+
* 5. Processes results and responds naturally
|
|
15
|
+
*
|
|
16
|
+
* Use this pattern when:
|
|
17
|
+
* ✅ Building true autonomous AI agents
|
|
18
|
+
* ✅ LLM should decide which tools to use
|
|
19
|
+
* ✅ Complex workflows with LLM reasoning
|
|
20
|
+
* ✅ Multi-step agentic processes
|
|
21
|
+
* ✅ User gives high-level instructions (not low-level API calls)
|
|
22
|
+
*
|
|
23
|
+
* SETUP:
|
|
24
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
25
|
+
* 1. Create .env file in examples/tools/:
|
|
26
|
+
* OPENAI_API_KEY=sk-xxxxxxxxxxxxx
|
|
27
|
+
*
|
|
28
|
+
* 2. Install dependencies:
|
|
29
|
+
* cd examples/tools && npm install
|
|
30
|
+
*
|
|
31
|
+
* USAGE:
|
|
32
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
33
|
+
* # From root directory:
|
|
34
|
+
* pnpm edit:langchain
|
|
35
|
+
*
|
|
36
|
+
* # Or from examples/tools directory:
|
|
37
|
+
* npm run edit:langchain
|
|
38
|
+
*
|
|
39
|
+
* WHAT IT DOES:
|
|
40
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
41
|
+
* This example shows an AI agent that can:
|
|
42
|
+
* 1. Replace text in files
|
|
43
|
+
* 2. Insert new content
|
|
44
|
+
* 3. Delete lines
|
|
45
|
+
* 4. Append to files
|
|
46
|
+
* 5. Respond naturally in conversation style
|
|
47
|
+
*
|
|
48
|
+
* Example conversation:
|
|
49
|
+
* User: "Update the TODO list - mark first item as DONE and add error handling"
|
|
50
|
+
* AI Agent: "I'll help you update the TODO list..."
|
|
51
|
+
* [AI Agent calls edit tool multiple times]
|
|
52
|
+
* AI Agent: "Done! I've updated the file with your changes."
|
|
53
|
+
*
|
|
54
|
+
* ============================================================================
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import 'dotenv/config';
|
|
58
|
+
import fs from 'fs';
|
|
59
|
+
import path from 'path';
|
|
60
|
+
import { fileURLToPath } from 'url';
|
|
61
|
+
import readline from 'readline';
|
|
62
|
+
import { createAgent } from 'langchain';
|
|
63
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
64
|
+
import {
|
|
65
|
+
MatimoInstance,
|
|
66
|
+
convertToolsToLangChain,
|
|
67
|
+
type ToolDefinition,
|
|
68
|
+
getPathApprovalManager,
|
|
69
|
+
} from '@matimo/core';
|
|
70
|
+
|
|
71
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run AI Agent with Edit tools
|
|
75
|
+
* The agent receives natural language requests and decides which edit operations to use
|
|
76
|
+
*/
|
|
77
|
+
// Create readline interface for interactive approval prompts
|
|
78
|
+
const rl = readline.createInterface({
|
|
79
|
+
input: process.stdin,
|
|
80
|
+
output: process.stdout,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let isReadlineClosed = false;
|
|
84
|
+
|
|
85
|
+
// Track when readline closes (e.g., piped input ends)
|
|
86
|
+
rl.on('close', () => {
|
|
87
|
+
isReadlineClosed = true;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Prompt user for approval decision
|
|
92
|
+
*/
|
|
93
|
+
async function promptForApproval(
|
|
94
|
+
filePath: string,
|
|
95
|
+
mode: 'read' | 'write' | 'search'
|
|
96
|
+
): Promise<boolean> {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
// If readline is closed (e.g., non-TTY/piped input), auto-approve
|
|
99
|
+
if (isReadlineClosed) {
|
|
100
|
+
console.info(
|
|
101
|
+
`[${mode.toUpperCase()}] Access to ${filePath} auto-approved (non-interactive mode)`
|
|
102
|
+
);
|
|
103
|
+
resolve(true);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
rl.question(`[${mode.toUpperCase()}] Approve access to ${filePath}? (y/n): `, (answer) => {
|
|
107
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function runEditAIAgent() {
|
|
113
|
+
console.info('\n╔════════════════════════════════════════════════════════╗');
|
|
114
|
+
console.info('║ Edit Tool AI Agent - LangChain + OpenAI ║');
|
|
115
|
+
console.info('║ True autonomous agent with LLM reasoning ║');
|
|
116
|
+
console.info('╚════════════════════════════════════════════════════════╝\n');
|
|
117
|
+
|
|
118
|
+
// Check required environment variables
|
|
119
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
120
|
+
if (!openaiKey) {
|
|
121
|
+
console.error('❌ Error: OPENAI_API_KEY not set in .env');
|
|
122
|
+
console.info(' Set it: export OPENAI_API_KEY="sk-..."');
|
|
123
|
+
console.info(' Get one from: https://platform.openai.com/api-keys');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.info('🤖 Using OpenAI (GPT-4o-mini) as the AI agent\n');
|
|
128
|
+
|
|
129
|
+
// Create a temp file for the agent to work with
|
|
130
|
+
const tempFile = path.join(__dirname, 'temp-agent-edit-demo.txt');
|
|
131
|
+
fs.writeFileSync(
|
|
132
|
+
tempFile,
|
|
133
|
+
`Project TODO List
|
|
134
|
+
================
|
|
135
|
+
- TODO: Implement authentication module
|
|
136
|
+
- TODO: Add database migrations
|
|
137
|
+
- TODO: Write API documentation
|
|
138
|
+
- TODO: Set up CI/CD pipeline
|
|
139
|
+
`
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Initialize Matimo with auto-discovery
|
|
144
|
+
console.info('🚀 Initializing Matimo...');
|
|
145
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
146
|
+
|
|
147
|
+
// Set up approval callback for interactive approval
|
|
148
|
+
const approvalManager = getPathApprovalManager();
|
|
149
|
+
approvalManager.setApprovalCallback(promptForApproval);
|
|
150
|
+
|
|
151
|
+
// Get edit tool
|
|
152
|
+
console.info('💬 Loading edit tool...');
|
|
153
|
+
const matimoTools = matimo.listTools();
|
|
154
|
+
const editTools = matimoTools.filter((t) => t.name === 'edit') as ToolDefinition[];
|
|
155
|
+
console.info(`✅ Loaded ${editTools.length} edit tool(s)\n`);
|
|
156
|
+
|
|
157
|
+
if (editTools.length === 0) {
|
|
158
|
+
console.error('❌ Edit tool not found');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Convert to LangChain tools using the built-in converter
|
|
163
|
+
const langchainTools = await convertToolsToLangChain(editTools, matimo);
|
|
164
|
+
|
|
165
|
+
// Initialize OpenAI LLM
|
|
166
|
+
console.info('🤖 Initializing OpenAI (GPT-4o-mini) LLM...');
|
|
167
|
+
const model = new ChatOpenAI({
|
|
168
|
+
modelName: 'gpt-4o-mini',
|
|
169
|
+
temperature: 0.7,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Create agent
|
|
173
|
+
console.info('🔧 Creating agent...\n');
|
|
174
|
+
const agent = await createAgent({
|
|
175
|
+
model,
|
|
176
|
+
tools: langchainTools as any,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Define agent tasks (natural language requests)
|
|
180
|
+
const userRequests = [
|
|
181
|
+
{
|
|
182
|
+
title: 'Example 1: Replace TODO with DONE',
|
|
183
|
+
request: `Mark the first authentication TODO as DONE in file ${tempFile}. Then describe what you did.`,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
title: 'Example 2: Insert new item',
|
|
187
|
+
request: `Add a new TODO "Set up error logging" after line 5 in file ${tempFile}. Confirm the action and tell me how many lines were affected.`,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
title: 'Example 3: List file content',
|
|
191
|
+
request: `Show me the current content of file ${tempFile} (use read tool if available, or describe the final state after your edits)`,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
console.info('🧪 Running AI Agent Tasks');
|
|
196
|
+
console.info('═'.repeat(60));
|
|
197
|
+
console.info(`📁 Working with: ${tempFile}\n`);
|
|
198
|
+
|
|
199
|
+
// Show initial file content
|
|
200
|
+
console.info('📄 Initial file content:');
|
|
201
|
+
console.info('─'.repeat(60));
|
|
202
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
203
|
+
console.info('─'.repeat(60) + '\n');
|
|
204
|
+
|
|
205
|
+
// Run each task through the agent
|
|
206
|
+
for (const task of userRequests) {
|
|
207
|
+
console.info(`${task.title}`);
|
|
208
|
+
console.info('─'.repeat(60));
|
|
209
|
+
console.info(`👤 User: "${task.request}"\n`);
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const response = await agent.invoke({
|
|
213
|
+
messages: [
|
|
214
|
+
{
|
|
215
|
+
role: 'user',
|
|
216
|
+
content: task.request,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Get the last message from the agent
|
|
222
|
+
const lastMessage = response.messages[response.messages.length - 1];
|
|
223
|
+
if (lastMessage) {
|
|
224
|
+
const content =
|
|
225
|
+
typeof lastMessage.content === 'string'
|
|
226
|
+
? lastMessage.content
|
|
227
|
+
: String(lastMessage.content);
|
|
228
|
+
|
|
229
|
+
console.info(`🤖 Agent: ${content || '(Tool executed successfully)'}\n`);
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
233
|
+
console.info(`⚠️ Agent error: ${errorMsg}\n`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.info('═'.repeat(60));
|
|
238
|
+
console.info('\n📄 Final file content:');
|
|
239
|
+
console.info('─'.repeat(60));
|
|
240
|
+
console.info(fs.readFileSync(tempFile, 'utf-8'));
|
|
241
|
+
console.info('─'.repeat(60));
|
|
242
|
+
|
|
243
|
+
console.info('\n✨ AI Agent Examples Complete!\n');
|
|
244
|
+
console.info('Key Features:');
|
|
245
|
+
console.info(' ✅ Real LLM (OpenAI) decides which tools to use');
|
|
246
|
+
console.info(' ✅ Natural language requests, not API calls');
|
|
247
|
+
console.info(' ✅ LLM generates tool parameters based on context');
|
|
248
|
+
console.info(' ✅ Agentic reasoning and decision-making\n');
|
|
249
|
+
console.info('⚠️ Note on agent responses:');
|
|
250
|
+
console.info(' The agent DOES call the real LLM to decide which tool to use.');
|
|
251
|
+
console.info(' However, LangChain agents generate minimal responses when tools succeed.');
|
|
252
|
+
console.info(' The agent autonomously executes tools without verbose narration.\n');
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('❌ Error:', error instanceof Error ? error.message : String(error));
|
|
255
|
+
if (error instanceof Error && error.stack) {
|
|
256
|
+
console.error('Stack:', error.stack);
|
|
257
|
+
}
|
|
258
|
+
process.exit(1);
|
|
259
|
+
} finally {
|
|
260
|
+
// Clean up
|
|
261
|
+
if (fs.existsSync(tempFile)) {
|
|
262
|
+
fs.unlinkSync(tempFile);
|
|
263
|
+
}
|
|
264
|
+
if (!isReadlineClosed) {
|
|
265
|
+
rl.close();
|
|
266
|
+
isReadlineClosed = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Run the AI agent
|
|
272
|
+
runEditAIAgent().catch(console.error);
|