@game_ryo/lsji 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +15 -7
  2. package/src/cli.js +395 -62
  3. package/src/execution/budget/circuit-breaker.js +245 -0
  4. package/src/execution/budget/cost-tracker.js +387 -0
  5. package/src/execution/budget/index.js +63 -0
  6. package/src/execution/budget/token-counter.js +159 -0
  7. package/src/execution/engine.js +428 -0
  8. package/src/execution/hitl/approval-gate.js +210 -0
  9. package/src/execution/hitl/index.js +12 -0
  10. package/src/execution/hitl/notifier.js +151 -0
  11. package/src/execution/hitl/store.js +311 -0
  12. package/src/execution/idempotency.js +312 -0
  13. package/src/execution/index.js +14 -0
  14. package/src/index.js +80 -4
  15. package/src/llm/index.js +21 -0
  16. package/src/llm/llm-agent.js +357 -0
  17. package/src/llm/memory/conversation.js +271 -0
  18. package/src/llm/memory/episodic.js +312 -0
  19. package/src/llm/memory/index.js +12 -0
  20. package/src/llm/memory/semantic.js +324 -0
  21. package/src/llm/plugins/index.js +202 -0
  22. package/src/llm/prompt-manager.js +332 -0
  23. package/src/llm/providers/anthropic.js +250 -0
  24. package/src/llm/providers/base.js +116 -0
  25. package/src/llm/providers/local.js +163 -0
  26. package/src/llm/providers/openai.js +212 -0
  27. package/src/llm/tools/registry.js +342 -0
  28. package/src/server/index.js +416 -0
  29. package/src/server/ui/index.html +16 -0
  30. package/src/server/ui/package.json +19 -0
  31. package/src/server/ui/src/main.jsx +10 -0
  32. package/src/server/ui/src/styles.css +260 -0
  33. package/src/server/ui/vite.config.js +27 -0
  34. package/docs/README.md +0 -43
  35. package/docs/blog/2019-05-28-first-blog-post.mdx +0 -12
  36. package/docs/blog/2019-05-29-long-blog-post.mdx +0 -44
  37. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  38. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  39. package/docs/blog/2021-08-26-welcome/index.mdx +0 -29
  40. package/docs/blog/authors.yml +0 -25
  41. package/docs/blog/tags.yml +0 -19
  42. package/docs/docs/api/agent.md +0 -151
  43. package/docs/docs/api/env.md +0 -133
  44. package/docs/docs/api/environments.md +0 -102
  45. package/docs/docs/api/qlearning.md +0 -138
  46. package/docs/docs/api/storage.md +0 -168
  47. package/docs/docs/architecture.md +0 -155
  48. package/docs/docs/cli.md +0 -210
  49. package/docs/docs/contributing.md +0 -162
  50. package/docs/docs/core-concepts.md +0 -152
  51. package/docs/docs/examples/advanced-training.md +0 -244
  52. package/docs/docs/examples/custom-environment.md +0 -198
  53. package/docs/docs/examples/custom-storage.md +0 -251
  54. package/docs/docs/getting-started.md +0 -91
  55. package/docs/docusaurus.config.ts +0 -149
  56. package/docs/package-lock.json +0 -19522
  57. package/docs/package.json +0 -49
  58. package/docs/sidebars.ts +0 -33
  59. package/docs/src/components/HomepageFeatures/index.tsx +0 -71
  60. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  61. package/docs/src/css/custom.css +0 -79
  62. package/docs/src/pages/index.module.css +0 -23
  63. package/docs/src/pages/index.tsx +0 -44
  64. package/docs/src/pages/markdown-page.mdx +0 -7
  65. package/docs/static/.nojekyll +0 -0
  66. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  67. package/docs/static/img/docusaurus.png +0 -0
  68. package/docs/static/img/favicon.ico +0 -0
  69. package/docs/static/img/logo.png +0 -0
  70. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  71. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  72. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  73. package/docs/tsconfig.json +0 -12
  74. package/legacy/worker.js +0 -166
  75. package/legacy/wrangler.toml +0 -11
@@ -0,0 +1,357 @@
1
+ /**
2
+ * LLM Agent
3
+ *
4
+ * Production-grade LLM-based agent with ReAct pattern, tools, memory,
5
+ * HITL approval, budget control, and durability.
6
+ */
7
+
8
+ import { createProvider } from './providers/base.js';
9
+ import { createToolRegistry } from './tools/registry.js';
10
+ import { createConversationMemory } from './memory/conversation.js';
11
+ import { createSemanticMemory } from './memory/semantic.js';
12
+ import { createEpisodicMemory } from './memory/episodic.js';
13
+ import { createPromptManager } from './prompt-manager.js';
14
+ import { createExecutionEngine } from '../execution/engine.js';
15
+ import { createApprovalGate } from '../execution/hitl/approval-gate.js';
16
+ import { createBudgetController } from '../execution/budget/index.js';
17
+ import { IdempotencyStore } from '../execution/idempotency.js';
18
+ import { v4 as uuidv4 } from 'uuid';
19
+
20
+ /**
21
+ * LLM Agent Configuration
22
+ * @typedef {Object} LLMAgentConfig
23
+ * @property {Object} llm - LLM provider config { provider, model, apiKey, ... }
24
+ * @property {Object} [tools] - Tool registry config
25
+ * @property {Object} [memory] - Memory config { conversation, semantic, episodic }
26
+ * @property {Object} [execution] - Execution engine config
27
+ * @property {Object} [hitl] - HITL approval config
28
+ * @property {Object} [budget] - Budget control config
29
+ * @property {Object} [idempotency] - Idempotency config
30
+ */
31
+
32
+ /**
33
+ * LLM Agent - Main agent class
34
+ */
35
+ export class LLMAgent {
36
+ constructor(config = {}) {
37
+ this.config = config;
38
+ this.llm = null;
39
+ this.tools = null;
40
+ this.memory = {};
41
+ this.execution = null;
42
+ this.hitl = null;
43
+ this.budget = null;
44
+ this.idempotency = null;
45
+ this.promptManager = null;
46
+ this.initialized = false;
47
+ }
48
+
49
+ /**
50
+ * Initialize all components
51
+ */
52
+ async initialize() {
53
+ if (this.initialized) return;
54
+
55
+ // Initialize LLM provider
56
+ this.llm = await createProvider(this.config.llm || { provider: 'openai', model: 'gpt-4o-mini' });
57
+
58
+ // Validate LLM
59
+ const valid = await this.llm.validate();
60
+ if (!valid.valid) {
61
+ throw new Error(`LLM validation failed: ${valid.error}`);
62
+ }
63
+
64
+ // Initialize tool registry
65
+ this.tools = createToolRegistry({
66
+ idempotencyStore: this.idempotency,
67
+ approvalGate: this.hitl,
68
+ });
69
+
70
+ // Initialize memory systems
71
+ if (this.config.memory?.conversation !== false) {
72
+ this.memory.conversation = await createConversationMemory(this.config.memory?.conversation);
73
+ await this.memory.conversation.startSession(this.config.sessionId);
74
+ }
75
+
76
+ if (this.config.memory?.semantic) {
77
+ this.memory.semantic = await createSemanticMemory(this.config.memory.semantic);
78
+ }
79
+
80
+ if (this.config.memory?.episodic) {
81
+ this.memory.episodic = await createEpisodicMemory(this.config.memory.episodic);
82
+ }
83
+
84
+ // Initialize execution engine
85
+ this.execution = await createExecutionEngine(this.config.execution);
86
+
87
+ // Initialize HITL
88
+ if (this.config.hitl?.enabled !== false) {
89
+ this.hitl = await createApprovalGate(this.config.hitl);
90
+ // Update tool registry with HITL
91
+ this.tools.approvalGate = this.hitl;
92
+ }
93
+
94
+ // Initialize budget
95
+ this.budget = createBudgetController(this.config.budget);
96
+
97
+ // Initialize idempotency
98
+ this.idempotency = await IdempotencyStore.create(this.config.idempotency);
99
+ this.tools.idempotencyStore = this.idempotency;
100
+
101
+ // Initialize prompt manager
102
+ this.promptManager = await createPromptManager(this.config.prompts);
103
+
104
+ this.initialized = true;
105
+ }
106
+
107
+ /**
108
+ * Run the agent on a task
109
+ */
110
+ async run(task, options = {}) {
111
+ await this.initialize();
112
+
113
+ const runId = options.runId || `run_${Date.now()}_${uuidv4().slice(0, 8)}`;
114
+ const budgetId = options.budgetId || runId;
115
+ const checkpointId = options.checkpointId;
116
+ const hitlRequired = options.hitlRequired || [];
117
+ const maxSteps = options.maxSteps || 50;
118
+
119
+ // Start episodic memory
120
+ let episodeId = null;
121
+ if (this.memory.episodic) {
122
+ episodeId = await this.memory.episodic.startEpisode(task, { runId, options });
123
+ }
124
+
125
+ // Get system prompt
126
+ const systemPrompt = await this.promptManager.render('system:react', {
127
+ tools: JSON.stringify(this.tools.getDefinitions(), null, 2),
128
+ task,
129
+ });
130
+
131
+ // Build initial messages
132
+ const messages = [
133
+ { role: 'system', content: systemPrompt },
134
+ { role: 'user', content: task },
135
+ ];
136
+
137
+ // Add conversation history if available
138
+ if (this.memory.conversation) {
139
+ const history = await this.memory.conversation.getMessagesForLLM(50000);
140
+ messages.splice(1, 0, ...history);
141
+ }
142
+
143
+ let step = 0;
144
+ let finalAnswer = null;
145
+
146
+ try {
147
+ while (step < maxSteps) {
148
+ step++;
149
+
150
+ // Check budget before each step
151
+ const budgetCheck = await this.budget.checkBudget(budgetId, 0, 0);
152
+ if (!budgetCheck.allowed) {
153
+ throw new Error(`Budget exceeded: ${budgetCheck.errors.map(e => e.type).join(', ')}`);
154
+ }
155
+
156
+ // Generate response
157
+ const response = await this.llm.generate(messages, {
158
+ tools: this.tools.getDefinitions(),
159
+ toolChoice: 'auto',
160
+ temperature: 0.7,
161
+ maxTokens: 4096,
162
+ });
163
+
164
+ // Record token usage
165
+ if (this.memory.episodic) {
166
+ this.memory.episodic.recordTokens(response.usage?.totalTokens || 0);
167
+ }
168
+ this.budget.recordCost({
169
+ budgetId,
170
+ ...response.usage,
171
+ model: this.llm.getModel(),
172
+ provider: this.config.llm?.provider || 'openai',
173
+ cost: this.llm.calculateCost?.(response.usage) || 0,
174
+ });
175
+
176
+ // Add assistant message
177
+ messages.push({
178
+ role: 'assistant',
179
+ content: response.content,
180
+ tool_calls: response.toolCalls,
181
+ });
182
+
183
+ if (this.memory.conversation) {
184
+ await this.memory.conversation.addMessage(messages[messages.length - 1]);
185
+ }
186
+
187
+ // Check for tool calls
188
+ if (response.toolCalls && response.toolCalls.length > 0) {
189
+ for (const toolCall of response.toolCalls) {
190
+ const toolName = toolCall.function.name;
191
+ const toolArgs = JSON.parse(toolCall.function.arguments);
192
+
193
+ // Check if tool requires HITL
194
+ const tool = this.tools.get(toolName);
195
+ if (tool?.requiresApproval && hitlRequired.includes(toolName)) {
196
+ // Request approval
197
+ try {
198
+ await this.hitl.requestApproval({
199
+ action: toolName,
200
+ context: { params: toolArgs, runId },
201
+ requester: 'agent',
202
+ });
203
+ } catch (error) {
204
+ // Approval denied or timeout
205
+ const errorMsg = `Tool ${toolName} requires approval: ${error.message}`;
206
+ messages.push({
207
+ role: 'tool',
208
+ tool_call_id: toolCall.id,
209
+ content: errorMsg,
210
+ });
211
+ continue;
212
+ }
213
+ }
214
+
215
+ // Execute tool
216
+ let result;
217
+ try {
218
+ result = await this.tools.execute(toolName, toolArgs, { runId, budgetId });
219
+ } catch (error) {
220
+ result = { error: error.message };
221
+ }
222
+
223
+ // Record tool execution in episodic memory
224
+ if (this.memory.episodic) {
225
+ await this.memory.episodic.addStep({
226
+ type: 'tool',
227
+ tool: toolName,
228
+ params: toolArgs,
229
+ result,
230
+ });
231
+ }
232
+
233
+ // Add tool result
234
+ messages.push({
235
+ role: 'tool',
236
+ tool_call_id: toolCall.id,
237
+ content: JSON.stringify(result),
238
+ });
239
+
240
+ if (this.memory.conversation) {
241
+ await this.memory.conversation.addMessage(messages[messages.length - 1]);
242
+ }
243
+ }
244
+
245
+ // Continue loop for next LLM call
246
+ continue;
247
+ }
248
+
249
+ // No tool calls - check if final answer
250
+ if (response.content && !response.content.includes('THOUGHT:') && !response.content.includes('ACTION:')) {
251
+ finalAnswer = response.content;
252
+ break;
253
+ }
254
+
255
+ // Parse ReAct format if present
256
+ if (response.content.includes('THOUGHT:') || response.content.includes('ACTION:')) {
257
+ // This is a ReAct formatted response without tool calls
258
+ // Continue to next iteration
259
+ continue;
260
+ }
261
+
262
+ // Default: treat as final answer
263
+ finalAnswer = response.content;
264
+ break;
265
+ }
266
+
267
+ // End episode
268
+ if (this.memory.episodic) {
269
+ await this.memory.episodic.endEpisode(finalAnswer ? 'success' : 'partial', { answer: finalAnswer, steps: step });
270
+ }
271
+
272
+ return {
273
+ success: !!finalAnswer,
274
+ answer: finalAnswer,
275
+ runId,
276
+ steps: step,
277
+ budget: this.budget.getStatus(budgetId),
278
+ };
279
+
280
+ } catch (error) {
281
+ if (this.memory.episodic) {
282
+ await this.memory.episodic.endEpisode('failure', { error: error.message, steps: step });
283
+ }
284
+ throw error;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Run with durability (checkpointing)
290
+ */
291
+ async runDurable(task, options = {}) {
292
+ await this.initialize();
293
+
294
+ const workflowId = options.workflowId || `workflow_${Date.now()}`;
295
+
296
+ return this.execution.execute({
297
+ id: workflowId,
298
+ name: task,
299
+ execute: async (context) => {
300
+ return this.run(task, { ...options, ...context });
301
+ },
302
+ }, {
303
+ workflowId,
304
+ checkpointEvery: options.checkpointEvery || 3,
305
+ idempotencyKey: options.idempotencyKey,
306
+ resumeFrom: options.resumeFrom,
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Resume from checkpoint
312
+ */
313
+ async resume(checkpointId) {
314
+ await this.initialize();
315
+ return this.execution.execute({ id: 'resume', execute: async () => {} }, { resumeFrom: checkpointId });
316
+ }
317
+
318
+ /**
319
+ * Get agent status
320
+ */
321
+ getStatus() {
322
+ return {
323
+ initialized: this.initialized,
324
+ llm: this.llm?.getModel(),
325
+ tools: this.tools?.getAll().map(t => t.name) || [],
326
+ memory: {
327
+ conversation: this.memory.conversation?.getSummary(),
328
+ semantic: this.memory.semantic ? 'enabled' : 'disabled',
329
+ episodic: this.memory.episodic ? 'enabled' : 'disabled',
330
+ },
331
+ budget: this.budget?.getStatus(),
332
+ hitl: this.hitl ? 'enabled' : 'disabled',
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Shutdown
338
+ */
339
+ async shutdown() {
340
+ if (this.memory.conversation) {
341
+ await this.memory.conversation.clear();
342
+ }
343
+ if (this.execution?.storage) {
344
+ await this.execution.storage.close();
345
+ }
346
+ this.initialized = false;
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Create LLM agent from config
352
+ */
353
+ export async function createLLMAgent(config = {}) {
354
+ const agent = new LLMAgent(config);
355
+ await agent.initialize();
356
+ return agent;
357
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Conversation Memory
3
+ *
4
+ * Manages conversation history for LLM agents.
5
+ * Supports token-aware truncation and persistence.
6
+ */
7
+
8
+ import { createStorage } from '../../index.js';
9
+
10
+ /**
11
+ * Conversation message
12
+ * @typedef {Object} Message
13
+ * @property {string} role - 'system' | 'user' | 'assistant' | 'tool'
14
+ * @property {string} content - Message content
15
+ * @property {string} [name] - Name for tool messages
16
+ * @property {string} [tool_call_id] - Tool call ID
17
+ * @property {Array} [tool_calls] - Tool calls from assistant
18
+ * @property {Date} [timestamp] - Message timestamp
19
+ */
20
+
21
+ /**
22
+ * Conversation Memory - Stores and manages conversation history
23
+ */
24
+ export class ConversationMemory {
25
+ constructor({ storage, maxTokens = 100000, tokenCounter } = {}) {
26
+ this.storage = storage;
27
+ this.maxTokens = maxTokens;
28
+ this.tokenCounter = tokenCounter;
29
+ this.messages = [];
30
+ this.sessionId = null;
31
+ this.initialized = false;
32
+ }
33
+
34
+ /**
35
+ * Initialize conversation table
36
+ */
37
+ async initialize() {
38
+ if (this.initialized) return;
39
+
40
+ if (this.storage.db) {
41
+ await this.storage.db.exec(`
42
+ CREATE TABLE IF NOT EXISTS conversations (
43
+ id TEXT PRIMARY KEY,
44
+ session_id TEXT NOT NULL,
45
+ role TEXT NOT NULL,
46
+ content TEXT NOT NULL,
47
+ name TEXT,
48
+ tool_call_id TEXT,
49
+ tool_calls TEXT,
50
+ tokens INTEGER,
51
+ created_at TEXT NOT NULL
52
+ )
53
+ `);
54
+
55
+ await this.storage.db.exec(`
56
+ CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id)
57
+ `);
58
+
59
+ await this.storage.db.exec(`
60
+ CREATE INDEX IF NOT EXISTS idx_conversations_created ON conversations(created_at)
61
+ `);
62
+ }
63
+
64
+ this.initialized = true;
65
+ }
66
+
67
+ /**
68
+ * Start a new conversation session
69
+ */
70
+ async startSession(sessionId = null) {
71
+ await this.initialize();
72
+ this.sessionId = sessionId || `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
73
+ this.messages = [];
74
+
75
+ if (!sessionId) {
76
+ // Load existing if sessionId provided
77
+ await this.loadSession(this.sessionId);
78
+ }
79
+
80
+ return this.sessionId;
81
+ }
82
+
83
+ /**
84
+ * Load conversation from storage
85
+ */
86
+ async loadSession(sessionId) {
87
+ await this.initialize();
88
+
89
+ if (this.storage.db) {
90
+ const rows = await this.storage.db.all(
91
+ 'SELECT * FROM conversations WHERE session_id = ? ORDER BY created_at ASC',
92
+ [sessionId]
93
+ );
94
+
95
+ this.messages = rows.map(row => ({
96
+ role: row.role,
97
+ content: row.content,
98
+ name: row.name,
99
+ tool_call_id: row.tool_call_id,
100
+ tool_calls: row.tool_calls ? JSON.parse(row.tool_calls) : undefined,
101
+ timestamp: row.created_at,
102
+ }));
103
+ }
104
+
105
+ return this.messages;
106
+ }
107
+
108
+ /**
109
+ * Add a message to conversation
110
+ */
111
+ async addMessage(message) {
112
+ await this.initialize();
113
+
114
+ const msg = {
115
+ role: message.role,
116
+ content: message.content,
117
+ name: message.name,
118
+ tool_call_id: message.tool_call_id,
119
+ tool_calls: message.tool_calls,
120
+ timestamp: message.timestamp || new Date().toISOString(),
121
+ };
122
+
123
+ this.messages.push(msg);
124
+
125
+ // Persist to storage
126
+ if (this.storage.db && this.sessionId) {
127
+ const tokens = this.tokenCounter
128
+ ? await this.tokenCounter.estimateTokens([msg], { provider: 'openai', model: 'gpt-4o-mini' })
129
+ : 0;
130
+
131
+ await this.storage.db.run(
132
+ `INSERT INTO conversations (id, session_id, role, content, name, tool_call_id, tool_calls, tokens, created_at)
133
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
134
+ [
135
+ `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
136
+ this.sessionId,
137
+ msg.role,
138
+ msg.content,
139
+ msg.name || null,
140
+ msg.tool_call_id || null,
141
+ msg.tool_calls ? JSON.stringify(msg.tool_calls) : null,
142
+ tokens,
143
+ msg.timestamp,
144
+ ]
145
+ );
146
+ }
147
+
148
+ // Trim if needed
149
+ await this.trimIfNeeded();
150
+
151
+ return msg;
152
+ }
153
+
154
+ /**
155
+ * Add multiple messages
156
+ */
157
+ async addMessages(messages) {
158
+ for (const msg of messages) {
159
+ await this.addMessage(msg);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Get all messages
165
+ */
166
+ getMessages() {
167
+ return [...this.messages];
168
+ }
169
+
170
+ /**
171
+ * Get messages for LLM (with token limit)
172
+ */
173
+ async getMessagesForLLM(maxTokens = null) {
174
+ const limit = maxTokens || this.maxTokens;
175
+ let totalTokens = 0;
176
+ const result = [];
177
+
178
+ // Add messages from newest to oldest until token limit
179
+ for (let i = this.messages.length - 1; i >= 0; i--) {
180
+ const msg = this.messages[i];
181
+ const msgTokens = this.tokenCounter
182
+ ? await this.tokenCounter.estimateTokens([msg], { provider: 'openai', model: 'gpt-4o-mini' })
183
+ : Math.ceil(msg.content.length / 4);
184
+
185
+ if (totalTokens + msgTokens > limit && result.length > 0) {
186
+ break;
187
+ }
188
+
189
+ totalTokens += msgTokens;
190
+ result.unshift(msg);
191
+ }
192
+
193
+ return result;
194
+ }
195
+
196
+ /**
197
+ * Trim old messages if over token limit
198
+ */
199
+ async trimIfNeeded() {
200
+ if (!this.tokenCounter) return;
201
+
202
+ const tokens = await this.tokenCounter.estimateTokens(
203
+ this.messages,
204
+ { provider: 'openai', model: 'gpt-4o-mini' }
205
+ );
206
+
207
+ if (tokens > this.maxTokens) {
208
+ // Remove oldest messages (but keep system message)
209
+ const systemMessages = this.messages.filter(m => m.role === 'system');
210
+ const otherMessages = this.messages.filter(m => m.role !== 'system');
211
+
212
+ // Keep removing oldest non-system messages until under limit
213
+ while (otherMessages.length > 0) {
214
+ otherMessages.shift();
215
+ const remainingTokens = await this.tokenCounter.estimateTokens(
216
+ [...systemMessages, ...otherMessages],
217
+ { provider: 'openai', model: 'gpt-4o-mini' }
218
+ );
219
+ if (remainingTokens <= this.maxTokens) break;
220
+ }
221
+
222
+ this.messages = [...systemMessages, ...otherMessages];
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Clear conversation
228
+ */
229
+ async clear() {
230
+ this.messages = [];
231
+
232
+ if (this.storage.db && this.sessionId) {
233
+ await this.storage.db.run(
234
+ 'DELETE FROM conversations WHERE session_id = ?',
235
+ [this.sessionId]
236
+ );
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Get conversation summary
242
+ */
243
+ getSummary() {
244
+ return {
245
+ sessionId: this.sessionId,
246
+ messageCount: this.messages.length,
247
+ roles: this.messages.reduce((acc, m) => {
248
+ acc[m.role] = (acc[m.role] || 0) + 1;
249
+ return acc;
250
+ }, {}),
251
+ firstMessage: this.messages[0]?.timestamp,
252
+ lastMessage: this.messages[this.messages.length - 1]?.timestamp,
253
+ };
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Create conversation memory from config
259
+ */
260
+ export async function createConversationMemory(config = {}) {
261
+ const storage = await createStorage(
262
+ config.storage?.type || 'sqlite',
263
+ config.storage?.options || {}
264
+ );
265
+
266
+ return new ConversationMemory({
267
+ storage,
268
+ maxTokens: config.maxTokens || 100000,
269
+ tokenCounter: config.tokenCounter,
270
+ });
271
+ }