@strav/brain 0.4.30 → 1.0.0-alpha.10

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 (40) hide show
  1. package/package.json +17 -20
  2. package/src/agent.ts +50 -75
  3. package/src/agent_result.ts +32 -0
  4. package/src/agent_runner.ts +63 -0
  5. package/src/brain_config.ts +80 -0
  6. package/src/brain_error.ts +29 -0
  7. package/src/brain_manager.ts +186 -123
  8. package/src/brain_provider.ts +91 -6
  9. package/src/define_tool.ts +42 -0
  10. package/src/index.ts +43 -42
  11. package/src/mcp_server.ts +47 -0
  12. package/src/provider.ts +83 -0
  13. package/src/providers/anthropic_provider.ts +435 -232
  14. package/src/thread.ts +99 -0
  15. package/src/tool.ts +28 -44
  16. package/src/tool_execution_error.ts +26 -0
  17. package/src/types.ts +164 -237
  18. package/CHANGELOG.md +0 -44
  19. package/README.md +0 -121
  20. package/src/helpers.ts +0 -1082
  21. package/src/mcp_toolbox.ts +0 -62
  22. package/src/memory/context_budget.ts +0 -120
  23. package/src/memory/index.ts +0 -17
  24. package/src/memory/memory_manager.ts +0 -168
  25. package/src/memory/semantic_memory.ts +0 -89
  26. package/src/memory/strategies/sliding_window.ts +0 -20
  27. package/src/memory/strategies/summarize.ts +0 -157
  28. package/src/memory/thread_store.ts +0 -56
  29. package/src/memory/token_counter.ts +0 -101
  30. package/src/memory/types.ts +0 -68
  31. package/src/providers/google_provider.ts +0 -496
  32. package/src/providers/openai_provider.ts +0 -569
  33. package/src/providers/openai_responses_provider.ts +0 -321
  34. package/src/utils/error_scrub.ts +0 -5
  35. package/src/utils/prompt.ts +0 -65
  36. package/src/utils/retry.ts +0 -104
  37. package/src/utils/schema.ts +0 -27
  38. package/src/utils/sse_parser.ts +0 -62
  39. package/src/workflow.ts +0 -199
  40. package/tsconfig.json +0 -5
@@ -1,158 +1,221 @@
1
- import { inject, ConfigurationError, Configuration } from '@strav/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
- TranscribeRequest,
13
- TranscriptionResponse,
14
- } from './types.ts'
15
- import type { MemoryConfig, ThreadStore } from './memory/types.ts'
16
-
17
1
  /**
18
- * Central AI configuration hub.
2
+ * `BrainManager` the per-app facade apps inject and call.
19
3
  *
20
- * Resolved once via the DI container reads the AI config
21
- * and initializes the appropriate provider drivers.
4
+ * Holds the configured `Provider` registry + the default-provider key
5
+ * + the tier-to-model map. Apps call `chat / stream / countTokens`
6
+ * with framework-native types; the manager resolves which provider
7
+ * runs the call (default unless `options.provider` overrides),
8
+ * applies tier sugar (`options.tier` → concrete `model`), and
9
+ * delegates.
22
10
  *
23
- * @example
24
- * app.singleton(BrainManager)
25
- * app.resolve(BrainManager)
11
+ * Constructed by `BrainProvider` at boot from `config.brain`. Apps
12
+ * also build one inline for tests:
26
13
  *
27
- * // Plug in a custom provider
28
- * BrainManager.useProvider(new OllamaProvider())
14
+ * ```ts
15
+ * const brain = new BrainManager({
16
+ * default: 'anthropic',
17
+ * providers: { anthropic: stubProvider },
18
+ * })
19
+ * ```
29
20
  */
30
- @inject
31
- export default class BrainManager {
32
- private static _config: BrainConfig
33
- private static _providers = new Map<string, AIProvider>()
34
- private static _beforeHooks: BeforeHook[] = []
35
- private static _afterHooks: AfterHook[] = []
36
- private static _threadStore: ThreadStore | null = null
37
- private static _memoryConfig: MemoryConfig = {}
38
-
39
- constructor(config: Configuration) {
40
- BrainManager._config = {
41
- default: config.get('ai.default', 'anthropic') as string,
42
- providers: config.get('ai.providers', {}) as Record<string, ProviderConfig>,
43
- maxTokens: config.get('ai.maxTokens', 4096) as number,
44
- temperature: config.get('ai.temperature', 0.7) as number,
45
- maxIterations: config.get('ai.maxIterations', 10) as number,
46
- }
47
-
48
- BrainManager._memoryConfig = config.get('ai.memory', {}) as MemoryConfig
49
21
 
50
- for (const [name, providerConfig] of Object.entries(BrainManager._config.providers)) {
51
- BrainManager._providers.set(name, BrainManager.createProvider(name, providerConfig))
52
- }
53
- }
22
+ import type { Agent } from './agent.ts'
23
+ import type { AgentResult } from './agent_result.ts'
24
+ import type { MCPServer } from './mcp_server.ts'
25
+ import { AgentRunner } from './agent_runner.ts'
26
+ import { BrainError } from './brain_error.ts'
27
+ import type { ModelTier } from './types.ts'
28
+ import type {
29
+ ChatOptions,
30
+ ChatResult,
31
+ Message,
32
+ StreamEvent,
33
+ } from './types.ts'
34
+ import type { Provider, RunWithToolsOptions } from './provider.ts'
35
+ import type { Tool } from './tool.ts'
36
+ import { DEFAULT_TIERS } from './brain_config.ts'
37
+
38
+ /** Container-aware Agent constructor resolver — `BrainProvider` installs one wired to `app.resolve(...)`. */
39
+ export type AgentResolver = <A extends Agent>(cls: new (...args: never[]) => A) => A
40
+
41
+ export interface BrainManagerOptions {
42
+ /** Name of the default provider — must exist in `providers`. */
43
+ default: string
44
+ /** Provider registry keyed by name. */
45
+ providers: Record<string, Provider>
46
+ /** Tier-to-model overrides; merged on top of the framework defaults. */
47
+ tiers?: Partial<Record<ModelTier, string>>
48
+ /** Default for `ChatOptions.cache` when the call site doesn't pass one. */
49
+ defaultCache?: boolean
50
+ /**
51
+ * Default MCP servers used on every `runTools` call when the per-call
52
+ * options don't specify them. Per-call `mcpServers` replaces the
53
+ * default outright (no merge) — apps that want additive behavior
54
+ * concat at the call site.
55
+ */
56
+ defaultMcpServers?: readonly MCPServer[]
57
+ }
54
58
 
55
- private static createProvider(name: string, config: ProviderConfig): AIProvider {
56
- const driver = config.driver ?? name
57
- switch (driver) {
58
- case 'anthropic':
59
- return new AnthropicProvider(config)
60
- case 'openai':
61
- return new OpenAIProvider(config, name)
62
- default:
63
- throw new ConfigurationError(
64
- `Unknown AI provider driver: ${driver}. Use BrainManager.useProvider() for custom providers.`
65
- )
59
+ export class BrainManager {
60
+ readonly defaultProvider: string
61
+ private readonly providers: Map<string, Provider>
62
+ private readonly tiers: Record<ModelTier, string>
63
+ private readonly defaultCache: boolean
64
+ private readonly defaultMcpServers: readonly MCPServer[]
65
+
66
+ constructor(options: BrainManagerOptions) {
67
+ if (!options.providers[options.default]) {
68
+ throw new BrainError(
69
+ `BrainManager: default provider "${options.default}" is not registered.`,
70
+ { context: { default: options.default, available: Object.keys(options.providers) } },
71
+ )
66
72
  }
73
+ this.defaultProvider = options.default
74
+ this.providers = new Map(Object.entries(options.providers))
75
+ this.tiers = { ...DEFAULT_TIERS, ...(options.tiers ?? {}) }
76
+ this.defaultCache = options.defaultCache ?? false
77
+ this.defaultMcpServers = options.defaultMcpServers ?? []
67
78
  }
68
79
 
69
- static get config(): BrainConfig {
70
- if (!BrainManager._config) {
71
- throw new ConfigurationError(
72
- 'BrainManager not configured. Resolve it through the container first.'
73
- )
80
+ /** Resolve a provider by name. Default when no name passed. Throws when unknown. */
81
+ provider(name?: string): Provider {
82
+ const key = name ?? this.defaultProvider
83
+ const provider = this.providers.get(key)
84
+ if (!provider) {
85
+ throw new BrainError(`BrainManager: no provider registered under "${key}".`, {
86
+ context: { requested: key, available: [...this.providers.keys()] },
87
+ })
74
88
  }
75
- return BrainManager._config
89
+ return provider
76
90
  }
77
91
 
78
- /** Get a provider by name, or the default provider. */
79
- static provider(name?: string): AIProvider {
80
- const key = name ?? BrainManager._config.default
81
- const p = BrainManager._providers.get(key)
82
- if (!p) throw new ConfigurationError(`AI provider "${key}" not configured.`)
83
- return p
92
+ /**
93
+ * One-shot chat: send the messages, await the full reply.
94
+ *
95
+ * Accepts either a bare prompt string (treated as a single
96
+ * user-role message) or a typed `Message[]` for multi-turn /
97
+ * pre-built conversations.
98
+ */
99
+ async chat(input: string | readonly Message[], options: ChatOptions = {}): Promise<ChatResult> {
100
+ const messages = normalizeInput(input)
101
+ const resolved = this.applyDefaults(options)
102
+ return this.provider(options.provider).chat(messages, resolved)
84
103
  }
85
104
 
86
- /** Swap or add a provider at runtime (e.g., for testing or a custom provider). */
87
- static useProvider(provider: AIProvider): void {
88
- BrainManager._providers.set(provider.name, provider)
105
+ /**
106
+ * Stream the reply. Yields a `text` event per delta and a single
107
+ * terminal `stop` event with usage + stop-reason. Apps that want
108
+ * just the final message use `chat()` instead — this surface is
109
+ * for UI streaming.
110
+ */
111
+ stream(
112
+ input: string | readonly Message[],
113
+ options: ChatOptions = {},
114
+ ): AsyncIterable<StreamEvent> {
115
+ const messages = normalizeInput(input)
116
+ const resolved = this.applyDefaults(options)
117
+ return this.provider(options.provider).stream(messages, resolved)
89
118
  }
90
119
 
91
- /** Get the configured memory settings. */
92
- static get memoryConfig(): MemoryConfig {
93
- return BrainManager._memoryConfig
120
+ /**
121
+ * Count input tokens for the given messages + options. Returns
122
+ * `null` when the configured provider doesn't expose a token count
123
+ * helper (no `countTokens` method) — apps can fall back to a local
124
+ * estimator at the call site.
125
+ */
126
+ async countTokens(
127
+ input: string | readonly Message[],
128
+ options: ChatOptions = {},
129
+ ): Promise<number | null> {
130
+ const provider = this.provider(options.provider)
131
+ if (!provider.countTokens) return null
132
+ const messages = normalizeInput(input)
133
+ const resolved = this.applyDefaults(options)
134
+ return provider.countTokens(messages, resolved)
94
135
  }
95
136
 
96
- /** Get the registered thread store, if any. */
97
- static get threadStore(): ThreadStore | null {
98
- return BrainManager._threadStore
137
+ /**
138
+ * Run an agentic loop: send `messages` + `tools` to the model;
139
+ * execute any tool the model calls; loop until the model returns
140
+ * a terminal `stop_reason` (`'end_turn'`) or `maxIterations` is hit.
141
+ *
142
+ * Throws `BrainError` when the configured provider doesn't
143
+ * implement `runWithTools` (V1: OpenAI / Gemini / DeepSeek providers
144
+ * don't yet — only `AnthropicProvider`).
145
+ */
146
+ async runTools(
147
+ input: string | readonly Message[],
148
+ tools: readonly Tool[],
149
+ options: RunWithToolsOptions = {},
150
+ ): Promise<AgentResult> {
151
+ const provider = this.provider(options.provider)
152
+ if (!provider.runWithTools) {
153
+ throw new BrainError(
154
+ `BrainManager.runTools: provider "${provider.name}" does not implement runWithTools. Use a provider that supports tool use (V1: Anthropic).`,
155
+ { context: { provider: provider.name } },
156
+ )
157
+ }
158
+ const messages = normalizeInput(input)
159
+ const resolved = this.applyDefaults(options) as RunWithToolsOptions
160
+ // MCP defaults — per-call override (when present) replaces the
161
+ // configured list outright; apps that want concat behavior
162
+ // construct the merged array themselves and pass it in.
163
+ if (resolved.mcpServers === undefined && this.defaultMcpServers.length > 0) {
164
+ resolved.mcpServers = this.defaultMcpServers
165
+ }
166
+ return provider.runWithTools(messages, tools, resolved)
99
167
  }
100
168
 
101
- /** Register a thread store for persistence (e.g., DatabaseThreadStore). */
102
- static useThreadStore(store: ThreadStore): void {
103
- BrainManager._threadStore = store
169
+ /**
170
+ * Resolve an `Agent` subclass from the container and return an
171
+ * `AgentRunner` ready to receive `input(...)` and `run()`. Apps
172
+ * `@inject()`-decorate their Agent subclass so constructor
173
+ * injection of dependencies (Repositories, services, etc.) flows
174
+ * through normally.
175
+ */
176
+ agent<A extends Agent>(AgentClass: new (...args: never[]) => A, instance?: A): AgentRunner {
177
+ const agent = instance ?? this.resolveAgent(AgentClass)
178
+ return new AgentRunner(this, agent)
104
179
  }
105
180
 
106
- /** Register a hook that runs before every completion. */
107
- static before(hook: BeforeHook): void {
108
- BrainManager._beforeHooks.push(hook)
109
- }
181
+ // ─── Internal ────────────────────────────────────────────────────────────
110
182
 
111
- /** Register a hook that runs after every completion. */
112
- static after(hook: AfterHook): void {
113
- BrainManager._afterHooks.push(hook)
183
+ private resolveAgent<A extends Agent>(AgentClass: new (...args: never[]) => A): A {
184
+ if (this.agentResolver) return this.agentResolver(AgentClass)
185
+ // Fallback: assume the Agent class is constructible without args.
186
+ // Apps that need DI on the agent register a resolver via
187
+ // `setAgentResolver` (BrainProvider wires this to the container).
188
+ return new (AgentClass as unknown as new () => A)()
114
189
  }
115
190
 
116
191
  /**
117
- * Run a completion through the named provider, with before/after hooks.
118
- * Used internally by AgentRunner and the `brain` helper.
192
+ * Internal `BrainProvider` calls this at boot to plug in the
193
+ * container's resolution function so `brain.agent(MyAgent)` runs
194
+ * `app.resolve(MyAgent)` under the hood. Apps that build a
195
+ * `BrainManager` by hand for tests can leave this unset and pass
196
+ * a pre-constructed agent to `brain.agent(_, instance)`.
119
197
  */
120
- static async complete(
121
- providerName: string | undefined,
122
- request: CompletionRequest
123
- ): Promise<CompletionResponse> {
124
- for (const hook of BrainManager._beforeHooks) await hook(request)
125
- const response = await BrainManager.provider(providerName).complete(request)
126
- for (const hook of BrainManager._afterHooks) await hook(request, response)
127
- return response
198
+ setAgentResolver(resolver: AgentResolver): void {
199
+ this.agentResolver = resolver
128
200
  }
129
201
 
130
- /**
131
- * Transcribe audio through the named provider. Throws a clear
132
- * ConfigurationError if the provider doesn't implement `transcribe()`
133
- * (Anthropic, at time of writing). Hooks are not invoked — they're
134
- * shaped for chat completions and don't carry an audio analogue.
135
- */
136
- static async transcribe(
137
- providerName: string | undefined,
138
- request: TranscribeRequest
139
- ): Promise<TranscriptionResponse> {
140
- const provider = BrainManager.provider(providerName)
141
- if (!provider.transcribe) {
142
- throw new ConfigurationError(
143
- `AI provider "${provider.name}" does not support transcribe(). ` +
144
- `Use the OpenAI or Google providers, or register a custom one via BrainManager.useProvider().`
145
- )
202
+ private agentResolver: AgentResolver | undefined
203
+
204
+ private applyDefaults(options: ChatOptions): ChatOptions {
205
+ const resolved: ChatOptions = { ...options }
206
+ if (resolved.model === undefined && resolved.tier !== undefined) {
207
+ resolved.model = this.tiers[resolved.tier]
146
208
  }
147
- return provider.transcribe(request)
209
+ if (resolved.cache === undefined && this.defaultCache) {
210
+ resolved.cache = true
211
+ }
212
+ return resolved
148
213
  }
214
+ }
149
215
 
150
- /** Clear all providers, hooks, and stores (for testing). */
151
- static reset(): void {
152
- BrainManager._providers.clear()
153
- BrainManager._beforeHooks = []
154
- BrainManager._afterHooks = []
155
- BrainManager._threadStore = null
156
- BrainManager._memoryConfig = {}
216
+ function normalizeInput(input: string | readonly Message[]): readonly Message[] {
217
+ if (typeof input === 'string') {
218
+ return [{ role: 'user', content: input }]
157
219
  }
220
+ return input
158
221
  }
@@ -1,16 +1,101 @@
1
- import { ServiceProvider } from '@strav/kernel'
2
- import type { Application } from '@strav/kernel'
3
- import BrainManager from './brain_manager.ts'
1
+ /**
2
+ * `BrainProvider` `ServiceProvider` that wires `BrainManager` into
3
+ * the container from `config.brain`.
4
+ *
5
+ * Reads the brain config at register time, instantiates every
6
+ * configured provider (today: just Anthropic), and binds a
7
+ * `BrainManager` singleton. Apps inject it the standard way:
8
+ *
9
+ * ```ts
10
+ * @inject()
11
+ * class GreetingService {
12
+ * constructor(private readonly brain: BrainManager) {}
13
+ *
14
+ * async greet(name: string): Promise<string> {
15
+ * const { text } = await this.brain.chat(`Greet ${name} warmly.`)
16
+ * return text
17
+ * }
18
+ * }
19
+ * ```
20
+ *
21
+ * Eager construction is on purpose — a missing API key or unknown
22
+ * driver should fail at boot, not at the first call. The `boot()`
23
+ * step resolves the manager so `ConfigError`s surface before any
24
+ * request hits.
25
+ */
4
26
 
5
- export default class BrainProvider extends ServiceProvider {
6
- readonly name = 'brain'
27
+ import { type Application, ConfigError, ConfigRepository, ServiceProvider } from '@strav/kernel'
28
+ import { BrainManager } from './brain_manager.ts'
29
+ import type { BrainConfigShape, ProviderConfig } from './brain_config.ts'
30
+ import { AnthropicProvider } from './providers/anthropic_provider.ts'
31
+ import type { Provider } from './provider.ts'
32
+
33
+ export class BrainProvider extends ServiceProvider {
34
+ override readonly name = 'brain'
7
35
  override readonly dependencies = ['config']
8
36
 
9
37
  override register(app: Application): void {
10
- app.singleton(BrainManager)
38
+ app.singleton(BrainManager, (c) => {
39
+ const config = c.resolve(ConfigRepository).get('brain') as BrainConfigShape | undefined
40
+ if (!config) {
41
+ throw new ConfigError(
42
+ 'BrainProvider: `config.brain` is missing. Add a `config/brain.ts` with at least `default` + `providers`.',
43
+ )
44
+ }
45
+ if (!config.providers || Object.keys(config.providers).length === 0) {
46
+ throw new ConfigError(
47
+ 'BrainProvider: `config.brain.providers` must have at least one entry.',
48
+ )
49
+ }
50
+ if (!config.providers[config.default]) {
51
+ throw new ConfigError(
52
+ `BrainProvider: default provider "${config.default}" is not declared in config.brain.providers.`,
53
+ )
54
+ }
55
+
56
+ const providers: Record<string, Provider> = {}
57
+ for (const [name, entry] of Object.entries(config.providers)) {
58
+ providers[name] = buildProvider(name, entry)
59
+ }
60
+
61
+ const options: ConstructorParameters<typeof BrainManager>[0] = {
62
+ default: config.default,
63
+ providers,
64
+ }
65
+ if (config.tiers !== undefined) options.tiers = config.tiers
66
+ if (config.cache?.auto !== undefined) options.defaultCache = config.cache.auto
67
+ if (config.mcpServers !== undefined) options.defaultMcpServers = config.mcpServers
68
+ const manager = new BrainManager(options)
69
+ // Plug in the container so `brain.agent(MyAgent)` resolves
70
+ // its constructor deps through `@inject()` like every other
71
+ // injected class. The variance widening at the boundary
72
+ // (`never[]` ↔ `any[]`) is purely a TS typing artifact — the
73
+ // container call is identical to a direct `c.resolve(MyAgent)`.
74
+ manager.setAgentResolver(<A>(cls: new (...args: never[]) => A) =>
75
+ c.resolve(cls as unknown as new (...args: unknown[]) => A),
76
+ )
77
+ return manager
78
+ })
11
79
  }
12
80
 
13
81
  override boot(app: Application): void {
82
+ // Force-resolve so config errors surface at boot, not on first call.
14
83
  app.resolve(BrainManager)
15
84
  }
16
85
  }
86
+
87
+ function buildProvider(name: string, config: ProviderConfig): Provider {
88
+ switch (config.driver) {
89
+ case 'anthropic':
90
+ if (!config.apiKey) {
91
+ throw new ConfigError(
92
+ `BrainProvider: anthropic provider "${name}" is missing apiKey. Source from env('ANTHROPIC_API_KEY').`,
93
+ )
94
+ }
95
+ return new AnthropicProvider(name, config)
96
+ default:
97
+ throw new ConfigError(
98
+ `BrainProvider: unknown driver for provider "${name}". Known drivers: anthropic.`,
99
+ )
100
+ }
101
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `defineTool({ name, description, inputSchema, execute })` — typed
3
+ * factory mirroring `defineWorkflow` / `defineMachine` / `defineDurable`.
4
+ *
5
+ * ```ts
6
+ * const getWeather = defineTool({
7
+ * name: 'get_weather',
8
+ * description: 'Get current weather for a location.',
9
+ * inputSchema: {
10
+ * type: 'object',
11
+ * properties: { city: { type: 'string' } },
12
+ * required: ['city'],
13
+ * },
14
+ * execute: async ({ city }: { city: string }, ctx) => {
15
+ * return weatherService.lookup(city, ctx.context.userId as string)
16
+ * },
17
+ * })
18
+ * ```
19
+ *
20
+ * The generic parameters are usually inferred from `execute`'s first
21
+ * arg + return type; apps that want explicit typing pass them.
22
+ */
23
+
24
+ import type { Tool, ToolContext } from './tool.ts'
25
+
26
+ export interface DefineToolSpec<TInput, TOutput> {
27
+ name: string
28
+ description: string
29
+ inputSchema: Record<string, unknown>
30
+ execute(input: TInput, ctx: ToolContext): Promise<TOutput>
31
+ }
32
+
33
+ export function defineTool<TInput = unknown, TOutput = unknown>(
34
+ spec: DefineToolSpec<TInput, TOutput>,
35
+ ): Tool<TInput, TOutput> {
36
+ return {
37
+ name: spec.name,
38
+ description: spec.description,
39
+ inputSchema: spec.inputSchema,
40
+ execute: spec.execute,
41
+ }
42
+ }
package/src/index.ts CHANGED
@@ -1,48 +1,49 @@
1
- export { default, default as BrainManager } from './brain_manager.ts'
1
+ // Public API of @strav/brain.
2
+ //
3
+ // V1: Provider interface + AnthropicProvider, BrainManager, Thread,
4
+ // BrainProvider service-wiring, prompt caching.
5
+ // V2 (this slice): tools + agents — defineTool, Agent base + AgentRunner,
6
+ // BrainManager.runTools / .agent(Class), Provider.runWithTools.
7
+ // Still deferred: MCP, embeddings, streaming agent loops, server-side
8
+ // tools, structured outputs, other providers.
2
9
 
3
- // Provider
4
- export { default as BrainProvider } from './brain_provider.ts'
5
- export { brain, AgentRunner, Thread } from './helpers.ts'
6
10
  export { Agent } from './agent.ts'
7
- export { defineTool, defineToolbox } from './tool.ts'
8
- export { defineMcpToolbox } from './mcp_toolbox.ts'
9
- export type { McpToolboxOptions } from './mcp_toolbox.ts'
10
- export { Workflow } from './workflow.ts'
11
+ export type { AgentResult } from './agent_result.ts'
12
+ export { AgentRunner } from './agent_runner.ts'
13
+ export {
14
+ type AnthropicProviderConfig,
15
+ type BrainCacheConfig,
16
+ type BrainConfigShape,
17
+ DEFAULT_MODEL,
18
+ DEFAULT_TIERS,
19
+ type ProviderConfig,
20
+ } from './brain_config.ts'
21
+ export { BrainError } from './brain_error.ts'
22
+ export {
23
+ type AgentResolver,
24
+ BrainManager,
25
+ type BrainManagerOptions,
26
+ } from './brain_manager.ts'
27
+ export { BrainProvider } from './brain_provider.ts'
28
+ export { defineTool, type DefineToolSpec } from './define_tool.ts'
29
+ export type { MCPServer, MCPServerToolConfig } from './mcp_server.ts'
11
30
  export { AnthropicProvider } from './providers/anthropic_provider.ts'
12
- export { GoogleProvider } from './providers/google_provider.ts'
13
- export { OpenAIProvider } from './providers/openai_provider.ts'
14
- export { OpenAIResponsesProvider } from './providers/openai_responses_provider.ts'
15
- export { parseSSE } from './utils/sse_parser.ts'
16
- export { zodToJsonSchema } from './utils/schema.ts'
31
+ export type { Provider, RunWithToolsOptions } from './provider.ts'
32
+ export { Thread, type ThreadOptions, type ThreadState } from './thread.ts'
33
+ export type { Tool, ToolContext } from './tool.ts'
34
+ export { ToolExecutionError } from './tool_execution_error.ts'
17
35
  export type {
18
- AIProvider,
19
- BrainConfig,
20
- ProviderConfig,
21
- CompletionRequest,
22
- CompletionResponse,
23
- Message,
36
+ ChatOptions,
37
+ ChatResult,
38
+ ChatUsage,
24
39
  ContentBlock,
25
- ToolCall,
26
- ToolDefinition,
27
- StreamChunk,
28
- Usage,
29
- AgentResult,
30
- ToolCallRecord,
31
- AgentEvent,
32
- WorkflowResult,
33
- EmbeddingResponse,
34
- JsonSchema,
35
- SSEEvent,
36
- BeforeHook,
37
- AfterHook,
38
- SerializedThread,
39
- SerializedAgentState,
40
- SuspendedRun,
41
- ToolCallResult,
42
- OutputSchema,
40
+ MCPToolResultBlock,
41
+ MCPToolUseBlock,
42
+ Message,
43
+ ModelTier,
44
+ StreamEvent,
45
+ SystemPrompt,
46
+ TextBlock,
47
+ ToolResultBlock,
48
+ ToolUseBlock,
43
49
  } from './types.ts'
44
- export type { ChatOptions, GenerateOptions, GenerateResult, EmbedOptions } from './helpers.ts'
45
- export type { WorkflowContext } from './workflow.ts'
46
-
47
- // Memory
48
- export * from './memory/index.ts'
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `MCPServer` — declarative configuration for a remote MCP server
3
+ * that Anthropic's backend invokes on behalf of the model.
4
+ *
5
+ * V1 leverages Anthropic's server-side MCP support: apps declare
6
+ * server URLs (with optional bearer auth) on the request; the
7
+ * backend connects to them, discovers their tools, surfaces them to
8
+ * the model, and runs the tool calls itself. The agentic loop here
9
+ * doesn't intercept MCP tool calls — they appear in the response as
10
+ * `MCPToolUseBlock` / `MCPToolResultBlock` content blocks for
11
+ * observability and audit-trail rendering.
12
+ *
13
+ * For OpenAI / Gemini / DeepSeek providers (later slices), a local
14
+ * MCP client implementation will live alongside this to translate
15
+ * MCP-discovered tools into `Tool` records and let the framework run
16
+ * the loop. The V1 contract stays the same; the per-provider
17
+ * implementation differs.
18
+ *
19
+ * `allowedTools` opts into a subset of the server's exposed tools —
20
+ * useful for narrowing surface area when the MCP server exposes more
21
+ * capabilities than the agent should be able to invoke. `enabled`
22
+ * defaults to `true`; set to `false` to declare the server without
23
+ * routing model calls to it (rare, but handy for temporary
24
+ * disablement without re-deploying config).
25
+ */
26
+
27
+ export interface MCPServerToolConfig {
28
+ /** Whitelist of tool names the agent can call. Omit for "all tools the server exposes." */
29
+ allowedTools?: readonly string[]
30
+ /** Default `true`. Set `false` to declare-but-disable. */
31
+ enabled?: boolean
32
+ }
33
+
34
+ export interface MCPServer {
35
+ /** Server identifier — used in MCPToolUseBlock.serverName + logging. */
36
+ name: string
37
+ /** HTTPS URL of the MCP server. */
38
+ url: string
39
+ /**
40
+ * Optional bearer token. Apps source from env vars / secrets
41
+ * managers — never hardcode. The framework forwards this verbatim
42
+ * to the provider's `authorization_token` field.
43
+ */
44
+ authorizationToken?: string
45
+ /** Per-server tool config (allowlist / enable flag). */
46
+ tools?: MCPServerToolConfig
47
+ }