@strav/brain 1.0.0-alpha.22 → 1.0.0-alpha.25
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/package.json +3 -3
- package/src/agent_runner.ts +1 -1
- package/src/{provider.ts → brain_driver.ts} +11 -10
- package/src/brain_error.ts +86 -10
- package/src/brain_manager.ts +30 -7
- package/src/brain_provider.ts +16 -16
- package/src/drivers/anthropic/anthropic_brain_driver.ts +641 -0
- package/src/drivers/anthropic/anthropic_helpers.ts +65 -0
- package/src/drivers/anthropic/anthropic_message_builder.ts +258 -0
- package/src/drivers/anthropic/anthropic_response_mapper.ts +123 -0
- package/src/drivers/anthropic/anthropic_tool_loop.ts +246 -0
- package/src/drivers/anthropic/index.ts +1 -0
- package/src/{providers/deepseek_provider.ts → drivers/deepseek/deepseek_brain_driver.ts} +10 -10
- package/src/drivers/deepseek/index.ts +1 -0
- package/src/{providers/gemini_provider.ts → drivers/gemini/gemini_brain_driver.ts} +21 -21
- package/src/drivers/gemini/index.ts +1 -0
- package/src/drivers/ollama/index.ts +1 -0
- package/src/{providers/ollama_provider.ts → drivers/ollama/ollama_brain_driver.ts} +5 -5
- package/src/drivers/openai/index.ts +1 -0
- package/src/{providers/openai_provider.ts → drivers/openai/openai_brain_driver.ts} +152 -591
- package/src/drivers/openai/openai_helpers.ts +58 -0
- package/src/drivers/openai/openai_message_builder.ts +187 -0
- package/src/drivers/openai/openai_response_mapper.ts +70 -0
- package/src/drivers/openai/openai_tool_dispatch.ts +127 -0
- package/src/drivers/openai/openai_tool_loop.ts +191 -0
- package/src/drivers/openai_compat/index.ts +1 -0
- package/src/{providers/openai_compat_provider.ts → drivers/openai_compat/openai_compat_brain_driver.ts} +16 -16
- package/src/drivers/openai_responses/index.ts +1 -0
- package/src/{providers/openai_responses_provider.ts → drivers/openai_responses/openai_responses_brain_driver.ts} +24 -24
- package/src/index.ts +18 -12
- package/src/mcp/pool.ts +1 -1
- package/src/persistence/brain_message.ts +1 -1
- package/src/persistence/brain_message_repository.ts +3 -11
- package/src/persistence/brain_suspended_run.ts +1 -1
- package/src/persistence/brain_suspended_run_repository.ts +2 -11
- package/src/persistence/brain_thread.ts +1 -1
- package/src/persistence/brain_thread_repository.ts +2 -11
- package/src/persistence/index.ts +1 -1
- package/src/tool_runner.ts +1 -1
- package/src/types.ts +2 -2
- package/src/providers/anthropic_provider.ts +0 -1194
- /package/src/persistence/{schema → schemas}/brain_message_schema.ts +0 -0
- /package/src/persistence/{schema → schemas}/brain_suspended_run_schema.ts +0 -0
- /package/src/persistence/{schema → schemas}/brain_thread_schema.ts +0 -0
- /package/src/persistence/{schema → schemas}/index.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/brain",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.25",
|
|
4
4
|
"description": "Strav AI module — unified Provider interface, BrainManager, threads, prompt caching, tools / agents / MCP. Anthropic + OpenAI providers; Gemini / DeepSeek follow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"@anthropic-ai/sdk": "^0.100.0",
|
|
26
26
|
"@google/genai": "^2.7.0",
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
-
"@strav/database": "1.0.0-alpha.
|
|
29
|
-
"@strav/kernel": "1.0.0-alpha.
|
|
28
|
+
"@strav/database": "1.0.0-alpha.25",
|
|
29
|
+
"@strav/kernel": "1.0.0-alpha.25",
|
|
30
30
|
"openai": "^6.0.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
package/src/agent_runner.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
2
|
+
* `BrainDriver` — the contract every brain backend implements.
|
|
3
3
|
*
|
|
4
|
-
* Each concrete
|
|
5
|
-
*
|
|
6
|
-
* shapes (`Message`, `ChatOptions`)
|
|
7
|
-
* then translates the response back
|
|
4
|
+
* Each concrete driver (`AnthropicBrainDriver`, `OpenAIBrainDriver`,
|
|
5
|
+
* `GeminiBrainDriver`, `DeepSeekBrainDriver`, …) wraps the vendor's
|
|
6
|
+
* SDK and translates the framework shapes (`Message`, `ChatOptions`)
|
|
7
|
+
* into the vendor's native request, then translates the response back
|
|
8
|
+
* into `ChatResult` / `StreamEvent`.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
* `BrainManager` facade. The interface is exported so
|
|
11
|
-
* to plug in a custom
|
|
12
|
-
* subclassing.
|
|
10
|
+
* Drivers are values, not classes the app instantiates by name — apps
|
|
11
|
+
* use them via the `BrainManager` facade. The interface is exported so
|
|
12
|
+
* apps that need to plug in a custom backend (e.g. a local Ollama
|
|
13
|
+
* variant, or a private gateway) can do so without subclassing.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import type { AgentGenerateResult } from './agent_generate_result.ts'
|
|
@@ -106,7 +107,7 @@ export type RunWithToolsOptionsWithSuspend = RunWithToolsOptions & {
|
|
|
106
107
|
shouldSuspend: NonNullable<RunWithToolsOptions['shouldSuspend']>
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
export interface
|
|
110
|
+
export interface BrainDriver {
|
|
110
111
|
/** Identifier — matches the `config.brain.providers` key. */
|
|
111
112
|
readonly name: string
|
|
112
113
|
|
package/src/brain_error.ts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `BrainError` — typed
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* `BrainError` hierarchy — typed wrappers for failures originating
|
|
3
|
+
* in the brain stack.
|
|
4
|
+
*
|
|
5
|
+
* Provider-native errors (e.g. `Anthropic.RateLimitError`) are
|
|
6
|
+
* preserved on `.cause` so apps can `instanceof`-check them when
|
|
7
|
+
* they need vendor-specific recovery; the framework wrapping gives
|
|
8
|
+
* a consistent `StravError` to render through the standard
|
|
7
9
|
* exception handler.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Subclasses ship in v1 for the boot / lookup / usage paths.
|
|
12
|
+
* Vendor-side runtime errors use `BrainProviderError` as the
|
|
13
|
+
* generic wrapper. Granular vendor classes (rate-limit, content
|
|
14
|
+
* filter, etc.) land when apps actually need to branch on them at
|
|
15
|
+
* the framework level — until then, `instanceof Anthropic.RateLimitError`
|
|
16
|
+
* on `.cause` is the call-site pattern.
|
|
17
|
+
*
|
|
18
|
+
* - `BrainConfigError` — boot-time misconfiguration (missing
|
|
19
|
+
* provider in `config.brain.providers`, default key absent).
|
|
20
|
+
*
|
|
21
|
+
* - `UnknownProviderError` — `brain.provider(name)` for a name
|
|
22
|
+
* that wasn't registered.
|
|
23
|
+
*
|
|
24
|
+
* - `BrainUsageError` — pre-condition violations from the
|
|
25
|
+
* framework's own API contract (e.g. `AgentRunner.run` called
|
|
26
|
+
* before `input()`).
|
|
27
|
+
*
|
|
28
|
+
* - `BrainProviderError` — wraps a vendor exception. `cause` is
|
|
29
|
+
* preserved; default status 502.
|
|
14
30
|
*/
|
|
15
31
|
|
|
16
32
|
import { StravError } from '@strav/kernel'
|
|
@@ -27,3 +43,63 @@ export class BrainError extends StravError {
|
|
|
27
43
|
)
|
|
28
44
|
}
|
|
29
45
|
}
|
|
46
|
+
|
|
47
|
+
export class BrainConfigError extends BrainError {
|
|
48
|
+
constructor(
|
|
49
|
+
message: string,
|
|
50
|
+
options: { context?: Record<string, unknown> } = {},
|
|
51
|
+
) {
|
|
52
|
+
super(message, options)
|
|
53
|
+
// Reassign code/status via the underlying StravError props (the
|
|
54
|
+
// base constructor froze them with `brain.error`); we read them
|
|
55
|
+
// back through getters so subclass-specific overrides surface in
|
|
56
|
+
// logs.
|
|
57
|
+
Object.defineProperty(this, 'code', { value: 'brain.config' })
|
|
58
|
+
Object.defineProperty(this, 'status', { value: 500 })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class UnknownProviderError extends BrainError {
|
|
63
|
+
constructor(name: string, available: readonly string[]) {
|
|
64
|
+
super(
|
|
65
|
+
`Brain provider "${name}" is not registered. Available: ${available.join(', ') || '<none>'}.`,
|
|
66
|
+
{ context: { requested: name, available } },
|
|
67
|
+
)
|
|
68
|
+
Object.defineProperty(this, 'code', { value: 'brain.unknown_provider' })
|
|
69
|
+
Object.defineProperty(this, 'status', { value: 400 })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class BrainUsageError extends BrainError {
|
|
74
|
+
constructor(
|
|
75
|
+
message: string,
|
|
76
|
+
options: { context?: Record<string, unknown> } = {},
|
|
77
|
+
) {
|
|
78
|
+
super(message, options)
|
|
79
|
+
Object.defineProperty(this, 'code', { value: 'brain.usage' })
|
|
80
|
+
Object.defineProperty(this, 'status', { value: 500 })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class BrainProviderError extends BrainError {
|
|
85
|
+
constructor(
|
|
86
|
+
message: string,
|
|
87
|
+
options: {
|
|
88
|
+
provider: string
|
|
89
|
+
operation: string
|
|
90
|
+
context?: Record<string, unknown>
|
|
91
|
+
cause?: unknown
|
|
92
|
+
},
|
|
93
|
+
) {
|
|
94
|
+
super(message, {
|
|
95
|
+
context: {
|
|
96
|
+
provider: options.provider,
|
|
97
|
+
operation: options.operation,
|
|
98
|
+
...(options.context ?? {}),
|
|
99
|
+
},
|
|
100
|
+
...(options.cause !== undefined ? { cause: options.cause } : {}),
|
|
101
|
+
})
|
|
102
|
+
Object.defineProperty(this, 'code', { value: 'brain.provider_error' })
|
|
103
|
+
Object.defineProperty(this, 'status', { value: 502 })
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/brain_manager.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `BrainManager` — the per-app facade apps inject and call.
|
|
3
3
|
*
|
|
4
|
-
* Holds the configured `
|
|
4
|
+
* Holds the configured `BrainDriver` registry + the default-provider key
|
|
5
5
|
* + the tier-to-model map. Apps call `chat / stream / countTokens`
|
|
6
6
|
* with framework-native types; the manager resolves which provider
|
|
7
7
|
* runs the call (default unless `options.provider` overrides),
|
|
@@ -41,10 +41,10 @@ import type {
|
|
|
41
41
|
TranscribeResult,
|
|
42
42
|
} from './types.ts'
|
|
43
43
|
import type {
|
|
44
|
-
|
|
44
|
+
BrainDriver,
|
|
45
45
|
RunWithToolsOptions,
|
|
46
46
|
RunWithToolsOptionsWithSuspend,
|
|
47
|
-
} from './
|
|
47
|
+
} from './brain_driver.ts'
|
|
48
48
|
import { appendResumeResults, type SuspendedRun, type SuspendedState, type ToolResultInput } from './suspended_run.ts'
|
|
49
49
|
import type { Tool } from './tool.ts'
|
|
50
50
|
import { DEFAULT_TIERS } from './brain_config.ts'
|
|
@@ -56,7 +56,7 @@ export interface BrainManagerOptions {
|
|
|
56
56
|
/** Name of the default provider — must exist in `providers`. */
|
|
57
57
|
default: string
|
|
58
58
|
/** Provider registry keyed by name. */
|
|
59
|
-
providers: Record<string,
|
|
59
|
+
providers: Record<string, BrainDriver>
|
|
60
60
|
/** Tier-to-model overrides; merged on top of the framework defaults. */
|
|
61
61
|
tiers?: Partial<Record<ModelTier, string>>
|
|
62
62
|
/** Default for `ChatOptions.cache` when the call site doesn't pass one. */
|
|
@@ -72,7 +72,7 @@ export interface BrainManagerOptions {
|
|
|
72
72
|
|
|
73
73
|
export class BrainManager {
|
|
74
74
|
readonly defaultProvider: string
|
|
75
|
-
private readonly providers: Map<string,
|
|
75
|
+
private readonly providers: Map<string, BrainDriver>
|
|
76
76
|
private readonly tiers: Record<ModelTier, string>
|
|
77
77
|
private readonly defaultCache: boolean
|
|
78
78
|
private readonly defaultMcpServers: readonly MCPServer[]
|
|
@@ -92,7 +92,7 @@ export class BrainManager {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** Resolve a provider by name. Default when no name passed. Throws when unknown. */
|
|
95
|
-
provider(name?: string):
|
|
95
|
+
provider(name?: string): BrainDriver {
|
|
96
96
|
const key = name ?? this.defaultProvider
|
|
97
97
|
const provider = this.providers.get(key)
|
|
98
98
|
if (!provider) {
|
|
@@ -103,6 +103,29 @@ export class BrainManager {
|
|
|
103
103
|
return provider
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Register an additional provider after construction. Apps that
|
|
108
|
+
* wire a custom adapter (a fine-tuned model server, a fork of
|
|
109
|
+
* Anthropic with extra knobs, an internal LLM) use this to add it
|
|
110
|
+
* to the registry without going through `config.brain.providers`.
|
|
111
|
+
*
|
|
112
|
+
* Overwrites any existing provider under the same name.
|
|
113
|
+
*
|
|
114
|
+
* ```ts
|
|
115
|
+
* brain.extend('internal', new InternalLlmProvider({ baseUrl }))
|
|
116
|
+
* const reply = await brain.chat(messages, { provider: 'internal' })
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* Mirrors `RagManager.extend(name, factory)` / `PaymentManager.extend(name, factory)`
|
|
120
|
+
* — the OCP escape hatch every multi-driver Strav manager exposes.
|
|
121
|
+
*/
|
|
122
|
+
extend(name: string, provider: BrainDriver): void {
|
|
123
|
+
if (!name) {
|
|
124
|
+
throw new BrainError('BrainManager.extend: provider name must be a non-empty string.')
|
|
125
|
+
}
|
|
126
|
+
this.providers.set(name, provider)
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
/**
|
|
107
130
|
* One-shot chat: send the messages, await the full reply.
|
|
108
131
|
*
|
|
@@ -155,7 +178,7 @@ export class BrainManager {
|
|
|
155
178
|
*
|
|
156
179
|
* Throws `BrainError` when the configured provider doesn't
|
|
157
180
|
* implement `runWithTools` (V1: OpenAI / Gemini / DeepSeek providers
|
|
158
|
-
* don't yet — only `
|
|
181
|
+
* don't yet — only `AnthropicBrainDriver`).
|
|
159
182
|
*/
|
|
160
183
|
runTools(
|
|
161
184
|
input: string | readonly Message[],
|
package/src/brain_provider.ts
CHANGED
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
import { type Application, ConfigError, ConfigRepository, ServiceProvider } from '@strav/kernel'
|
|
28
28
|
import { BrainManager } from './brain_manager.ts'
|
|
29
29
|
import type { BrainConfigShape, ProviderConfig } from './brain_config.ts'
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import type {
|
|
30
|
+
import { AnthropicBrainDriver } from './drivers/anthropic/anthropic_brain_driver.ts'
|
|
31
|
+
import { DeepSeekBrainDriver } from './drivers/deepseek/deepseek_brain_driver.ts'
|
|
32
|
+
import { GeminiBrainDriver } from './drivers/gemini/gemini_brain_driver.ts'
|
|
33
|
+
import { OllamaBrainDriver } from './drivers/ollama/ollama_brain_driver.ts'
|
|
34
|
+
import { OpenAIBrainDriver } from './drivers/openai/openai_brain_driver.ts'
|
|
35
|
+
import { OpenAIResponsesBrainDriver } from './drivers/openai_responses/openai_responses_brain_driver.ts'
|
|
36
|
+
import type { BrainDriver } from './brain_driver.ts'
|
|
37
37
|
|
|
38
38
|
export class BrainProvider extends ServiceProvider {
|
|
39
39
|
override readonly name = 'brain'
|
|
@@ -58,9 +58,9 @@ export class BrainProvider extends ServiceProvider {
|
|
|
58
58
|
)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const providers: Record<string,
|
|
61
|
+
const providers: Record<string, BrainDriver> = {}
|
|
62
62
|
for (const [name, entry] of Object.entries(config.providers)) {
|
|
63
|
-
providers[name] =
|
|
63
|
+
providers[name] = buildBrainDriver(name, entry)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const options: ConstructorParameters<typeof BrainManager>[0] = {
|
|
@@ -89,7 +89,7 @@ export class BrainProvider extends ServiceProvider {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function
|
|
92
|
+
function buildBrainDriver(name: string, config: ProviderConfig): BrainDriver {
|
|
93
93
|
switch (config.driver) {
|
|
94
94
|
case 'anthropic':
|
|
95
95
|
if (!config.apiKey) {
|
|
@@ -97,42 +97,42 @@ function buildProvider(name: string, config: ProviderConfig): Provider {
|
|
|
97
97
|
`BrainProvider: anthropic provider "${name}" is missing apiKey. Source from env('ANTHROPIC_API_KEY').`,
|
|
98
98
|
)
|
|
99
99
|
}
|
|
100
|
-
return new
|
|
100
|
+
return new AnthropicBrainDriver(name, config)
|
|
101
101
|
case 'openai':
|
|
102
102
|
if (!config.apiKey) {
|
|
103
103
|
throw new ConfigError(
|
|
104
104
|
`BrainProvider: openai provider "${name}" is missing apiKey. Source from env('OPENAI_API_KEY').`,
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
|
-
return new
|
|
107
|
+
return new OpenAIBrainDriver(name, config)
|
|
108
108
|
case 'openai-responses':
|
|
109
109
|
if (!config.apiKey) {
|
|
110
110
|
throw new ConfigError(
|
|
111
111
|
`BrainProvider: openai-responses provider "${name}" is missing apiKey. Source from env('OPENAI_API_KEY').`,
|
|
112
112
|
)
|
|
113
113
|
}
|
|
114
|
-
return new
|
|
114
|
+
return new OpenAIResponsesBrainDriver(name, config)
|
|
115
115
|
case 'google':
|
|
116
116
|
if (!config.apiKey) {
|
|
117
117
|
throw new ConfigError(
|
|
118
118
|
`BrainProvider: google provider "${name}" is missing apiKey. Source from env('GOOGLE_API_KEY').`,
|
|
119
119
|
)
|
|
120
120
|
}
|
|
121
|
-
return new
|
|
121
|
+
return new GeminiBrainDriver(name, config)
|
|
122
122
|
case 'deepseek':
|
|
123
123
|
if (!config.apiKey) {
|
|
124
124
|
throw new ConfigError(
|
|
125
125
|
`BrainProvider: deepseek provider "${name}" is missing apiKey. Source from env('DEEPSEEK_API_KEY').`,
|
|
126
126
|
)
|
|
127
127
|
}
|
|
128
|
-
return new
|
|
128
|
+
return new DeepSeekBrainDriver(name, config)
|
|
129
129
|
case 'ollama':
|
|
130
130
|
if (!config.defaultModel) {
|
|
131
131
|
throw new ConfigError(
|
|
132
132
|
`BrainProvider: ollama provider "${name}" is missing defaultModel. Ollama models are user-installed — pick one you've pulled (e.g. 'llama3.2').`,
|
|
133
133
|
)
|
|
134
134
|
}
|
|
135
|
-
return new
|
|
135
|
+
return new OllamaBrainDriver(name, config)
|
|
136
136
|
default: {
|
|
137
137
|
const exhaustiveCheck: never = config
|
|
138
138
|
throw new ConfigError(
|