Skip to content

Commit 40807ad

Browse files
authored
Add files via upload
1 parent 9d2d2dd commit 40807ad

File tree

2 files changed

+432
-0
lines changed

2 files changed

+432
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
2+
// CLIENT CODE
3+
4+
import { spawn } from 'child_process';
5+
import EventEmitter, { on } from 'node:events';
6+
7+
import { fileURLToPath } from 'url';
8+
import { dirname, join } from 'path';
9+
import { initializeMessage, initializedMessage, listToolsMessage, sampleResponse } from "./utils/messages.js";
10+
import {
11+
isJsonRpcMessage,
12+
getRpcMessage,
13+
isInitializeResponseMessage,
14+
isNotificationMessage
15+
} from "./utils/helpers.js";
16+
17+
const __filename = fileURLToPath(import.meta.url);
18+
const __dirname = dirname(__filename);
19+
20+
let serverPath = join(__dirname, 'server.js');
21+
console.log("DEBUG Client starting server at:", serverPath);
22+
23+
24+
const child = spawn('node', [serverPath]);
25+
26+
let initialized = false;
27+
28+
// Listen for data from the child
29+
30+
let onmessage: Function | null = null;
31+
let onsampling: Function | null = null;
32+
let onerror: Function | null = null;
33+
let onnotification: Function | null = null;
34+
35+
function handleRpcMessage(data) {
36+
let message = getRpcMessage(data.toString().trim());
37+
if (message.method && message.method.startsWith("sampling/")) {
38+
onsampling?.(message);
39+
40+
return;
41+
} else if(message.result) {
42+
onmessage?.(message.result);
43+
// If the message has a result, it is a response to a request
44+
45+
} else if (message.method && message.method.startsWith("notifications/")) {
46+
onnotification?.(message);
47+
} else {
48+
console.log(`CLIENT::ondata> (FROM SERVER): Unrecognized message: ${data.toString().trim()}`);
49+
}
50+
}
51+
52+
function _handleMessageResponse(): Promise<void> {
53+
return new Promise((resolve, reject) => {
54+
// we need to tell it to resolve when we get the response
55+
56+
function handleMessage(data) {
57+
const message = data.toString().trim();
58+
if (!isNotificationMessage(message)) {
59+
const json = getRpcMessage(message);
60+
61+
// TODO add null check
62+
63+
resolve(json?.result);
64+
65+
child.stdout.removeListener('data', handleMessage); // remove listener after handling the message
66+
67+
} else {
68+
// Notification do nothing, let other handlers take care of it
69+
// keep listener here as we got a nofication and we're still waiting for the response }
70+
}
71+
}
72+
73+
74+
child.stdout.on('data',handleMessage);
75+
});
76+
}
77+
78+
function _makeRequest(message: any) {
79+
child.stdin.write(_serializeMessage(message));
80+
}
81+
82+
function _serializeMessage(message: any): string {
83+
return JSON.stringify(message) + "\n";
84+
}
85+
86+
async function listTools(): Promise<void> {
87+
_makeRequest(listToolsMessage);
88+
89+
return _handleMessageResponse();
90+
}
91+
92+
async function callTool(toolName: string, args: any): Promise<void> {
93+
const toolMessage = {
94+
jsonrpc: "2.0",
95+
method: "tools/call",
96+
params: {
97+
name: toolName,
98+
arguments: args
99+
},
100+
id: Math.floor(Math.random() * 1000) // Random ID for the request
101+
};
102+
_makeRequest(toolMessage);
103+
return _handleMessageResponse();
104+
}
105+
106+
async function connect(): Promise<void> {
107+
// sending data to the server
108+
console.log("DEBUG Client sending data to server...");
109+
_makeRequest(initializeMessage);
110+
111+
return new Promise((resolve, reject) => {
112+
child.stdout.once('data', (data) => {
113+
const message = data.toString().trim();
114+
if (isJsonRpcMessage(message)) {
115+
const json = getRpcMessage(message);
116+
if (isInitializeResponseMessage(json)) {
117+
console.log("DEBUG Client received initialize response:", json.result);
118+
119+
_makeRequest(initializedMessage);
120+
console.log("DEBUG Client connected and initialized:");
121+
initialized = true;
122+
setupListeners();
123+
resolve();
124+
} else {
125+
reject(new Error("Unexpected message received during initialization."));
126+
}
127+
} else {
128+
reject(new Error("Invalid JSON RPC message received during initialization."));
129+
}
130+
});
131+
});
132+
}
133+
134+
function setupListeners() {
135+
child.stdout.resume(); // kick it back into action
136+
console.log("DEBUG CLIENT, Setting up listeners for child process...");
137+
child.stdout.on('data', (data) => {
138+
139+
if(isJsonRpcMessage(data.toString().trim())) {
140+
handleRpcMessage(data);
141+
} else {
142+
// TODO, handle this better, notifications come here too
143+
console.log(`setupListener: Unrecognized message: ${data.toString().trim()}`);
144+
}
145+
});
146+
147+
// Optionally handle errors
148+
child.stderr.on('data', (data) => {
149+
  console.error(`Client::onerror> (FROM SERVER): ${data}`);
150+
});
151+
152+
// Handle child process exit
153+
child.on('exit', (code) => {
154+
  console.log(`Client::onexit> (FROM SERVER): SERVER exited with code ${code}`);
155+
});
156+
}
157+
158+
async function main() {
159+
// just to keep track of all messages, responses and notifications
160+
// onmessage = (message) => {
161+
// console.log("MESSAGE RECEIVED:", message);
162+
// }
163+
164+
onnotification = (message) => {
165+
console.log("NOTIFICATION RECEIVED:", message);
166+
167+
};
168+
169+
onsampling = (message) => {
170+
console.log("SAMPLING RECEIVED:", message);
171+
console.log(message.params.messages[0].content.text);
172+
// call your LLM, product response back to server
173+
createSampleResponse(message.params.messages[0].content.text);
174+
};
175+
176+
await connect();
177+
// After connection, you can start sending messages
178+
let toolResponse = await listTools();
179+
console.log("Tools response:", toolResponse);
180+
181+
let toolResult = await callTool("ExampleTool", { arg1: 5, arg2: 10 });
182+
console.log("Tool call result:", toolResult);
183+
}
184+
185+
async function createSampleResponse(message: string) {
186+
const llmResponse = await callLLM(message);
187+
// make copy sampleResponse to avoid mutating the original
188+
const copy = { ...sampleResponse };
189+
copy.content.text = llmResponse;
190+
191+
_makeRequest(sampleResponse);
192+
}
193+
194+
async function callLLM(message: string): Promise<string> {
195+
return Promise.resolve(`LLM response to: ${message}`);
196+
}
197+
198+
199+
main();
200+

0 commit comments

Comments
 (0)