Files
smartchat/ts/smartchat.classes.chatsession.ts
jkunz dd04edb420 feat(initial): scaffold @push.rocks/smartchat with core, CLI, and web layers
Three-layer architecture built on @push.rocks/smartagent:
- ts/ — ChatSession wrapping runAgent() with conversation state management
- ts_cli/ — ink-based terminal chat TUI (React.createElement, no JSX)
- ts_web/ — Lit web components (smartchat-window, smartchat-message, smartchat-input)
2026-03-06 23:20:12 +00:00

104 lines
2.9 KiB
TypeScript

import { runAgent } from './plugins.js';
import type { ModelMessage, IAgentRunResult } from './plugins.js';
import type { IChatSessionOptions, IChatCallbacks, IChatUsage } from './smartchat.interfaces.js';
/**
* ChatSession manages in-memory conversation state and wraps `runAgent()`.
* Stateless by design — consumers are responsible for persistence.
*/
export class ChatSession {
private messages: ModelMessage[] = [];
private usage: IChatUsage = {
inputTokens: 0,
outputTokens: 0,
totalTokens: 0,
turns: 0,
};
private busy = false;
constructor(
private options: IChatSessionOptions,
private callbacks: IChatCallbacks = {},
) {}
/**
* Send a user message, run the agent, and return the result.
* Updates internal message history automatically.
*/
async send(userMessage: string): Promise<IAgentRunResult> {
if (this.busy) {
throw new Error('ChatSession is busy — wait for the current turn to complete.');
}
this.busy = true;
try {
const result = await runAgent({
model: this.options.model,
system: this.options.system,
prompt: userMessage,
tools: this.options.tools,
maxSteps: this.options.maxSteps ?? 20,
messages: this.messages,
onToken: this.callbacks.onToken,
onToolCall: this.callbacks.onToolCall,
});
// Update conversation history
this.messages = result.messages;
// Accumulate usage
this.usage.inputTokens += result.usage.inputTokens;
this.usage.outputTokens += result.usage.outputTokens;
this.usage.totalTokens += result.usage.totalTokens;
this.usage.turns++;
this.callbacks.onTurnComplete?.(result);
return result;
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
this.callbacks.onError?.(err);
throw err;
} finally {
this.busy = false;
}
}
/** Whether a turn is currently in progress */
isBusy(): boolean {
return this.busy;
}
/** Get current conversation history */
getMessages(): ModelMessage[] {
return [...this.messages];
}
/** Replace messages (e.g. for restoring a saved session) */
setMessages(messages: ModelMessage[]): void {
this.messages = [...messages];
}
/** Clear conversation history and reset usage */
clear(): void {
this.messages = [];
this.usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, turns: 0 };
}
/** Get accumulated token usage across all turns */
getUsage(): IChatUsage {
return { ...this.usage };
}
/** Update session options (model, system prompt, tools, etc.) */
updateOptions(options: Partial<IChatSessionOptions>): void {
Object.assign(this.options, options);
}
/** Update callbacks */
updateCallbacks(callbacks: Partial<IChatCallbacks>): void {
Object.assign(this.callbacks, callbacks);
}
}