confused-ai-core 0.1.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.
- package/FEATURES.md +169 -0
- package/package.json +119 -0
- package/src/agent.ts +187 -0
- package/src/agentic/index.ts +87 -0
- package/src/agentic/runner.ts +386 -0
- package/src/agentic/types.ts +91 -0
- package/src/artifacts/artifact.ts +417 -0
- package/src/artifacts/index.ts +42 -0
- package/src/artifacts/media.ts +304 -0
- package/src/cli/index.ts +122 -0
- package/src/core/base-agent.ts +151 -0
- package/src/core/context-builder.ts +106 -0
- package/src/core/index.ts +8 -0
- package/src/core/schemas.ts +17 -0
- package/src/core/types.ts +158 -0
- package/src/create-agent.ts +309 -0
- package/src/debug-logger.ts +188 -0
- package/src/dx/agent.ts +88 -0
- package/src/dx/define-agent.ts +183 -0
- package/src/dx/dev-logger.ts +57 -0
- package/src/dx/index.ts +11 -0
- package/src/errors.ts +175 -0
- package/src/execution/engine.ts +522 -0
- package/src/execution/graph-builder.ts +362 -0
- package/src/execution/index.ts +8 -0
- package/src/execution/types.ts +257 -0
- package/src/execution/worker-pool.ts +308 -0
- package/src/extensions/index.ts +123 -0
- package/src/guardrails/allowlist.ts +155 -0
- package/src/guardrails/index.ts +17 -0
- package/src/guardrails/types.ts +159 -0
- package/src/guardrails/validator.ts +265 -0
- package/src/index.ts +74 -0
- package/src/knowledge/index.ts +5 -0
- package/src/knowledge/types.ts +52 -0
- package/src/learning/in-memory-store.ts +72 -0
- package/src/learning/index.ts +6 -0
- package/src/learning/types.ts +42 -0
- package/src/llm/cache.ts +300 -0
- package/src/llm/index.ts +22 -0
- package/src/llm/model-resolver.ts +81 -0
- package/src/llm/openai-provider.ts +313 -0
- package/src/llm/openrouter-provider.ts +29 -0
- package/src/llm/types.ts +131 -0
- package/src/memory/in-memory-store.ts +255 -0
- package/src/memory/index.ts +7 -0
- package/src/memory/types.ts +193 -0
- package/src/memory/vector-store.ts +251 -0
- package/src/observability/console-logger.ts +123 -0
- package/src/observability/index.ts +12 -0
- package/src/observability/metrics.ts +85 -0
- package/src/observability/otlp-exporter.ts +417 -0
- package/src/observability/tracer.ts +105 -0
- package/src/observability/types.ts +341 -0
- package/src/orchestration/agent-adapter.ts +33 -0
- package/src/orchestration/index.ts +34 -0
- package/src/orchestration/load-balancer.ts +151 -0
- package/src/orchestration/mcp-types.ts +59 -0
- package/src/orchestration/message-bus.ts +192 -0
- package/src/orchestration/orchestrator.ts +349 -0
- package/src/orchestration/pipeline.ts +66 -0
- package/src/orchestration/supervisor.ts +107 -0
- package/src/orchestration/swarm.ts +1099 -0
- package/src/orchestration/toolkit.ts +47 -0
- package/src/orchestration/types.ts +339 -0
- package/src/planner/classical-planner.ts +383 -0
- package/src/planner/index.ts +8 -0
- package/src/planner/llm-planner.ts +353 -0
- package/src/planner/types.ts +227 -0
- package/src/planner/validator.ts +297 -0
- package/src/production/circuit-breaker.ts +290 -0
- package/src/production/graceful-shutdown.ts +251 -0
- package/src/production/health.ts +333 -0
- package/src/production/index.ts +57 -0
- package/src/production/latency-eval.ts +62 -0
- package/src/production/rate-limiter.ts +287 -0
- package/src/production/resumable-stream.ts +289 -0
- package/src/production/types.ts +81 -0
- package/src/sdk/index.ts +374 -0
- package/src/session/db-driver.ts +50 -0
- package/src/session/in-memory-store.ts +235 -0
- package/src/session/index.ts +12 -0
- package/src/session/sql-store.ts +315 -0
- package/src/session/sqlite-store.ts +61 -0
- package/src/session/types.ts +153 -0
- package/src/tools/base-tool.ts +223 -0
- package/src/tools/browser-tool.ts +123 -0
- package/src/tools/calculator-tool.ts +265 -0
- package/src/tools/file-tools.ts +394 -0
- package/src/tools/github-tool.ts +432 -0
- package/src/tools/hackernews-tool.ts +187 -0
- package/src/tools/http-tool.ts +118 -0
- package/src/tools/index.ts +99 -0
- package/src/tools/jira-tool.ts +373 -0
- package/src/tools/notion-tool.ts +322 -0
- package/src/tools/openai-tool.ts +236 -0
- package/src/tools/registry.ts +131 -0
- package/src/tools/serpapi-tool.ts +234 -0
- package/src/tools/shell-tool.ts +118 -0
- package/src/tools/slack-tool.ts +327 -0
- package/src/tools/telegram-tool.ts +127 -0
- package/src/tools/types.ts +229 -0
- package/src/tools/websearch-tool.ts +335 -0
- package/src/tools/wikipedia-tool.ts +177 -0
- package/src/tools/yfinance-tool.ts +33 -0
- package/src/voice/index.ts +17 -0
- package/src/voice/voice-provider.ts +228 -0
- package/tests/artifact.test.ts +241 -0
- package/tests/circuit-breaker.test.ts +171 -0
- package/tests/health.test.ts +192 -0
- package/tests/llm-cache.test.ts +186 -0
- package/tests/rate-limiter.test.ts +161 -0
- package/tsconfig.json +29 -0
- package/vitest.config.ts +47 -0
package/src/sdk/index.ts
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK for building agents with a fluent API
|
|
3
|
+
*
|
|
4
|
+
* This module provides a high-level, chainable API for creating and configuring
|
|
5
|
+
* agents, making it easy for developers to get started quickly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import {
|
|
10
|
+
Agent,
|
|
11
|
+
AgentConfig,
|
|
12
|
+
AgentContext,
|
|
13
|
+
AgentInput,
|
|
14
|
+
AgentOutput,
|
|
15
|
+
AgentState,
|
|
16
|
+
BaseAgent,
|
|
17
|
+
} from '../core/index.js';
|
|
18
|
+
import { MemoryStore, InMemoryStore } from '../memory/index.js';
|
|
19
|
+
import { ToolRegistry, ToolRegistryImpl, Tool } from '../tools/index.js';
|
|
20
|
+
import { Planner, ClassicalPlanner, PlanningAlgorithm } from '../planner/index.js';
|
|
21
|
+
import { ExecutionEngine, ExecutionEngineImpl } from '../execution/index.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Agent definition configuration
|
|
25
|
+
*/
|
|
26
|
+
export interface AgentDefinitionConfig<TInput, TOutput> {
|
|
27
|
+
name: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
inputSchema: z.ZodType<TInput>;
|
|
30
|
+
outputSchema: z.ZodType<TOutput>;
|
|
31
|
+
tools?: Tool[];
|
|
32
|
+
memory?: MemoryStore;
|
|
33
|
+
planner?: Planner;
|
|
34
|
+
maxIterations?: number;
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Agent run configuration
|
|
40
|
+
*/
|
|
41
|
+
export interface AgentRunConfig<TInput> {
|
|
42
|
+
input: TInput;
|
|
43
|
+
context?: Record<string, unknown>;
|
|
44
|
+
metadata?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Define a new agent with typed input/output
|
|
49
|
+
*/
|
|
50
|
+
export function defineAgent<TInput = string, TOutput = unknown>(
|
|
51
|
+
config: AgentDefinitionConfig<TInput, TOutput>
|
|
52
|
+
): DefinedAgent<TInput, TOutput> {
|
|
53
|
+
return new DefinedAgent(config);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Defined agent class with fluent API
|
|
58
|
+
*/
|
|
59
|
+
export class DefinedAgent<TInput, TOutput> {
|
|
60
|
+
private config: AgentDefinitionConfig<TInput, TOutput>;
|
|
61
|
+
private toolRegistry: ToolRegistry;
|
|
62
|
+
private memoryStore: MemoryStore;
|
|
63
|
+
private plannerInstance: Planner;
|
|
64
|
+
private _executionEngine: ExecutionEngine;
|
|
65
|
+
|
|
66
|
+
constructor(config: AgentDefinitionConfig<TInput, TOutput>) {
|
|
67
|
+
this.config = config;
|
|
68
|
+
this.toolRegistry = new ToolRegistryImpl();
|
|
69
|
+
this.memoryStore = config.memory ?? new InMemoryStore();
|
|
70
|
+
this.plannerInstance = config.planner ?? new ClassicalPlanner({ algorithm: PlanningAlgorithm.HIERARCHICAL });
|
|
71
|
+
// _executionEngine is initialized for future use in distributed execution scenarios
|
|
72
|
+
this._executionEngine = new ExecutionEngineImpl();
|
|
73
|
+
|
|
74
|
+
// Register provided tools
|
|
75
|
+
if (config.tools) {
|
|
76
|
+
for (const tool of config.tools) {
|
|
77
|
+
this.toolRegistry.register(tool);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Add a tool to the agent
|
|
84
|
+
*/
|
|
85
|
+
withTool(tool: Tool): this {
|
|
86
|
+
this.toolRegistry.register(tool);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Add multiple tools
|
|
92
|
+
*/
|
|
93
|
+
withTools(tools: Tool[]): this {
|
|
94
|
+
for (const tool of tools) {
|
|
95
|
+
this.toolRegistry.register(tool);
|
|
96
|
+
}
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set the memory store
|
|
102
|
+
*/
|
|
103
|
+
withMemory(memory: MemoryStore): this {
|
|
104
|
+
Object.assign(this, { memoryStore: memory });
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Set the planner
|
|
110
|
+
*/
|
|
111
|
+
withPlanner(planner: Planner): this {
|
|
112
|
+
Object.assign(this, { plannerInstance: planner });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Set the execution engine
|
|
118
|
+
*/
|
|
119
|
+
withExecutionEngine(engine: ExecutionEngine): this {
|
|
120
|
+
this._executionEngine = engine;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the execution engine
|
|
126
|
+
*/
|
|
127
|
+
getExecutionEngine(): ExecutionEngine {
|
|
128
|
+
return this._executionEngine;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run the agent with input
|
|
133
|
+
*/
|
|
134
|
+
async run(config: AgentRunConfig<TInput>): Promise<TOutput> {
|
|
135
|
+
// Validate input
|
|
136
|
+
const validatedInput = this.config.inputSchema.parse(config.input);
|
|
137
|
+
|
|
138
|
+
// Create agent instance
|
|
139
|
+
const agent = new TypedAgent({
|
|
140
|
+
name: this.config.name,
|
|
141
|
+
description: this.config.description,
|
|
142
|
+
maxIterations: this.config.maxIterations,
|
|
143
|
+
timeoutMs: this.config.timeoutMs,
|
|
144
|
+
}, this.config.outputSchema);
|
|
145
|
+
|
|
146
|
+
// Create context
|
|
147
|
+
const { AgentContextBuilder } = await import('../core/context-builder.js');
|
|
148
|
+
const context = new AgentContextBuilder()
|
|
149
|
+
.withAgentId(agent.id)
|
|
150
|
+
.withMemory(this.memoryStore)
|
|
151
|
+
.withTools(this.toolRegistry)
|
|
152
|
+
.withPlanner(this.plannerInstance)
|
|
153
|
+
.withMetadataEntries(config.metadata ?? {})
|
|
154
|
+
.build();
|
|
155
|
+
|
|
156
|
+
// Run agent
|
|
157
|
+
const output = await agent.run(
|
|
158
|
+
{
|
|
159
|
+
prompt: JSON.stringify(validatedInput),
|
|
160
|
+
context: config.context,
|
|
161
|
+
},
|
|
162
|
+
context
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Parse and validate output
|
|
166
|
+
return this.config.outputSchema.parse(output.result);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Create a plan for a goal
|
|
171
|
+
*/
|
|
172
|
+
async plan(goal: string): Promise<import('../planner/types.js').Plan> {
|
|
173
|
+
return this.plannerInstance.plan(goal, {
|
|
174
|
+
availableTools: this.toolRegistry.list().map(t => t.name),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get agent configuration
|
|
180
|
+
*/
|
|
181
|
+
getConfig(): AgentDefinitionConfig<TInput, TOutput> {
|
|
182
|
+
return { ...this.config };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Typed agent implementation
|
|
188
|
+
*/
|
|
189
|
+
class TypedAgent<TOutput> extends BaseAgent {
|
|
190
|
+
constructor(
|
|
191
|
+
config: AgentConfig,
|
|
192
|
+
_outputSchema: z.ZodType<TOutput>
|
|
193
|
+
) {
|
|
194
|
+
super(config);
|
|
195
|
+
// _outputSchema is available for potential future validation use
|
|
196
|
+
void _outputSchema;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
protected async execute(input: AgentInput, _ctx: AgentContext): Promise<unknown> {
|
|
200
|
+
// Get the output schema from the config to validate result
|
|
201
|
+
// This is a demo implementation that generates a mock research result
|
|
202
|
+
const parsedInput = JSON.parse(input.prompt);
|
|
203
|
+
|
|
204
|
+
// In a real implementation, this would:
|
|
205
|
+
// 1. Use the planner to create a plan
|
|
206
|
+
// 2. Execute tasks using available tools
|
|
207
|
+
// 3. Process results and generate output
|
|
208
|
+
|
|
209
|
+
// For demo purposes, return a mock result that matches ResearchOutput schema
|
|
210
|
+
return {
|
|
211
|
+
summary: `Research on "${parsedInput.topic}" completed. This is a demo result showing the agent framework is working correctly.`,
|
|
212
|
+
sources: [
|
|
213
|
+
'https://example.com/source1',
|
|
214
|
+
'https://example.com/source2',
|
|
215
|
+
'https://example.com/source3',
|
|
216
|
+
],
|
|
217
|
+
confidence: 0.85,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create a workflow builder
|
|
224
|
+
*/
|
|
225
|
+
export function createWorkflow(): WorkflowBuilder {
|
|
226
|
+
return new WorkflowBuilder();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Workflow builder for chaining agents
|
|
231
|
+
*/
|
|
232
|
+
export class WorkflowBuilder {
|
|
233
|
+
private steps: WorkflowStep[] = [];
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Add a task to the workflow
|
|
237
|
+
*/
|
|
238
|
+
task(name: string, agent: DefinedAgent<unknown, unknown>): this {
|
|
239
|
+
this.steps.push({ type: 'task', name, agent });
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Add parallel execution
|
|
245
|
+
*/
|
|
246
|
+
parallel(): this {
|
|
247
|
+
this.steps.push({ type: 'parallel' });
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Add sequential execution
|
|
253
|
+
*/
|
|
254
|
+
sequential(): this {
|
|
255
|
+
this.steps.push({ type: 'sequential' });
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Add dependency
|
|
261
|
+
*/
|
|
262
|
+
dependsOn(...taskNames: string[]): this {
|
|
263
|
+
const lastStep = this.steps[this.steps.length - 1];
|
|
264
|
+
if (lastStep && lastStep.type === 'task') {
|
|
265
|
+
lastStep.dependencies = taskNames;
|
|
266
|
+
}
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Build the workflow
|
|
272
|
+
*/
|
|
273
|
+
build(): Workflow {
|
|
274
|
+
return new Workflow(this.steps);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Execute the workflow
|
|
279
|
+
*/
|
|
280
|
+
async execute(context?: Record<string, unknown>): Promise<WorkflowResult> {
|
|
281
|
+
const workflow = this.build();
|
|
282
|
+
return workflow.execute(context);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Workflow step definition
|
|
288
|
+
*/
|
|
289
|
+
interface WorkflowStep {
|
|
290
|
+
type: 'task' | 'parallel' | 'sequential';
|
|
291
|
+
name?: string;
|
|
292
|
+
agent?: DefinedAgent<unknown, unknown>;
|
|
293
|
+
dependencies?: string[];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Workflow class
|
|
298
|
+
*/
|
|
299
|
+
export class Workflow {
|
|
300
|
+
private steps: WorkflowStep[];
|
|
301
|
+
|
|
302
|
+
constructor(steps: WorkflowStep[]) {
|
|
303
|
+
this.steps = steps;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Execute the workflow
|
|
308
|
+
*/
|
|
309
|
+
async execute(context?: Record<string, unknown>): Promise<WorkflowResult> {
|
|
310
|
+
const results: Record<string, unknown> = {};
|
|
311
|
+
|
|
312
|
+
for (const step of this.steps) {
|
|
313
|
+
if (step.type === 'task' && step.agent && step.name) {
|
|
314
|
+
const result = await step.agent.run({
|
|
315
|
+
input: context ?? {},
|
|
316
|
+
context,
|
|
317
|
+
});
|
|
318
|
+
results[step.name] = result;
|
|
319
|
+
}
|
|
320
|
+
// TODO: Implement parallel/sequential execution
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { results };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Workflow execution result
|
|
329
|
+
*/
|
|
330
|
+
export interface WorkflowResult {
|
|
331
|
+
results: Record<string, unknown>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Wrap a DefinedAgent as a core Agent for use with the Orchestrator (supervisor, pipeline).
|
|
336
|
+
*/
|
|
337
|
+
export function asOrchestratorAgent(definedAgent: DefinedAgent<unknown, unknown>): Agent {
|
|
338
|
+
return new DefinedAgentAdapter(definedAgent);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
class DefinedAgentAdapter extends Agent {
|
|
342
|
+
constructor(private readonly definedAgent: DefinedAgent<unknown, unknown>) {
|
|
343
|
+
super({
|
|
344
|
+
name: definedAgent.getConfig().name,
|
|
345
|
+
description: definedAgent.getConfig().description ?? '',
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async run(input: AgentInput, _ctx: AgentContext): Promise<AgentOutput> {
|
|
350
|
+
const parsedInput =
|
|
351
|
+
typeof input.prompt === 'string' && (input.prompt.startsWith('{') || input.prompt.startsWith('['))
|
|
352
|
+
? (JSON.parse(input.prompt) as unknown)
|
|
353
|
+
: input.prompt;
|
|
354
|
+
const result = await this.definedAgent.run({
|
|
355
|
+
input: parsedInput,
|
|
356
|
+
context: input.context ?? _ctx.metadata,
|
|
357
|
+
});
|
|
358
|
+
return {
|
|
359
|
+
result,
|
|
360
|
+
state: AgentState.COMPLETED,
|
|
361
|
+
metadata: {
|
|
362
|
+
startTime: new Date(),
|
|
363
|
+
iterations: 0,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Re-export core types for convenience
|
|
370
|
+
export * from '../core/types.js';
|
|
371
|
+
export * from '../memory/types.js';
|
|
372
|
+
export * from '../tools/types.js';
|
|
373
|
+
export * from '../planner/types.js';
|
|
374
|
+
export * from '../execution/types.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database driver interface for session persistence.
|
|
3
|
+
* Implement for SQLite, PostgreSQL, or any SQL-compatible store.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Minimal SQL driver: run queries and execute statements.
|
|
8
|
+
* Plug any DB by implementing this interface.
|
|
9
|
+
*/
|
|
10
|
+
export interface SessionDbDriver {
|
|
11
|
+
/** Run a SELECT and return rows */
|
|
12
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
13
|
+
|
|
14
|
+
/** Run INSERT/UPDATE/DELETE; no return value */
|
|
15
|
+
run(sql: string, params?: unknown[]): Promise<void>;
|
|
16
|
+
|
|
17
|
+
/** Run DDL (e.g. CREATE TABLE). Optional; use run() if not supported. */
|
|
18
|
+
exec?(sql: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Row shape for sessions table (driver-agnostic)
|
|
23
|
+
*/
|
|
24
|
+
export interface SessionRow {
|
|
25
|
+
id: string;
|
|
26
|
+
agent_id: string;
|
|
27
|
+
user_id: string | null;
|
|
28
|
+
state: string;
|
|
29
|
+
messages: string;
|
|
30
|
+
metadata: string;
|
|
31
|
+
context: string;
|
|
32
|
+
created_at: string;
|
|
33
|
+
updated_at: string;
|
|
34
|
+
expires_at: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Row shape for session_runs table
|
|
39
|
+
*/
|
|
40
|
+
export interface SessionRunRow {
|
|
41
|
+
id: string;
|
|
42
|
+
session_id: string;
|
|
43
|
+
agent_id: string;
|
|
44
|
+
start_time: string;
|
|
45
|
+
end_time: string | null;
|
|
46
|
+
status: string;
|
|
47
|
+
steps: number;
|
|
48
|
+
result: string | null;
|
|
49
|
+
error: string | null;
|
|
50
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory session store implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
SessionStore,
|
|
7
|
+
Session,
|
|
8
|
+
SessionRun,
|
|
9
|
+
SessionId,
|
|
10
|
+
SessionState,
|
|
11
|
+
SessionQuery,
|
|
12
|
+
SessionStoreConfig,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
import type { Message } from '../llm/types.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default configuration
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_CONFIG: Required<SessionStoreConfig> = {
|
|
20
|
+
defaultTtlMs: 24 * 60 * 60 * 1000, // 24 hours
|
|
21
|
+
maxSessionsPerAgent: 100,
|
|
22
|
+
maxMessagesPerSession: 1000,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* In-memory implementation of SessionStore
|
|
27
|
+
* Suitable for development and testing
|
|
28
|
+
*/
|
|
29
|
+
export class InMemorySessionStore implements SessionStore {
|
|
30
|
+
private sessions: Map<SessionId, Session> = new Map();
|
|
31
|
+
private runs: Map<string, SessionRun[]> = new Map();
|
|
32
|
+
private config: Required<SessionStoreConfig>;
|
|
33
|
+
|
|
34
|
+
constructor(config: SessionStoreConfig = {}) {
|
|
35
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async create(session: Omit<Session, 'id' | 'createdAt' | 'updatedAt'>): Promise<Session> {
|
|
39
|
+
const id = this.generateId();
|
|
40
|
+
const now = new Date();
|
|
41
|
+
|
|
42
|
+
const newSession: Session = {
|
|
43
|
+
...session,
|
|
44
|
+
id,
|
|
45
|
+
createdAt: now,
|
|
46
|
+
updatedAt: now,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.sessions.set(id, newSession);
|
|
50
|
+
this.runs.set(id, []);
|
|
51
|
+
|
|
52
|
+
// Enforce max sessions per agent
|
|
53
|
+
this.enforceMaxSessions(session.agentId);
|
|
54
|
+
|
|
55
|
+
return newSession;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async get(sessionId: SessionId): Promise<Session | null> {
|
|
59
|
+
const session = this.sessions.get(sessionId);
|
|
60
|
+
if (!session) return null;
|
|
61
|
+
|
|
62
|
+
// Check expiration
|
|
63
|
+
if (session.expiresAt && session.expiresAt < new Date()) {
|
|
64
|
+
await this.delete(sessionId);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return session;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async update(sessionId: SessionId, updates: Partial<Omit<Session, 'id' | 'createdAt'>>): Promise<Session> {
|
|
72
|
+
const existing = this.sessions.get(sessionId);
|
|
73
|
+
if (!existing) {
|
|
74
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const updated: Session = {
|
|
78
|
+
...existing,
|
|
79
|
+
...updates,
|
|
80
|
+
id: existing.id,
|
|
81
|
+
createdAt: existing.createdAt,
|
|
82
|
+
updatedAt: new Date(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.sessions.set(sessionId, updated);
|
|
86
|
+
return updated;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async delete(sessionId: SessionId): Promise<boolean> {
|
|
90
|
+
this.runs.delete(sessionId);
|
|
91
|
+
return this.sessions.delete(sessionId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async list(query?: SessionQuery): Promise<Session[]> {
|
|
95
|
+
let sessions = Array.from(this.sessions.values());
|
|
96
|
+
|
|
97
|
+
if (query?.agentId) {
|
|
98
|
+
sessions = sessions.filter(s => s.agentId === query.agentId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (query?.userId) {
|
|
102
|
+
sessions = sessions.filter(s => s.userId === query.userId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (query?.state) {
|
|
106
|
+
sessions = sessions.filter(s => s.state === query.state);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (query?.before) {
|
|
110
|
+
sessions = sessions.filter(s => s.createdAt < query.before!);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (query?.after) {
|
|
114
|
+
sessions = sessions.filter(s => s.createdAt > query.after!);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sort by updatedAt desc
|
|
118
|
+
sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
119
|
+
|
|
120
|
+
if (query?.limit) {
|
|
121
|
+
sessions = sessions.slice(0, query.limit);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return sessions;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async addMessage(sessionId: SessionId, message: Message): Promise<Session> {
|
|
128
|
+
const session = await this.get(sessionId);
|
|
129
|
+
if (!session) {
|
|
130
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const messages = [...session.messages, message];
|
|
134
|
+
|
|
135
|
+
// Enforce max messages
|
|
136
|
+
if (messages.length > this.config.maxMessagesPerSession) {
|
|
137
|
+
messages.shift(); // Remove oldest
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return this.update(sessionId, {
|
|
141
|
+
messages,
|
|
142
|
+
state: SessionState.ACTIVE,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async getMessages(sessionId: SessionId): Promise<Message[]> {
|
|
147
|
+
const session = await this.get(sessionId);
|
|
148
|
+
if (!session) {
|
|
149
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
150
|
+
}
|
|
151
|
+
return [...session.messages];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async clearMessages(sessionId: SessionId): Promise<Session> {
|
|
155
|
+
return this.update(sessionId, { messages: [] });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async setContext(sessionId: SessionId, key: string, value: unknown): Promise<Session> {
|
|
159
|
+
const session = await this.get(sessionId);
|
|
160
|
+
if (!session) {
|
|
161
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return this.update(sessionId, {
|
|
165
|
+
context: { ...session.context, [key]: value },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async getContext(sessionId: SessionId, key: string): Promise<unknown> {
|
|
170
|
+
const session = await this.get(sessionId);
|
|
171
|
+
if (!session) {
|
|
172
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
173
|
+
}
|
|
174
|
+
return session.context[key];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async recordRun(run: Omit<SessionRun, 'id'>): Promise<SessionRun> {
|
|
178
|
+
const session = await this.get(run.sessionId);
|
|
179
|
+
if (!session) {
|
|
180
|
+
throw new Error(`Session not found: ${run.sessionId}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const newRun: SessionRun = {
|
|
184
|
+
...run,
|
|
185
|
+
id: `run-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const runs = this.runs.get(run.sessionId) ?? [];
|
|
189
|
+
runs.push(newRun);
|
|
190
|
+
this.runs.set(run.sessionId, runs);
|
|
191
|
+
|
|
192
|
+
return newRun;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async getRuns(sessionId: SessionId): Promise<SessionRun[]> {
|
|
196
|
+
return [...(this.runs.get(sessionId) ?? [])];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async cleanup(): Promise<number> {
|
|
200
|
+
const now = new Date();
|
|
201
|
+
let cleaned = 0;
|
|
202
|
+
|
|
203
|
+
for (const [id, session] of this.sessions) {
|
|
204
|
+
if (session.expiresAt && session.expiresAt < now) {
|
|
205
|
+
await this.delete(id);
|
|
206
|
+
cleaned++;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return cleaned;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate a unique ID
|
|
215
|
+
*/
|
|
216
|
+
private generateId(): SessionId {
|
|
217
|
+
return `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Enforce max sessions per agent
|
|
222
|
+
*/
|
|
223
|
+
private enforceMaxSessions(agentId: string): void {
|
|
224
|
+
const agentSessions = Array.from(this.sessions.values())
|
|
225
|
+
.filter(s => s.agentId === agentId)
|
|
226
|
+
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
227
|
+
|
|
228
|
+
const excess = agentSessions.length - this.config.maxSessionsPerAgent;
|
|
229
|
+
if (excess > 0) {
|
|
230
|
+
for (let i = 0; i < excess; i++) {
|
|
231
|
+
this.delete(agentSessions[i].id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session management module
|
|
3
|
+
*
|
|
4
|
+
* Provides session state management for conversation and run state persistence.
|
|
5
|
+
* In-memory (dev) or DB-backed (SQLite, PostgreSQL via SessionDbDriver).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export { InMemorySessionStore } from './in-memory-store.js';
|
|
10
|
+
export type { SessionDbDriver, SessionRow, SessionRunRow } from './db-driver.js';
|
|
11
|
+
export { SqlSessionStore } from './sql-store.js';
|
|
12
|
+
export { createSqliteSessionStore } from './sqlite-store.js';
|