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:
2026-03-06 23:20:12 +00:00
commit dd04edb420
24 changed files with 11344 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
export { ChatSession } from './smartchat.classes.chatsession.js';
export type {
IChatSessionOptions,
IChatCallbacks,
TChatEvent,
IChatUsage,
} from './smartchat.interfaces.js';
+7
View File
@@ -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';
+103
View File
@@ -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);
}
}
+52
View File
@@ -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;
}