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)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
export { ChatSession } from './smartchat.classes.chatsession.js';
|
||||
export type {
|
||||
IChatSessionOptions,
|
||||
IChatCallbacks,
|
||||
TChatEvent,
|
||||
IChatUsage,
|
||||
} from './smartchat.interfaces.js';
|
||||
@@ -0,0 +1,7 @@
|
||||
// @push.rocks/smartagent
|
||||
import { runAgent } from '@push.rocks/smartagent';
|
||||
export { runAgent };
|
||||
export type { IAgentRunOptions, IAgentRunResult } from '@push.rocks/smartagent';
|
||||
|
||||
// @push.rocks/smartai (types re-exported from ai SDK)
|
||||
export type { LanguageModelV3, ModelMessage, ToolSet } from '@push.rocks/smartai';
|
||||
@@ -0,0 +1,103 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { LanguageModelV3, ModelMessage, ToolSet, IAgentRunResult } from './plugins.js';
|
||||
|
||||
/**
|
||||
* Options for creating a ChatSession.
|
||||
*/
|
||||
export interface IChatSessionOptions {
|
||||
/** The language model to use */
|
||||
model: LanguageModelV3;
|
||||
/** System prompt for the agent */
|
||||
system?: string;
|
||||
/** Tools available to the agent */
|
||||
tools?: ToolSet;
|
||||
/** Maximum agentic steps per turn */
|
||||
maxSteps?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for real-time streaming events during a turn.
|
||||
*/
|
||||
export interface IChatCallbacks {
|
||||
/** Called for each streamed text token */
|
||||
onToken?: (delta: string) => void;
|
||||
/** Called when a tool call starts */
|
||||
onToolCall?: (name: string, input: unknown) => void;
|
||||
/** Called when a tool call completes */
|
||||
onToolResult?: (name: string, result: string) => void;
|
||||
/** Called when a turn completes */
|
||||
onTurnComplete?: (result: IAgentRunResult) => void;
|
||||
/** Called on error */
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Events emitted during chat.
|
||||
*/
|
||||
export type TChatEvent =
|
||||
| { type: 'token'; delta: string }
|
||||
| { type: 'tool_call_start'; name: string; input: unknown }
|
||||
| { type: 'tool_call_end'; name: string; result: string }
|
||||
| { type: 'turn_start' }
|
||||
| { type: 'turn_complete'; result: IAgentRunResult }
|
||||
| { type: 'error'; error: Error };
|
||||
|
||||
/**
|
||||
* Usage stats accumulated across all turns.
|
||||
*/
|
||||
export interface IChatUsage {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
totalTokens: number;
|
||||
turns: number;
|
||||
}
|
||||
Reference in New Issue
Block a user