@strav/brain 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/CHANGELOG.md +32 -0
- package/README.md +82 -0
- package/package.json +28 -0
- package/src/agent.ts +73 -0
- package/src/brain_manager.ts +136 -0
- package/src/brain_provider.ts +16 -0
- package/src/helpers.ts +903 -0
- package/src/index.ts +42 -0
- package/src/memory/context_budget.ts +120 -0
- package/src/memory/index.ts +17 -0
- package/src/memory/memory_manager.ts +168 -0
- package/src/memory/semantic_memory.ts +89 -0
- package/src/memory/strategies/sliding_window.ts +20 -0
- package/src/memory/strategies/summarize.ts +157 -0
- package/src/memory/thread_store.ts +56 -0
- package/src/memory/token_counter.ts +101 -0
- package/src/memory/types.ts +68 -0
- package/src/providers/anthropic_provider.ts +276 -0
- package/src/providers/openai_provider.ts +509 -0
- package/src/providers/openai_responses_provider.ts +319 -0
- package/src/tool.ts +50 -0
- package/src/types.ts +182 -0
- package/src/utils/retry.ts +100 -0
- package/src/utils/schema.ts +27 -0
- package/src/utils/sse_parser.ts +62 -0
- package/src/workflow.ts +180 -0
- package/tsconfig.json +5 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Memory management** — three-tier conversation memory system for long-running threads
|
|
8
|
+
- `thread.memory()` enables opt-in context window management
|
|
9
|
+
- **Working memory** — recent messages within token budget
|
|
10
|
+
- **Episodic memory** — LLM-generated summaries of compacted older messages
|
|
11
|
+
- **Semantic memory** — structured facts extracted from conversation, injected into system prompt
|
|
12
|
+
- `TokenCounter` — approximate token estimation per provider (~4 chars/token)
|
|
13
|
+
- `ContextBudget` — budget allocation across system prompt, summaries, facts, and working messages
|
|
14
|
+
- `MemoryManager` — orchestrates compaction and fact extraction
|
|
15
|
+
- `SemanticMemory` — in-memory fact store with `<known_facts>` prompt injection
|
|
16
|
+
- `SummarizeStrategy` — LLM-powered compaction with optional fact extraction
|
|
17
|
+
- `SlidingWindowStrategy` — drop oldest messages without summarization
|
|
18
|
+
- `InMemoryThreadStore` — default `ThreadStore` implementation for dev/testing
|
|
19
|
+
- `ThreadStore` interface — pluggable persistence (implement for database-backed storage)
|
|
20
|
+
- `BrainManager.useThreadStore()` — register a thread store for persistence
|
|
21
|
+
- `BrainManager.memoryConfig` / `BrainManager.threadStore` — accessors for memory configuration
|
|
22
|
+
- `thread.id()` — set thread identifier for persistence
|
|
23
|
+
- `thread.persist()` — enable auto-save to ThreadStore after each `send()`
|
|
24
|
+
- `thread.facts` / `thread.episodicSummary` — access memory state
|
|
25
|
+
- `thread.serializeMemory()` / `thread.restoreMemory()` — extended serialization with memory state
|
|
26
|
+
- `BrainConfig.memory` — optional `MemoryConfig` field for global memory settings
|
|
27
|
+
|
|
28
|
+
## 0.1.1
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Applied consistent code formatting across all source files
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @stravigor/brain
|
|
2
|
+
|
|
3
|
+
AI module for the [Strav](https://www.npmjs.com/package/@stravigor/core) framework. Provides a unified interface for AI providers with support for agents, threads, tool use, and multi-step workflows.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @stravigor/brain
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `@stravigor/core` as a peer dependency.
|
|
12
|
+
|
|
13
|
+
## Providers
|
|
14
|
+
|
|
15
|
+
- **Anthropic** (Claude)
|
|
16
|
+
- **OpenAI** (GPT, also works with DeepSeek via custom `baseUrl`)
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { brain } from '@stravigor/brain'
|
|
22
|
+
|
|
23
|
+
// One-shot chat
|
|
24
|
+
const response = await brain.chat('Explain quantum computing')
|
|
25
|
+
|
|
26
|
+
// Streaming
|
|
27
|
+
for await (const chunk of brain.stream('Write a poem')) {
|
|
28
|
+
process.stdout.write(chunk.text)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Structured output with Zod
|
|
32
|
+
import { z } from 'zod'
|
|
33
|
+
const result = await brain.generate('List 3 colors', {
|
|
34
|
+
schema: z.object({ colors: z.array(z.string()) }),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Embeddings
|
|
38
|
+
const vectors = await brain.embed('Hello world')
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Agents
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { Agent, defineTool } from '@stravigor/brain'
|
|
45
|
+
|
|
46
|
+
class ResearchAgent extends Agent {
|
|
47
|
+
provider = 'anthropic'
|
|
48
|
+
model = 'claude-sonnet-4-20250514'
|
|
49
|
+
instructions = 'You are a research assistant.'
|
|
50
|
+
tools = [searchTool, summarizeTool]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = await brain.agent(ResearchAgent).input('Find info on Bun').run()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Threads
|
|
57
|
+
|
|
58
|
+
Multi-turn conversations with serialization support:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const thread = brain.thread({ provider: 'anthropic', model: 'claude-sonnet-4-20250514' })
|
|
62
|
+
await thread.send('Hello')
|
|
63
|
+
await thread.send('Tell me more')
|
|
64
|
+
const saved = thread.serialize() // persist and restore later
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Workflows
|
|
68
|
+
|
|
69
|
+
Orchestrate multi-agent pipelines:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const workflow = brain.workflow()
|
|
73
|
+
.step('research', ResearchAgent)
|
|
74
|
+
.step('summarize', SummaryAgent)
|
|
75
|
+
.parallel('review', [FactCheckAgent, StyleAgent])
|
|
76
|
+
|
|
77
|
+
const result = await workflow.run('Analyze this topic')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@strav/brain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "AI module for the Strav framework",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./*": "./src/*.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"package.json",
|
|
14
|
+
"tsconfig.json",
|
|
15
|
+
"CHANGELOG.md"
|
|
16
|
+
],
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@strav/kernel": "0.1.0",
|
|
19
|
+
"@strav/workflow": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"zod": "^3.25 || ^4.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "bun test tests/",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ToolDefinition,
|
|
3
|
+
ToolCall,
|
|
4
|
+
ToolCallRecord,
|
|
5
|
+
AgentResult,
|
|
6
|
+
OutputSchema,
|
|
7
|
+
} from './types.ts'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base class for AI agents.
|
|
11
|
+
*
|
|
12
|
+
* Extend this class to define an agent with custom instructions,
|
|
13
|
+
* tools, structured output, and lifecycle hooks.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* class SupportAgent extends Agent {
|
|
17
|
+
* provider = 'anthropic'
|
|
18
|
+
* model = 'claude-sonnet-4-5-20250929'
|
|
19
|
+
* instructions = 'You are a customer support agent.'
|
|
20
|
+
* tools = [searchTool, lookupOrderTool]
|
|
21
|
+
*
|
|
22
|
+
* output = z.object({
|
|
23
|
+
* reply: z.string(),
|
|
24
|
+
* category: z.enum(['billing', 'shipping', 'product', 'other']),
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* onToolCall(call: ToolCall) {
|
|
28
|
+
* console.log(`Calling tool: ${call.name}`)
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
export abstract class Agent {
|
|
33
|
+
/** Provider name (e.g., 'anthropic', 'openai'). Falls back to config default. */
|
|
34
|
+
provider?: string
|
|
35
|
+
|
|
36
|
+
/** Model identifier. Falls back to the provider's configured default model. */
|
|
37
|
+
model?: string
|
|
38
|
+
|
|
39
|
+
/** System prompt / instructions for this agent. Supports `{{key}}` context interpolation. */
|
|
40
|
+
instructions: string = ''
|
|
41
|
+
|
|
42
|
+
/** Tools available to this agent during execution. */
|
|
43
|
+
tools?: ToolDefinition[]
|
|
44
|
+
|
|
45
|
+
/** Structured output schema (Zod or JSON Schema). When set, the final response is parsed and validated. */
|
|
46
|
+
output?: OutputSchema
|
|
47
|
+
|
|
48
|
+
/** Maximum tool-use loop iterations before forcing a stop. Falls back to config default (10). */
|
|
49
|
+
maxIterations?: number
|
|
50
|
+
|
|
51
|
+
/** Maximum tokens per completion request. Falls back to config default (4096). */
|
|
52
|
+
maxTokens?: number
|
|
53
|
+
|
|
54
|
+
/** Temperature for completion requests. Falls back to config default (0.7). */
|
|
55
|
+
temperature?: number
|
|
56
|
+
|
|
57
|
+
// ── Lifecycle hooks (optional) ───────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** Called before the first completion request. */
|
|
60
|
+
onStart?(input: string, context: Record<string, unknown>): void | Promise<void>
|
|
61
|
+
|
|
62
|
+
/** Called when the model requests a tool call, before execution. */
|
|
63
|
+
onToolCall?(call: ToolCall): void | Promise<void>
|
|
64
|
+
|
|
65
|
+
/** Called after a tool finishes execution. */
|
|
66
|
+
onToolResult?(call: ToolCallRecord): void | Promise<void>
|
|
67
|
+
|
|
68
|
+
/** Called when the agent run completes successfully. */
|
|
69
|
+
onComplete?(result: AgentResult): void | Promise<void>
|
|
70
|
+
|
|
71
|
+
/** Called when the agent run encounters an error. */
|
|
72
|
+
onError?(error: Error): void | Promise<void>
|
|
73
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { inject, ConfigurationError, Configuration } from '@stravigor/kernel'
|
|
2
|
+
import { AnthropicProvider } from './providers/anthropic_provider.ts'
|
|
3
|
+
import { OpenAIProvider } from './providers/openai_provider.ts'
|
|
4
|
+
import type {
|
|
5
|
+
AIProvider,
|
|
6
|
+
BrainConfig,
|
|
7
|
+
ProviderConfig,
|
|
8
|
+
CompletionRequest,
|
|
9
|
+
CompletionResponse,
|
|
10
|
+
BeforeHook,
|
|
11
|
+
AfterHook,
|
|
12
|
+
} from './types.ts'
|
|
13
|
+
import type { MemoryConfig, ThreadStore } from './memory/types.ts'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Central AI configuration hub.
|
|
17
|
+
*
|
|
18
|
+
* Resolved once via the DI container — reads the AI config
|
|
19
|
+
* and initializes the appropriate provider drivers.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* app.singleton(BrainManager)
|
|
23
|
+
* app.resolve(BrainManager)
|
|
24
|
+
*
|
|
25
|
+
* // Plug in a custom provider
|
|
26
|
+
* BrainManager.useProvider(new OllamaProvider())
|
|
27
|
+
*/
|
|
28
|
+
@inject
|
|
29
|
+
export default class BrainManager {
|
|
30
|
+
private static _config: BrainConfig
|
|
31
|
+
private static _providers = new Map<string, AIProvider>()
|
|
32
|
+
private static _beforeHooks: BeforeHook[] = []
|
|
33
|
+
private static _afterHooks: AfterHook[] = []
|
|
34
|
+
private static _threadStore: ThreadStore | null = null
|
|
35
|
+
private static _memoryConfig: MemoryConfig = {}
|
|
36
|
+
|
|
37
|
+
constructor(config: Configuration) {
|
|
38
|
+
BrainManager._config = {
|
|
39
|
+
default: config.get('ai.default', 'anthropic') as string,
|
|
40
|
+
providers: config.get('ai.providers', {}) as Record<string, ProviderConfig>,
|
|
41
|
+
maxTokens: config.get('ai.maxTokens', 4096) as number,
|
|
42
|
+
temperature: config.get('ai.temperature', 0.7) as number,
|
|
43
|
+
maxIterations: config.get('ai.maxIterations', 10) as number,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
BrainManager._memoryConfig = config.get('ai.memory', {}) as MemoryConfig
|
|
47
|
+
|
|
48
|
+
for (const [name, providerConfig] of Object.entries(BrainManager._config.providers)) {
|
|
49
|
+
BrainManager._providers.set(name, BrainManager.createProvider(name, providerConfig))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private static createProvider(name: string, config: ProviderConfig): AIProvider {
|
|
54
|
+
const driver = config.driver ?? name
|
|
55
|
+
switch (driver) {
|
|
56
|
+
case 'anthropic':
|
|
57
|
+
return new AnthropicProvider(config)
|
|
58
|
+
case 'openai':
|
|
59
|
+
return new OpenAIProvider(config, name)
|
|
60
|
+
default:
|
|
61
|
+
throw new ConfigurationError(
|
|
62
|
+
`Unknown AI provider driver: ${driver}. Use BrainManager.useProvider() for custom providers.`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static get config(): BrainConfig {
|
|
68
|
+
if (!BrainManager._config) {
|
|
69
|
+
throw new ConfigurationError(
|
|
70
|
+
'BrainManager not configured. Resolve it through the container first.'
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
return BrainManager._config
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Get a provider by name, or the default provider. */
|
|
77
|
+
static provider(name?: string): AIProvider {
|
|
78
|
+
const key = name ?? BrainManager._config.default
|
|
79
|
+
const p = BrainManager._providers.get(key)
|
|
80
|
+
if (!p) throw new ConfigurationError(`AI provider "${key}" not configured.`)
|
|
81
|
+
return p
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Swap or add a provider at runtime (e.g., for testing or a custom provider). */
|
|
85
|
+
static useProvider(provider: AIProvider): void {
|
|
86
|
+
BrainManager._providers.set(provider.name, provider)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Get the configured memory settings. */
|
|
90
|
+
static get memoryConfig(): MemoryConfig {
|
|
91
|
+
return BrainManager._memoryConfig
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Get the registered thread store, if any. */
|
|
95
|
+
static get threadStore(): ThreadStore | null {
|
|
96
|
+
return BrainManager._threadStore
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Register a thread store for persistence (e.g., DatabaseThreadStore). */
|
|
100
|
+
static useThreadStore(store: ThreadStore): void {
|
|
101
|
+
BrainManager._threadStore = store
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Register a hook that runs before every completion. */
|
|
105
|
+
static before(hook: BeforeHook): void {
|
|
106
|
+
BrainManager._beforeHooks.push(hook)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Register a hook that runs after every completion. */
|
|
110
|
+
static after(hook: AfterHook): void {
|
|
111
|
+
BrainManager._afterHooks.push(hook)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Run a completion through the named provider, with before/after hooks.
|
|
116
|
+
* Used internally by AgentRunner and the `brain` helper.
|
|
117
|
+
*/
|
|
118
|
+
static async complete(
|
|
119
|
+
providerName: string | undefined,
|
|
120
|
+
request: CompletionRequest
|
|
121
|
+
): Promise<CompletionResponse> {
|
|
122
|
+
for (const hook of BrainManager._beforeHooks) await hook(request)
|
|
123
|
+
const response = await BrainManager.provider(providerName).complete(request)
|
|
124
|
+
for (const hook of BrainManager._afterHooks) await hook(request, response)
|
|
125
|
+
return response
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Clear all providers, hooks, and stores (for testing). */
|
|
129
|
+
static reset(): void {
|
|
130
|
+
BrainManager._providers.clear()
|
|
131
|
+
BrainManager._beforeHooks = []
|
|
132
|
+
BrainManager._afterHooks = []
|
|
133
|
+
BrainManager._threadStore = null
|
|
134
|
+
BrainManager._memoryConfig = {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ServiceProvider } from '@stravigor/kernel'
|
|
2
|
+
import type { Application } from '@stravigor/kernel'
|
|
3
|
+
import BrainManager from './brain_manager.ts'
|
|
4
|
+
|
|
5
|
+
export default class BrainProvider extends ServiceProvider {
|
|
6
|
+
readonly name = 'brain'
|
|
7
|
+
override readonly dependencies = ['config']
|
|
8
|
+
|
|
9
|
+
override register(app: Application): void {
|
|
10
|
+
app.singleton(BrainManager)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override boot(app: Application): void {
|
|
14
|
+
app.resolve(BrainManager)
|
|
15
|
+
}
|
|
16
|
+
}
|