ai-harness / src /cli /commands /chat.ts
stevenkhan's picture
Initial AI Harness - production-grade model-agnostic CLI agent runtime
908562b verified
// ─── Chat Command ───────────────────────────────────────────────────────────
import { createInterface } from 'readline';
import { createRuntime } from '../state/factory.js';
import { EventRenderer } from '../renderers/index.js';
import type { Message } from '../../core/provider/index.js';
import { now, type HarnessEvent } from '../../core/events/index.js';
export async function chatCommand(opts: {
provider: string;
model?: string;
skills: string[];
verbose?: boolean;
compact?: boolean;
}): Promise<void> {
const renderer = new EventRenderer({ verbose: opts.verbose ?? false, compact: opts.compact ?? false });
const { runtime, eventBus, provider } = await createRuntime(opts);
eventBus.on((event) => renderer.render(event));
const rl = createInterface({ input: process.stdin, output: process.stdout });
const prompt = () => new Promise<string>((resolve) => rl.question('\x1b[36m❯\x1b[0m ', resolve));
console.log(`\x1b[90mProvider: ${provider.label} | Type your message (Ctrl+C to exit)\x1b[0m\n`);
const messages: Message[] = [{ role: 'system', content: 'You are a helpful AI assistant with access to tools.' }];
while (true) {
const input = await prompt();
if (!input.trim()) continue;
if (input.trim() === '/quit' || input.trim() === '/exit') break;
messages.push({ role: 'user', content: input });
eventBus.emit({ type: 'model.request.start', provider: provider.id, model: opts.model ?? 'default', timestamp: now() });
const startMs = Date.now();
try {
for await (const event of provider.stream({
model: opts.model ?? (await provider.listModels())[0]!.id,
messages,
tools: runtime ? undefined : undefined, // Tools available through runtime
})) {
if (event.type === 'text-delta') {
process.stdout.write(event.text);
} else if (event.type === 'finish') {
const durationMs = Date.now() - startMs;
eventBus.emit({ type: 'model.request.end', provider: provider.id, model: opts.model ?? 'default', usage: event.result.usage, durationMs, timestamp: now() });
messages.push({ role: 'assistant', content: event.result.content });
if (!event.result.content.endsWith('\n')) process.stdout.write('\n');
}
}
} catch (err) {
const errMsg = err instanceof Error ? err.message : String(err);
console.error(`\x1b[31mError: ${errMsg}\x1b[0m`);
}
console.log();
}
rl.close();
console.log('\x1b[90mGoodbye.\x1b[0m');
process.exit(0);
}