Files
BrowserOS/tests/agent-cli.ts
Nikhil 575b8fb24a few minor improvements to new agent (#68)
* feat: agent-cli to test agent server locally

* fix: make browseros tests headless
2025-12-04 10:50:45 -08:00

163 lines
4.2 KiB
TypeScript

#!/usr/bin/env bun
/**
* Chat CLI - Send a chat request to the HTTP Agent Server
*
* Usage:
* bun scripts/chat.ts "your message here"
* bun scripts/chat.ts --provider=openai --model=gpt-4o "your message here"
*
* Options:
* --provider AI provider (default: google)
* --model Model name (default: gemini-2.5-flash)
* --host Server host (default: http://127.0.0.1:9200)
* --show-full-output Show full tool output (default: truncated to 50 chars)
*/
interface ChatRequest {
conversationId: string;
message: string;
provider: string;
model: string;
apiKey?: string;
}
function parseArgs(): {
message: string;
provider: string;
model: string;
host: string;
showFullOutput: boolean;
} {
const args = process.argv.slice(2);
let provider = 'google';
let model = 'gemini-2.5-flash';
let host = 'http://127.0.0.1:9200';
let showFullOutput = false;
let message = '';
for (const arg of args) {
if (arg.startsWith('--provider=')) {
provider = arg.split('=')[1];
} else if (arg.startsWith('--model=')) {
model = arg.split('=')[1];
} else if (arg.startsWith('--host=')) {
host = arg.split('=')[1];
} else if (arg === '--show-full-output') {
showFullOutput = true;
} else if (!arg.startsWith('--')) {
message = arg;
}
}
if (!message) {
console.error('Usage: bun tests/test-agent-cli.ts [options] "your message"');
console.error('Options:');
console.error(' --provider=<provider> AI provider (anthropic, openai, google, etc.)');
console.error(' --model=<model> Model name');
console.error(' --host=<url> Server URL (default: http://127.0.0.1:9200)');
console.error(' --show-full-output Show full tool output (default: truncated)');
process.exit(1);
}
return {message, provider, model, host, showFullOutput};
}
function truncateOutput(obj: unknown, maxLen = 50): unknown {
if (typeof obj === 'string') {
return obj.length > maxLen ? obj.slice(0, maxLen) + '...' : obj;
}
if (Array.isArray(obj)) {
return obj.map(item => truncateOutput(item, maxLen));
}
if (obj && typeof obj === 'object') {
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = truncateOutput(value, maxLen);
}
return result;
}
return obj;
}
async function chat(config: {
message: string;
provider: string;
model: string;
host: string;
showFullOutput: boolean;
}) {
const conversationId = crypto.randomUUID();
const request: ChatRequest = {
conversationId,
message: config.message,
provider: config.provider,
model: config.model,
};
console.log('\n--- Request ---');
console.log(JSON.stringify(request, null, 2));
console.log('\n--- Response Stream ---\n');
const response = await fetch(`${config.host}/chat`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(request),
});
if (!response.ok) {
const error = await response.text();
console.error(`HTTP ${response.status}: ${error}`);
process.exit(1);
}
const reader = response.body?.getReader();
if (!reader) {
console.error('No response body');
process.exit(1);
}
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const {done, value} = await reader.read();
if (done) break;
buffer += decoder.decode(value, {stream: true});
const lines = buffer.split('\n\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
console.log('\n--- Done ---\n');
continue;
}
try {
const event = JSON.parse(data);
let displayEvent = event;
if (!config.showFullOutput && event.type === 'tool-output-available') {
displayEvent = { ...event, output: truncateOutput(event.output) };
}
console.log(JSON.stringify(displayEvent, null, 2));
} catch {
console.log(data);
}
}
}
}
}
const config = parseArgs();
chat(config).catch(err => {
console.error('Error:', err.message);
process.exit(1);
});