104 lines
2.9 KiB
TypeScript
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);
|
||
|
|
}
|
||
|
|
}
|