1+ #!/usr/bin/env node
2+
13import { AzureChatOpenAI } from '@langchain/openai' ;
24import { BaseChatModel } from '@langchain/core/language_models/chat_models' ;
35import { ChatPromptTemplate } from '@langchain/core/prompts' ;
@@ -7,30 +9,117 @@ import { loadMcpTools } from '@langchain/mcp-adapters';
79import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' ;
810import { getAzureOpenAiTokenProvider } from './src/auth.js' ;
911import { Client } from '@modelcontextprotocol/sdk/client/index.js' ;
12+ import { BaseMessage , HumanMessage , AIMessage } from '@langchain/core/messages' ;
1013import path from 'node:path' ;
14+ import fs from 'node:fs/promises' ;
15+ import os from 'node:os' ;
1116import dotenv from 'dotenv' ;
1217
13- dotenv . config ( { path : path . join ( process . cwd ( ) , '../../.env' ) } ) ;
18+ dotenv . config ( { path : path . join ( process . cwd ( ) , '../../.env' ) , quiet : true } ) ;
1419
1520const agentSystemPrompt = `
16- # Role
21+ ## Role
1722You an expert assistant that helps users with managing burger orders. Use the provided tools to get the information you need and perform actions on behalf of the user.
1823Only answer to requests that are related to burger orders and the menu. If the user asks for something else, politely inform them that you can only assist with burger orders.
1924
20- # Task
25+ ## Task
2126Help the user with their request, ask any clarifying questions if needed.
2227
23- # Instructions
28+ ## Instructions
2429- Always use the tools provided to get the information requested or perform any actions
2530- If you get any errors when trying to use a tool that does not seem related to missing parameters, try again
2631- If you cannot get the information needed to answer the user's question or perform the specified action, inform the user that you are unable to do so. Never make up information.
2732- The get_burger tool can help you get informations about the burgers
2833- Creating or cancelling an order requires a \`userId\`: if not provided, ask the user to provide it or to run the CLI with the \`--userId\` option.
34+
35+ ## Output
36+ Your response will be printed to a terminal. Do not use markdown formatting or any other special formatting. Just provide the plain text response.
2937` ;
3038
31- const question = 'show me the burger menu' ;
39+ interface CliArgs {
40+ question: string ;
41+ userId ? : string ;
42+ isNew: boolean ;
43+ }
44+
45+ interface SessionData {
46+ history: Array < { type : 'human' | 'ai' ; content: string } > ;
47+ userId ? : string ;
48+ }
49+
50+ function parseArgs ( ) : CliArgs {
51+ const args = process . argv . slice ( 2 ) ;
52+
53+ if ( args . length === 0 || args . includes ( '--help' ) || args . includes ( '-h' ) ) {
54+ console . log ( 'Usage: agent-cli <question> [--userId <userId>] [--new]' ) ;
55+ console . log ( ' question: Your question about burger orders' ) ;
56+ console . log ( ' --userId: Optional user ID (needed for some tasks)' ) ;
57+ console . log ( ' --new: Start a new session' ) ;
58+ process . exit ( 0 ) ;
59+ }
60+
61+ const questionParts : string [ ] = [ ] ;
62+ let userId : string | undefined ;
63+ let isNew = false ;
64+
65+ for ( let i = 0 ; i < args . length ; i ++ ) {
66+ const arg = args [ i ] ;
67+
68+ if ( arg === '--userId' ) {
69+ userId = args [ i + 1 ] ;
70+ i ++ ;
71+ } else if ( arg === '--new' ) {
72+ isNew = true ;
73+ } else {
74+ questionParts . push ( arg ) ;
75+ }
76+ }
77+
78+ const question = questionParts . join ( ' ' ) ;
79+
80+ if ( ! question ) {
81+ console . error ( 'Error: Question is required' ) ;
82+ process . exit ( 1 ) ;
83+ }
84+
85+ return { question, userId, isNew } ;
86+ }
87+
88+ async function getSessionPath ( ) : Promise < string > {
89+ const userDataDir = path . join ( os . homedir ( ) , '.burger-agent-cli' ) ;
90+ await fs . mkdir ( userDataDir , { recursive : true } ) ;
91+ return path . join ( userDataDir , 'burger-agent-cli.json' ) ;
92+ }
93+
94+ async function loadSession ( ) : Promise < SessionData > {
95+ try {
96+ const sessionPath = await getSessionPath ( ) ;
97+ const content = await fs . readFile ( sessionPath , 'utf-8' ) ;
98+ return JSON . parse ( content ) ;
99+ } catch {
100+ return { history : [ ] } ;
101+ }
102+ }
103+
104+ async function saveSession ( session : SessionData ) : Promise < void > {
105+ try {
106+ const sessionPath = await getSessionPath ( ) ;
107+ await fs . writeFile ( sessionPath , JSON . stringify ( session , null , 2 ) ) ;
108+ } catch ( error ) {
109+ console . error ( 'Failed to save session:' , error ) ;
110+ }
111+ }
112+
113+ function convertHistoryToMessages ( history : SessionData [ 'history' ] ) : BaseMessage [ ] {
114+ return history . map ( msg =>
115+ msg . type === 'human'
116+ ? new HumanMessage ( msg . content )
117+ : new AIMessage ( msg . content )
118+ ) ;
119+ }
32120
33121export async function run ( ) {
122+ const { question, userId, isNew } = parseArgs ( ) ;
34123 const azureOpenAiEndpoint = process . env . AZURE_OPENAI_API_ENDPOINT ;
35124 const burgerMcpEndpoint = process . env . BURGER_MCP_URL ?? 'http://localhost:3000/mcp' ;
36125
@@ -40,20 +129,25 @@ export async function run() {
40129 if ( ! azureOpenAiEndpoint || ! burgerMcpEndpoint ) {
41130 const errorMessage = 'Missing required environment variables: AZURE_OPENAI_API_ENDPOINT or BURGER_MCP_URL' ;
42131 console . error ( errorMessage ) ;
43- return {
44- status : 500 ,
45- jsonBody : {
46- error : errorMessage ,
47- } ,
48- } ;
132+ return ;
133+ }
134+
135+ let session : SessionData ;
136+ if ( isNew ) {
137+ session = { history : [ ] , userId } ;
138+ } else {
139+ session = await loadSession ( ) ;
140+ if ( userId && session . userId !== userId ) {
141+ session . userId = userId ;
142+ }
49143 }
50144
51145 const azureADTokenProvider = getAzureOpenAiTokenProvider ( ) ;
52146
53147 model = new AzureChatOpenAI ( {
54- // Controls randomness. 0 = deterministic, 1 = maximum randomness
55148 temperature : 0.3 ,
56149 azureADTokenProvider,
150+ azureOpenAIApiVersion : process . env . AZURE_OPENAI_API_VERSION ,
57151 } ) ;
58152
59153 const client = new Client ( {
@@ -67,8 +161,10 @@ export async function run() {
67161 const tools = await loadMcpTools ( 'burger' , client ) ;
68162 console . log ( `Loaded ${ tools . length } tools from Burger MCP server` ) ;
69163
164+ console . log ( `Thinking...` ) ;
165+
70166 const prompt = ChatPromptTemplate . fromMessages ( [
71- [ 'system' , agentSystemPrompt ] ,
167+ [ 'system' , agentSystemPrompt + ( session . userId ? `\n\nUser ID: ${ session . userId } ` : '' ) ] ,
72168 [ 'placeholder' , '{chat_history}' ] ,
73169 [ 'human' , '{input}' ] ,
74170 [ 'placeholder' , '{agent_scratchpad}' ] ,
@@ -82,17 +178,31 @@ export async function run() {
82178 const agentExecutor = new AgentExecutor ( {
83179 agent,
84180 tools,
85- returnIntermediateSteps : true ,
181+ returnIntermediateSteps : false ,
182+ } ) ;
183+
184+ const chatHistory = convertHistoryToMessages ( session . history ) ;
185+
186+ const response = await agentExecutor . invoke ( {
187+ input : question ,
188+ chat_history : chatHistory
86189 } ) ;
87190
88- const response = await agentExecutor . invoke ( { input : question } ) ;
191+ console . log ( '----------\n' + response . output ) ;
192+
193+ session . history . push ( { type : 'human' , content : question } ) ;
194+ session . history . push ( { type : 'ai' , content : response . output } ) ;
195+
196+ await saveSession ( session ) ;
197+
198+ console . log ( '----------\nDone.' ) ;
89199
90- console . log ( 'Intermediate steps:' , JSON . stringify ( response . intermediateSteps , null , 2 ) ) ;
91- console . log ( 'Final answer:' , response . output ) ;
92200 } catch ( _error : unknown ) {
93201 const error = _error as Error ;
94- console . error ( `Error when processing chat-post request: ${ error . message } ` ) ;
202+ console . error ( `Error when processing request: ${ error . message } ` ) ;
95203 }
96204}
97205
98- run ( ) ;
206+ if ( process . argv [ 1 ] && process . argv [ 1 ] . endsWith ( 'agent-cli.ts' ) || process . argv [ 1 ] . endsWith ( 'agent-cli.js' ) ) {
207+ run ( ) ;
208+ }
0 commit comments