@strav/brain 1.0.0-alpha.8 → 1.0.1

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 +23 -7
  2. package/src/agent.ts +97 -0
  3. package/src/agent_generate_result.ts +32 -0
  4. package/src/agent_result.ts +39 -0
  5. package/src/agent_runner.ts +265 -0
  6. package/src/agent_stream_event.ts +100 -0
  7. package/src/brain_config.ts +218 -1
  8. package/src/brain_driver.ts +247 -0
  9. package/src/brain_error.ts +86 -10
  10. package/src/brain_manager.ts +419 -5
  11. package/src/brain_provider.ts +89 -10
  12. package/src/define_tool.ts +42 -0
  13. package/src/drivers/anthropic/anthropic_brain_driver.ts +641 -0
  14. package/src/drivers/anthropic/anthropic_helpers.ts +65 -0
  15. package/src/drivers/anthropic/anthropic_message_builder.ts +258 -0
  16. package/src/drivers/anthropic/anthropic_response_mapper.ts +123 -0
  17. package/src/drivers/anthropic/anthropic_tool_loop.ts +246 -0
  18. package/src/drivers/anthropic/index.ts +1 -0
  19. package/src/drivers/deepseek/deepseek_brain_driver.ts +117 -0
  20. package/src/drivers/deepseek/index.ts +1 -0
  21. package/src/drivers/gemini/gemini_brain_driver.ts +1064 -0
  22. package/src/drivers/gemini/index.ts +1 -0
  23. package/src/drivers/minimax/index.ts +1 -0
  24. package/src/drivers/minimax/minimax_brain_driver.ts +84 -0
  25. package/src/drivers/ollama/index.ts +1 -0
  26. package/src/drivers/ollama/ollama_brain_driver.ts +86 -0
  27. package/src/drivers/openai/index.ts +1 -0
  28. package/src/drivers/openai/openai_brain_driver.ts +796 -0
  29. package/src/drivers/openai/openai_helpers.ts +58 -0
  30. package/src/drivers/openai/openai_message_builder.ts +187 -0
  31. package/src/drivers/openai/openai_response_mapper.ts +70 -0
  32. package/src/drivers/openai/openai_tool_dispatch.ts +127 -0
  33. package/src/drivers/openai/openai_tool_loop.ts +191 -0
  34. package/src/drivers/openai_compat/index.ts +1 -0
  35. package/src/drivers/openai_compat/openai_compat_brain_driver.ts +616 -0
  36. package/src/drivers/openai_responses/index.ts +1 -0
  37. package/src/drivers/openai_responses/openai_responses_brain_driver.ts +1015 -0
  38. package/src/drivers/openrouter/index.ts +1 -0
  39. package/src/drivers/openrouter/openrouter_brain_driver.ts +137 -0
  40. package/src/drivers/qwen/index.ts +1 -0
  41. package/src/drivers/qwen/qwen_brain_driver.ts +103 -0
  42. package/src/index.ts +86 -8
  43. package/src/mcp/client.ts +243 -0
  44. package/src/mcp/index.ts +23 -0
  45. package/src/mcp/oauth.ts +227 -0
  46. package/src/mcp/pool.ts +106 -0
  47. package/src/mcp/resolve_mcp_tools.ts +108 -0
  48. package/src/mcp_server.ts +63 -0
  49. package/src/output_schema.ts +72 -0
  50. package/src/persistence/brain_message.ts +34 -0
  51. package/src/persistence/brain_message_repository.ts +98 -0
  52. package/src/persistence/brain_store.ts +166 -0
  53. package/src/persistence/brain_suspended_run.ts +30 -0
  54. package/src/persistence/brain_suspended_run_repository.ts +59 -0
  55. package/src/persistence/brain_thread.ts +30 -0
  56. package/src/persistence/brain_thread_repository.ts +56 -0
  57. package/src/persistence/database_brain_store.ts +190 -0
  58. package/src/persistence/index.ts +48 -0
  59. package/src/persistence/schemas/brain_message_schema.ts +61 -0
  60. package/src/persistence/schemas/brain_suspended_run_schema.ts +58 -0
  61. package/src/persistence/schemas/brain_thread_schema.ts +50 -0
  62. package/src/persistence/schemas/index.ts +3 -0
  63. package/src/suspended_run.ts +153 -0
  64. package/src/thread.ts +40 -1
  65. package/src/tool.ts +42 -0
  66. package/src/tool_execution_error.ts +26 -0
  67. package/src/tool_runner.ts +81 -0
  68. package/src/translate/index.ts +19 -0
  69. package/src/translate/translate_cache.ts +78 -0
  70. package/src/translate/translate_provider.ts +46 -0
  71. package/src/translate/translator.ts +271 -0
  72. package/src/types.ts +431 -1
  73. package/src/zod/index.ts +121 -0
  74. package/src/provider.ts +0 -48
  75. package/src/providers/anthropic_provider.ts +0 -227
@@ -1,227 +0,0 @@
1
- /**
2
- * `AnthropicProvider` — implementation of `Provider` backed by the
3
- * official `@anthropic-ai/sdk`.
4
- *
5
- * Responsibilities:
6
- * 1. Hold a singleton `Anthropic` client instance for the
7
- * configured API key + base URL.
8
- * 2. Translate the framework's `ChatOptions` / `Message` shapes
9
- * into Anthropic's `MessageCreateParams` (system as `TextBlock[]`
10
- * with `cache_control` when requested; messages with per-block
11
- * cache flags translated likewise; `thinking` mapped to
12
- * `ThinkingConfigParam`; `effort` placed under `output_config`).
13
- * 3. Translate the response back to `ChatResult` — flatten the
14
- * content blocks into a single `text` string, surface usage with
15
- * cache-hit counters, and pass the raw `Message` through on `.raw`.
16
- * 4. Stream via `client.messages.stream()` and yield the framework
17
- * `StreamEvent` union — `text` deltas plus a terminal `stop`
18
- * event with usage + stop reason.
19
- *
20
- * Errors from the SDK propagate; apps that want provider-specific
21
- * recovery can `instanceof Anthropic.RateLimitError` etc. The brain
22
- * facade wraps the call site in `BrainError` only for invariants the
23
- * facade owns (e.g. "no provider configured").
24
- */
25
-
26
- import Anthropic from '@anthropic-ai/sdk'
27
- import type { AnthropicProviderConfig } from '../brain_config.ts'
28
- import { DEFAULT_MODEL } from '../brain_config.ts'
29
- import type { Provider } from '../provider.ts'
30
- import type {
31
- ChatOptions,
32
- ChatResult,
33
- ChatUsage,
34
- Message,
35
- StreamEvent,
36
- SystemPrompt,
37
- } from '../types.ts'
38
-
39
- const EPHEMERAL_CACHE = { type: 'ephemeral' } as const
40
-
41
- export class AnthropicProvider implements Provider {
42
- readonly name: string
43
- private readonly client: Anthropic
44
- private readonly defaultModel: string
45
- private readonly defaultMaxTokens: number
46
- private readonly betas: readonly string[]
47
-
48
- constructor(
49
- name: string,
50
- config: AnthropicProviderConfig,
51
- options: { client?: Anthropic } = {},
52
- ) {
53
- this.name = name
54
- this.defaultModel = config.defaultModel ?? DEFAULT_MODEL
55
- this.defaultMaxTokens = config.defaultMaxTokens ?? 4096
56
- this.betas = config.betas ?? []
57
- // `client` injection point — tests pass a stub; apps that want a
58
- // pre-configured SDK instance (custom retry, fetch transport, etc.)
59
- // build their own and hand it over here.
60
- this.client =
61
- options.client ??
62
- new Anthropic({
63
- apiKey: config.apiKey,
64
- ...(config.baseUrl !== undefined ? { baseURL: config.baseUrl } : {}),
65
- })
66
- }
67
-
68
- async chat(messages: readonly Message[], options: ChatOptions = {}): Promise<ChatResult> {
69
- const params = this.buildParams(messages, options)
70
- const response = await this.client.messages.create(params)
71
- return this.toChatResult(response)
72
- }
73
-
74
- async *stream(
75
- messages: readonly Message[],
76
- options: ChatOptions = {},
77
- ): AsyncIterable<StreamEvent> {
78
- const params = this.buildParams(messages, options)
79
- const stream = this.client.messages.stream(params)
80
- for await (const event of stream) {
81
- if (
82
- event.type === 'content_block_delta' &&
83
- event.delta.type === 'text_delta'
84
- ) {
85
- yield { type: 'text', delta: event.delta.text }
86
- }
87
- }
88
- const final = await stream.finalMessage()
89
- yield {
90
- type: 'stop',
91
- stopReason: final.stop_reason,
92
- usage: toUsage(final.usage),
93
- }
94
- }
95
-
96
- async countTokens(
97
- messages: readonly Message[],
98
- options: ChatOptions = {},
99
- ): Promise<number> {
100
- const base = this.buildParams(messages, options)
101
- // count_tokens only accepts a subset of MessageCreateParams; build
102
- // a focused payload that matches what apps actually need to budget.
103
- const result = await this.client.messages.countTokens({
104
- model: base.model,
105
- messages: base.messages,
106
- ...(base.system !== undefined ? { system: base.system } : {}),
107
- ...(base.thinking !== undefined ? { thinking: base.thinking } : {}),
108
- })
109
- return result.input_tokens
110
- }
111
-
112
- // ─── Param translation ──────────────────────────────────────────────────
113
-
114
- private buildParams(
115
- messages: readonly Message[],
116
- options: ChatOptions,
117
- ): Anthropic.MessageCreateParamsNonStreaming {
118
- const model = options.model ?? this.defaultModel
119
- const params: Anthropic.MessageCreateParamsNonStreaming = {
120
- model,
121
- max_tokens: options.maxTokens ?? this.defaultMaxTokens,
122
- messages: messages.map(toMessageParam),
123
- }
124
-
125
- const system = toSystemParam(options.system)
126
- if (system !== undefined) params.system = system
127
-
128
- if (options.thinking === 'adaptive') {
129
- params.thinking = { type: 'adaptive' }
130
- } else if (options.thinking === 'disabled') {
131
- params.thinking = { type: 'disabled' }
132
- }
133
-
134
- if (options.effort !== undefined) {
135
- params.output_config = { effort: options.effort }
136
- }
137
-
138
- if (options.cache === true) {
139
- // Top-level auto-cache the last cacheable block. Maps to the
140
- // SDK's `cache_control` shorthand on the request body.
141
- ;(params as { cache_control?: { type: 'ephemeral' } }).cache_control = EPHEMERAL_CACHE
142
- }
143
-
144
- const betas = mergeBetas(this.betas, options.betas)
145
- if (betas.length > 0) {
146
- ;(params as { betas?: readonly string[] }).betas = betas
147
- }
148
-
149
- return params
150
- }
151
-
152
- private toChatResult(message: Anthropic.Message): ChatResult<Anthropic.Message> {
153
- const text = message.content
154
- .filter((b): b is Anthropic.TextBlock => b.type === 'text')
155
- .map((b) => b.text)
156
- .join('')
157
- return {
158
- text,
159
- model: message.model,
160
- stopReason: message.stop_reason,
161
- usage: toUsage(message.usage),
162
- raw: message,
163
- }
164
- }
165
- }
166
-
167
- // ─── Shape converters ─────────────────────────────────────────────────────
168
-
169
- function toUsage(u: Anthropic.Usage): ChatUsage {
170
- return {
171
- inputTokens: u.input_tokens,
172
- outputTokens: u.output_tokens,
173
- cacheReadTokens: u.cache_read_input_tokens ?? 0,
174
- cacheCreationTokens: u.cache_creation_input_tokens ?? 0,
175
- }
176
- }
177
-
178
- function toMessageParam(message: Message): Anthropic.MessageParam {
179
- if (typeof message.content === 'string') {
180
- return { role: message.role, content: message.content }
181
- }
182
- return {
183
- role: message.role,
184
- content: message.content.map((block) => {
185
- const param: Anthropic.TextBlockParam = { type: 'text', text: block.text }
186
- if (block.cache) param.cache_control = EPHEMERAL_CACHE
187
- return param
188
- }),
189
- }
190
- }
191
-
192
- function toSystemParam(
193
- system: SystemPrompt | undefined,
194
- ): string | Anthropic.TextBlockParam[] | undefined {
195
- if (system === undefined) return undefined
196
- if (typeof system === 'string') return system
197
- if (Array.isArray(system)) {
198
- return system.map((block) => {
199
- const param: Anthropic.TextBlockParam = { type: 'text', text: block.text }
200
- if (block.cache) param.cache_control = EPHEMERAL_CACHE
201
- return param
202
- })
203
- }
204
- const param: Anthropic.TextBlockParam = { type: 'text', text: system.text }
205
- if (system.cache) param.cache_control = EPHEMERAL_CACHE
206
- return [param]
207
- }
208
-
209
- function mergeBetas(
210
- providerBetas: readonly string[],
211
- callBetas: readonly string[] | undefined,
212
- ): readonly string[] {
213
- if (!callBetas || callBetas.length === 0) return providerBetas
214
- const seen = new Set<string>()
215
- const out: string[] = []
216
- for (const b of providerBetas) {
217
- if (seen.has(b)) continue
218
- seen.add(b)
219
- out.push(b)
220
- }
221
- for (const b of callBetas) {
222
- if (seen.has(b)) continue
223
- seen.add(b)
224
- out.push(b)
225
- }
226
- return out
227
- }