@strav/brain 1.0.0-alpha.40 → 1.0.0-alpha.42

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strav/brain",
3
- "version": "1.0.0-alpha.40",
3
+ "version": "1.0.0-alpha.42",
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",
@@ -26,8 +26,8 @@
26
26
  "@anthropic-ai/sdk": "^0.100.0",
27
27
  "@google/genai": "^2.7.0",
28
28
  "@modelcontextprotocol/sdk": "^1.29.0",
29
- "@strav/database": "1.0.0-alpha.40",
30
- "@strav/kernel": "1.0.0-alpha.40",
29
+ "@strav/database": "1.0.0-alpha.42",
30
+ "@strav/kernel": "1.0.0-alpha.42",
31
31
  "openai": "^6.0.0"
32
32
  },
33
33
  "peerDependencies": {
@@ -151,6 +151,89 @@ export interface OllamaProviderConfig {
151
151
  defaultTranscribeModel?: string
152
152
  }
153
153
 
154
+ /**
155
+ * Qwen (Alibaba DashScope) driver config — backed by the `openai`
156
+ * SDK pointed at DashScope's OpenAI-compatible `/compatible-mode/v1`
157
+ * endpoint.
158
+ *
159
+ * DashScope publishes regional endpoints (Singapore, Beijing, Hong Kong,
160
+ * US-Virginia). The default is the Singapore endpoint
161
+ * (`dashscope-intl`) to fit Strav's SEA-first positioning; apps in
162
+ * other regions override via `baseUrl`.
163
+ */
164
+ export interface QwenProviderConfig {
165
+ driver: 'qwen'
166
+ /** API key. Required. Most apps source from `env('DASHSCOPE_API_KEY')` or `env('QWEN_API_KEY')`. */
167
+ apiKey: string
168
+ /**
169
+ * Optional base URL override. Defaults to the Singapore endpoint
170
+ * `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`. Other
171
+ * documented options:
172
+ * - `https://dashscope.aliyuncs.com/compatible-mode/v1` (Beijing)
173
+ * - `https://dashscope-us.aliyuncs.com/compatible-mode/v1` (US/Virginia)
174
+ * - `https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1` (Hong Kong)
175
+ */
176
+ baseUrl?: string
177
+ /** Default model. Defaults to `qwen-plus` (mid-tier; `qwen-turbo` is cheaper, `qwen-max` is heaviest). */
178
+ defaultModel?: string
179
+ /** Default `max_tokens` for `chat()` calls that don't specify one. */
180
+ defaultMaxTokens?: number
181
+ }
182
+
183
+ /**
184
+ * MiniMax driver config — backed by the `openai` SDK pointed at
185
+ * MiniMax's OpenAI-compatible endpoint at `https://api.minimax.io/v1`.
186
+ */
187
+ export interface MiniMaxProviderConfig {
188
+ driver: 'minimax'
189
+ /** API key. Required. Most apps source from `env('MINIMAX_API_KEY')`. */
190
+ apiKey: string
191
+ /** Optional base URL override. Defaults to `https://api.minimax.io/v1`. */
192
+ baseUrl?: string
193
+ /** Default model. Defaults to `MiniMax-M2`. */
194
+ defaultModel?: string
195
+ /** Default `max_tokens` for `chat()` calls that don't specify one. */
196
+ defaultMaxTokens?: number
197
+ }
198
+
199
+ /**
200
+ * OpenRouter driver config — backed by the `openai` SDK pointed at
201
+ * `https://openrouter.ai/api/v1`. OpenRouter multiplexes 300+ models
202
+ * behind one OpenAI-compatible endpoint; **the surface a given call
203
+ * supports depends on the chosen model**. Pin an explicit model slug
204
+ * (e.g. `anthropic/claude-sonnet-4`, `meta-llama/llama-3.3-70b`) per
205
+ * call; consult `https://openrouter.ai/api/v1/models` for the
206
+ * `supported_parameters` per model. Calls to `embed` / `transcribe`
207
+ * are not exposed; structured output uses the OpenAI-compat
208
+ * tool-forcing pattern and depends on the model supporting
209
+ * function-calling.
210
+ */
211
+ export interface OpenRouterProviderConfig {
212
+ driver: 'openrouter'
213
+ /** API key. Required. Most apps source from `env('OPENROUTER_API_KEY')`. */
214
+ apiKey: string
215
+ /** Optional base URL override. Defaults to `https://openrouter.ai/api/v1`. */
216
+ baseUrl?: string
217
+ /**
218
+ * Default model. No default — OpenRouter has no canonical pick;
219
+ * apps must choose a slug (e.g. `anthropic/claude-sonnet-4`).
220
+ */
221
+ defaultModel?: string
222
+ /** Default `max_tokens` for `chat()` calls that don't specify one. */
223
+ defaultMaxTokens?: number
224
+ /**
225
+ * Optional app URL — forwarded as the `HTTP-Referer` header.
226
+ * OpenRouter uses it to attribute traffic on their leaderboard
227
+ * and rankings page.
228
+ */
229
+ appUrl?: string
230
+ /**
231
+ * Optional app title — forwarded as the `X-Title` header. Paired
232
+ * with `appUrl` for OpenRouter's leaderboard.
233
+ */
234
+ appTitle?: string
235
+ }
236
+
154
237
  export type ProviderConfig =
155
238
  | AnthropicProviderConfig
156
239
  | OpenAIProviderConfig
@@ -158,6 +241,9 @@ export type ProviderConfig =
158
241
  | GeminiProviderConfig
159
242
  | DeepSeekProviderConfig
160
243
  | OllamaProviderConfig
244
+ | QwenProviderConfig
245
+ | MiniMaxProviderConfig
246
+ | OpenRouterProviderConfig
161
247
 
162
248
  /** Cache-shape defaults applied when `ChatOptions.cache` is omitted. */
163
249
  export interface BrainCacheConfig {
@@ -25,15 +25,18 @@
25
25
  */
26
26
 
27
27
  import { type Application, ConfigError, ConfigRepository, ServiceProvider } from '@strav/kernel'
28
- import { BrainManager } from './brain_manager.ts'
29
28
  import type { BrainConfigShape, ProviderConfig } from './brain_config.ts'
29
+ import type { BrainDriver } from './brain_driver.ts'
30
+ import { BrainManager } from './brain_manager.ts'
30
31
  import { AnthropicBrainDriver } from './drivers/anthropic/anthropic_brain_driver.ts'
31
32
  import { DeepSeekBrainDriver } from './drivers/deepseek/deepseek_brain_driver.ts'
32
33
  import { GeminiBrainDriver } from './drivers/gemini/gemini_brain_driver.ts'
34
+ import { MiniMaxBrainDriver } from './drivers/minimax/minimax_brain_driver.ts'
33
35
  import { OllamaBrainDriver } from './drivers/ollama/ollama_brain_driver.ts'
34
36
  import { OpenAIBrainDriver } from './drivers/openai/openai_brain_driver.ts'
35
37
  import { OpenAIResponsesBrainDriver } from './drivers/openai_responses/openai_responses_brain_driver.ts'
36
- import type { BrainDriver } from './brain_driver.ts'
38
+ import { OpenRouterBrainDriver } from './drivers/openrouter/openrouter_brain_driver.ts'
39
+ import { QwenBrainDriver } from './drivers/qwen/qwen_brain_driver.ts'
37
40
 
38
41
  export class BrainProvider extends ServiceProvider {
39
42
  override readonly name = 'brain'
@@ -133,10 +136,31 @@ function buildBrainDriver(name: string, config: ProviderConfig): BrainDriver {
133
136
  )
134
137
  }
135
138
  return new OllamaBrainDriver(name, config)
139
+ case 'qwen':
140
+ if (!config.apiKey) {
141
+ throw new ConfigError(
142
+ `BrainProvider: qwen provider "${name}" is missing apiKey. Source from env('DASHSCOPE_API_KEY') or env('QWEN_API_KEY').`,
143
+ )
144
+ }
145
+ return new QwenBrainDriver(name, config)
146
+ case 'minimax':
147
+ if (!config.apiKey) {
148
+ throw new ConfigError(
149
+ `BrainProvider: minimax provider "${name}" is missing apiKey. Source from env('MINIMAX_API_KEY').`,
150
+ )
151
+ }
152
+ return new MiniMaxBrainDriver(name, config)
153
+ case 'openrouter':
154
+ if (!config.apiKey) {
155
+ throw new ConfigError(
156
+ `BrainProvider: openrouter provider "${name}" is missing apiKey. Source from env('OPENROUTER_API_KEY').`,
157
+ )
158
+ }
159
+ return new OpenRouterBrainDriver(name, config)
136
160
  default: {
137
161
  const exhaustiveCheck: never = config
138
162
  throw new ConfigError(
139
- `BrainProvider: unknown driver for provider "${name}". Known drivers: anthropic, openai, openai-responses, google, deepseek, ollama.`,
163
+ `BrainProvider: unknown driver for provider "${name}". Known drivers: anthropic, openai, openai-responses, google, deepseek, ollama, qwen, minimax, openrouter.`,
140
164
  )
141
165
  // (unreachable — kept for the exhaustive check to fire when a new driver lands)
142
166
  // biome-ignore lint/correctness/noUnreachable: kept for the exhaustive-check above
@@ -0,0 +1 @@
1
+ export { MiniMaxBrainDriver } from './minimax_brain_driver.ts'
@@ -0,0 +1,84 @@
1
+ /**
2
+ * `MiniMaxBrainDriver` — `OpenAICompatBrainDriver` pointed at
3
+ * MiniMax's OpenAI-compatible endpoint at `https://api.minimax.io/v1`.
4
+ *
5
+ * MiniMax (M2, abab line) speaks the OpenAI Chat Completions wire
6
+ * format and supports function-calling, so the OpenAI-compat base
7
+ * already covers chat / stream / generate (via `json_object`) /
8
+ * runWithToolsAndSchema (via tool-forcing).
9
+ *
10
+ * MiniMax has a `reasoning_split=true` flag that surfaces the
11
+ * assistant's reasoning trace on a separate `reasoning_details`
12
+ * field. Not wired in V1 — `effort` / `thinking` are stripped by
13
+ * the OpenAI-compat `buildParams` override and apps reach for
14
+ * reasoning surfaces via the Anthropic / Gemini / OpenAI Responses
15
+ * drivers.
16
+ *
17
+ * `embed` / `transcribe` are not exposed via the compatibility
18
+ * endpoint; the inherited stubs are replaced with explicit
19
+ * `BrainError`s so apps see a clear message instead of a wire 404.
20
+ */
21
+
22
+ import type OpenAI from 'openai'
23
+ import type { MiniMaxProviderConfig } from '../../brain_config.ts'
24
+ import { BrainError } from '../../brain_error.ts'
25
+ import type { ResolveMcpToolsOptions } from '../../mcp/resolve_mcp_tools.ts'
26
+ import type {
27
+ AudioSource,
28
+ EmbedOptions,
29
+ EmbedResult,
30
+ TranscribeOptions,
31
+ TranscribeResult,
32
+ } from '../../types.ts'
33
+ import { OpenAICompatBrainDriver } from '../openai_compat/openai_compat_brain_driver.ts'
34
+
35
+ const DEFAULT_MINIMAX_MODEL = 'MiniMax-M2'
36
+ const DEFAULT_MINIMAX_BASE_URL = 'https://api.minimax.io/v1'
37
+
38
+ export interface MiniMaxProviderOptions {
39
+ client?: OpenAI
40
+ /**
41
+ * Internal seam — tests inject a stub MCP client factory so MCP
42
+ * tool resolution doesn't dial the network. Real apps leave it
43
+ * unset; the provider uses the default `MCPClient`.
44
+ */
45
+ mcpClientFactory?: ResolveMcpToolsOptions['clientFactory']
46
+ }
47
+
48
+ export class MiniMaxBrainDriver extends OpenAICompatBrainDriver {
49
+ constructor(name: string, config: MiniMaxProviderConfig, options: MiniMaxProviderOptions = {}) {
50
+ super(
51
+ name,
52
+ {
53
+ driver: 'openai',
54
+ apiKey: config.apiKey,
55
+ baseUrl: config.baseUrl ?? DEFAULT_MINIMAX_BASE_URL,
56
+ defaultModel: config.defaultModel ?? DEFAULT_MINIMAX_MODEL,
57
+ ...(config.defaultMaxTokens !== undefined
58
+ ? { defaultMaxTokens: config.defaultMaxTokens }
59
+ : {}),
60
+ },
61
+ options,
62
+ )
63
+ }
64
+
65
+ override async embed(
66
+ _texts: readonly string[],
67
+ _options?: EmbedOptions,
68
+ ): Promise<EmbedResult<OpenAI.CreateEmbeddingResponse>> {
69
+ throw new BrainError(
70
+ `MiniMaxBrainDriver.embed: MiniMax's OpenAI-compatibility endpoint does not expose embeddings. Route embed calls to a provider with native support — OpenAI / Gemini / Ollama.`,
71
+ { context: { provider: this.name } },
72
+ )
73
+ }
74
+
75
+ override async transcribe(
76
+ _audio: AudioSource,
77
+ _options?: TranscribeOptions,
78
+ ): Promise<TranscribeResult<OpenAI.Audio.TranscriptionCreateResponse>> {
79
+ throw new BrainError(
80
+ "MiniMaxBrainDriver.transcribe: MiniMax's OpenAI-compatibility endpoint does not expose audio transcription. Route transcribe calls to a provider with native support — OpenAI / Ollama / Gemini.",
81
+ { context: { provider: this.name } },
82
+ )
83
+ }
84
+ }
@@ -0,0 +1 @@
1
+ export { OpenRouterBrainDriver } from './openrouter_brain_driver.ts'
@@ -0,0 +1,137 @@
1
+ /**
2
+ * `OpenRouterBrainDriver` — `OpenAICompatBrainDriver` pointed at
3
+ * OpenRouter's OpenAI-compatible endpoint at
4
+ * `https://openrouter.ai/api/v1`.
5
+ *
6
+ * OpenRouter multiplexes 300+ hosted models (Anthropic, OpenAI,
7
+ * Google, Meta, Mistral, DeepSeek, Qwen, ...) behind one
8
+ * Chat-Completions-shaped API. The driver does NOT try to predict
9
+ * which model supports what — capabilities vary per slug. The
10
+ * supported approach is:
11
+ *
12
+ * - Pin an explicit model slug per call (e.g.
13
+ * `anthropic/claude-sonnet-4`, `meta-llama/llama-3.3-70b`).
14
+ * - If the chosen model doesn't support a primitive (tools,
15
+ * JSON mode, schema-forcing), the upstream OpenRouter error
16
+ * bubbles up as a `BrainError` from the inherited helpers.
17
+ * - Consult `https://openrouter.ai/api/v1/models` and filter on
18
+ * the `supported_parameters` field to pick a model that
19
+ * handles the feature you need.
20
+ *
21
+ * Two OpenRouter-specific niceties:
22
+ *
23
+ * - Optional `appUrl` (→ `HTTP-Referer`) and `appTitle`
24
+ * (→ `X-Title`) forwarded as default headers on every request.
25
+ * OpenRouter uses these to attribute traffic on their public
26
+ * leaderboard / rankings.
27
+ *
28
+ * - No `defaultModel` default — OpenRouter has no canonical pick.
29
+ * Apps must set one, or pass `options.model` per call. The
30
+ * inherited OpenAI default `gpt-5` would silently route through
31
+ * OpenAI's slug namespace and surprise users.
32
+ *
33
+ * Inherits all OpenAI-compat overrides: `buildParams` strips
34
+ * `reasoning_effort` (re-add it in a subclass / per-call `extraBody`
35
+ * for models that take it), `generate` uses `json_object`-mode +
36
+ * schema-in-system-prompt, `runWithToolsAndSchema` /
37
+ * `streamWithToolsAndSchema` use the tool-forcing pattern.
38
+ *
39
+ * `embed` / `transcribe` are not exposed via OpenRouter — replaced
40
+ * with explicit `BrainError`s.
41
+ */
42
+
43
+ import OpenAI from 'openai'
44
+ import type { OpenRouterProviderConfig } from '../../brain_config.ts'
45
+ import { BrainError } from '../../brain_error.ts'
46
+ import type { ResolveMcpToolsOptions } from '../../mcp/resolve_mcp_tools.ts'
47
+ import type {
48
+ AudioSource,
49
+ EmbedOptions,
50
+ EmbedResult,
51
+ TranscribeOptions,
52
+ TranscribeResult,
53
+ } from '../../types.ts'
54
+ import { OpenAICompatBrainDriver } from '../openai_compat/openai_compat_brain_driver.ts'
55
+
56
+ const DEFAULT_OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1'
57
+ /**
58
+ * Fallback when neither `config.defaultModel` nor a per-call
59
+ * `options.model` is supplied. Picked as a broad-purpose,
60
+ * tool-capable, well-priced default; apps that want something else
61
+ * just pass `defaultModel`.
62
+ */
63
+ const DEFAULT_OPENROUTER_MODEL = 'openai/gpt-4o-mini'
64
+
65
+ export interface OpenRouterProviderOptions {
66
+ client?: OpenAI
67
+ /**
68
+ * Internal seam — tests inject a stub MCP client factory so MCP
69
+ * tool resolution doesn't dial the network. Real apps leave it
70
+ * unset; the provider uses the default `MCPClient`.
71
+ */
72
+ mcpClientFactory?: ResolveMcpToolsOptions['clientFactory']
73
+ }
74
+
75
+ export class OpenRouterBrainDriver extends OpenAICompatBrainDriver {
76
+ constructor(
77
+ name: string,
78
+ config: OpenRouterProviderConfig,
79
+ options: OpenRouterProviderOptions = {},
80
+ ) {
81
+ const baseURL = config.baseUrl ?? DEFAULT_OPENROUTER_BASE_URL
82
+ const defaultHeaders = buildOpenRouterHeaders(config)
83
+ // Construct the OpenAI client locally so we can carry OpenRouter's
84
+ // attribution headers (HTTP-Referer / X-Title). The base
85
+ // OpenAIBrainDriver only exposes apiKey / baseUrl / organization,
86
+ // so we hand a fully-built client through `options.client`.
87
+ const client =
88
+ options.client ??
89
+ new OpenAI({
90
+ apiKey: config.apiKey,
91
+ baseURL,
92
+ ...(defaultHeaders ? { defaultHeaders } : {}),
93
+ })
94
+ super(
95
+ name,
96
+ {
97
+ driver: 'openai',
98
+ apiKey: config.apiKey,
99
+ baseUrl: baseURL,
100
+ defaultModel: config.defaultModel ?? DEFAULT_OPENROUTER_MODEL,
101
+ ...(config.defaultMaxTokens !== undefined
102
+ ? { defaultMaxTokens: config.defaultMaxTokens }
103
+ : {}),
104
+ },
105
+ { ...options, client },
106
+ )
107
+ }
108
+
109
+ override async embed(
110
+ _texts: readonly string[],
111
+ _options?: EmbedOptions,
112
+ ): Promise<EmbedResult<OpenAI.CreateEmbeddingResponse>> {
113
+ throw new BrainError(
114
+ `OpenRouterBrainDriver.embed: OpenRouter's API does not expose embeddings. Route embed calls to a provider with native support — OpenAI / Gemini / Ollama.`,
115
+ { context: { provider: this.name } },
116
+ )
117
+ }
118
+
119
+ override async transcribe(
120
+ _audio: AudioSource,
121
+ _options?: TranscribeOptions,
122
+ ): Promise<TranscribeResult<OpenAI.Audio.TranscriptionCreateResponse>> {
123
+ throw new BrainError(
124
+ "OpenRouterBrainDriver.transcribe: OpenRouter's API does not expose audio transcription. Route transcribe calls to a provider with native support — OpenAI / Ollama / Gemini.",
125
+ { context: { provider: this.name } },
126
+ )
127
+ }
128
+ }
129
+
130
+ function buildOpenRouterHeaders(
131
+ config: OpenRouterProviderConfig,
132
+ ): Record<string, string> | undefined {
133
+ const headers: Record<string, string> = {}
134
+ if (config.appUrl !== undefined) headers['HTTP-Referer'] = config.appUrl
135
+ if (config.appTitle !== undefined) headers['X-Title'] = config.appTitle
136
+ return Object.keys(headers).length > 0 ? headers : undefined
137
+ }
@@ -0,0 +1 @@
1
+ export { QwenBrainDriver } from './qwen_brain_driver.ts'
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `QwenBrainDriver` — `OpenAICompatBrainDriver` pointed at Alibaba
3
+ * DashScope's OpenAI-compatible `/compatible-mode/v1` endpoint.
4
+ *
5
+ * Why ship Qwen: deep coverage of Chinese + SEA languages, with the
6
+ * `qwen-plus` / `qwen-max` line punching at the frontier on those
7
+ * locales. Fits Strav's SEA-first positioning.
8
+ *
9
+ * Inherits the OpenAI-compat overrides (strip `reasoning_effort`,
10
+ * `json_object`-mode generate with schema-in-system-prompt,
11
+ * tool-forcing pattern for combined tools + schema) from the base
12
+ * class. Only adds:
13
+ *
14
+ * - Constructor with DashScope defaults — base URL
15
+ * `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` (the
16
+ * Singapore endpoint), default model `qwen-plus`.
17
+ *
18
+ * - `mapUsage` override — Qwen reports prompt-cache hits on
19
+ * OpenAI's standard `prompt_tokens_details.cached_tokens`
20
+ * field; the inherited mapping is already correct. Kept the
21
+ * hook here for vendor-specific extension fields if they
22
+ * surface later.
23
+ *
24
+ * `embed` / `transcribe` are not implemented for V1 — DashScope
25
+ * exposes both via separate endpoints, but the framework's seam
26
+ * is per-driver and we'd rather route those calls to a provider
27
+ * with first-class support. Override the inherited stubs to throw
28
+ * with a clear message instead of letting the SDK 404.
29
+ */
30
+
31
+ import type OpenAI from 'openai'
32
+ import type { QwenProviderConfig } from '../../brain_config.ts'
33
+ import { BrainError } from '../../brain_error.ts'
34
+ import type { ResolveMcpToolsOptions } from '../../mcp/resolve_mcp_tools.ts'
35
+ import type {
36
+ AudioSource,
37
+ EmbedOptions,
38
+ EmbedResult,
39
+ TranscribeOptions,
40
+ TranscribeResult,
41
+ } from '../../types.ts'
42
+ import { OpenAICompatBrainDriver } from '../openai_compat/openai_compat_brain_driver.ts'
43
+
44
+ const DEFAULT_QWEN_MODEL = 'qwen-plus'
45
+ const DEFAULT_QWEN_BASE_URL = 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1'
46
+
47
+ export interface QwenProviderOptions {
48
+ client?: OpenAI
49
+ /**
50
+ * Internal seam — tests inject a stub MCP client factory so MCP
51
+ * tool resolution doesn't dial the network. Real apps leave it
52
+ * unset; the provider uses the default `MCPClient`.
53
+ */
54
+ mcpClientFactory?: ResolveMcpToolsOptions['clientFactory']
55
+ }
56
+
57
+ export class QwenBrainDriver extends OpenAICompatBrainDriver {
58
+ constructor(name: string, config: QwenProviderConfig, options: QwenProviderOptions = {}) {
59
+ super(
60
+ name,
61
+ {
62
+ driver: 'openai',
63
+ apiKey: config.apiKey,
64
+ baseUrl: config.baseUrl ?? DEFAULT_QWEN_BASE_URL,
65
+ defaultModel: config.defaultModel ?? DEFAULT_QWEN_MODEL,
66
+ ...(config.defaultMaxTokens !== undefined
67
+ ? { defaultMaxTokens: config.defaultMaxTokens }
68
+ : {}),
69
+ },
70
+ options,
71
+ )
72
+ }
73
+
74
+ /**
75
+ * DashScope's compatibility endpoint does not expose embeddings
76
+ * via `/compatible-mode/v1/embeddings`. Override the inherited
77
+ * `embed` to throw clearly rather than 404 at the wire.
78
+ */
79
+ override async embed(
80
+ _texts: readonly string[],
81
+ _options?: EmbedOptions,
82
+ ): Promise<EmbedResult<OpenAI.CreateEmbeddingResponse>> {
83
+ throw new BrainError(
84
+ `QwenBrainDriver.embed: Qwen's OpenAI-compatibility endpoint does not expose embeddings. Route embed calls to a provider with native support — OpenAI / Gemini / Ollama.`,
85
+ { context: { provider: this.name } },
86
+ )
87
+ }
88
+
89
+ /**
90
+ * DashScope's compatibility endpoint does not expose audio
91
+ * transcription. Override the inherited `transcribe` to throw
92
+ * clearly.
93
+ */
94
+ override async transcribe(
95
+ _audio: AudioSource,
96
+ _options?: TranscribeOptions,
97
+ ): Promise<TranscribeResult<OpenAI.Audio.TranscriptionCreateResponse>> {
98
+ throw new BrainError(
99
+ "QwenBrainDriver.transcribe: Qwen's OpenAI-compatibility endpoint does not expose audio transcription. Route transcribe calls to a provider with native support — OpenAI / Ollama / Gemini.",
100
+ { context: { provider: this.name } },
101
+ )
102
+ }
103
+ }
package/src/index.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  //
3
3
  // Shipped:
4
4
  // - `BrainDriver` contract + concrete drivers — Anthropic, OpenAI
5
- // (Chat + Responses), Gemini, DeepSeek, Ollama, openai-compat.
5
+ // (Chat + Responses), Gemini, DeepSeek, Ollama, Qwen, MiniMax,
6
+ // OpenRouter, openai-compat.
6
7
  // - `BrainManager` + `Thread` (persisted history, compaction) + the
7
8
  // `BrainProvider` service wiring + prompt-cache plumbing.
8
9
  // - Tools — `defineTool`, `Agent` base + `AgentRunner`,
@@ -17,8 +18,8 @@ export { Agent } from './agent.ts'
17
18
  export type { AgentGenerateResult } from './agent_generate_result.ts'
18
19
  export type { AgentResult } from './agent_result.ts'
19
20
  export {
20
- AgentRunner,
21
21
  type AgentRunMaybeSuspended,
22
+ AgentRunner,
22
23
  type AgentRunResult,
23
24
  } from './agent_runner.ts'
24
25
  export type { AgentStreamEvent } from './agent_stream_event.ts'
@@ -26,15 +27,23 @@ export {
26
27
  type AnthropicProviderConfig,
27
28
  type BrainCacheConfig,
28
29
  type BrainConfigShape,
29
- type DeepSeekProviderConfig,
30
30
  DEFAULT_MODEL,
31
31
  DEFAULT_TIERS,
32
+ type DeepSeekProviderConfig,
32
33
  type GeminiProviderConfig,
34
+ type MiniMaxProviderConfig,
33
35
  type OllamaProviderConfig,
34
36
  type OpenAIProviderConfig,
35
37
  type OpenAIResponsesProviderConfig,
38
+ type OpenRouterProviderConfig,
36
39
  type ProviderConfig,
40
+ type QwenProviderConfig,
37
41
  } from './brain_config.ts'
42
+ export type {
43
+ BrainDriver,
44
+ RunWithToolsOptions,
45
+ RunWithToolsOptionsWithSuspend,
46
+ } from './brain_driver.ts'
38
47
  export {
39
48
  BrainConfigError,
40
49
  BrainError,
@@ -48,22 +57,20 @@ export {
48
57
  type BrainManagerOptions,
49
58
  } from './brain_manager.ts'
50
59
  export { BrainProvider } from './brain_provider.ts'
51
- export { defineTool, type DefineToolSpec } from './define_tool.ts'
52
- export { MCPClientPool, type MCPClientFactory } from './mcp/pool.ts'
53
- export type { MCPServer, MCPServerToolConfig } from './mcp_server.ts'
54
- export type { OutputSchema } from './output_schema.ts'
60
+ export { type DefineToolSpec, defineTool } from './define_tool.ts'
55
61
  export { AnthropicBrainDriver } from './drivers/anthropic/anthropic_brain_driver.ts'
56
62
  export { DeepSeekBrainDriver } from './drivers/deepseek/deepseek_brain_driver.ts'
57
63
  export { GeminiBrainDriver } from './drivers/gemini/gemini_brain_driver.ts'
64
+ export { MiniMaxBrainDriver } from './drivers/minimax/minimax_brain_driver.ts'
58
65
  export { OllamaBrainDriver } from './drivers/ollama/ollama_brain_driver.ts'
59
- export { OpenAICompatBrainDriver } from './drivers/openai_compat/openai_compat_brain_driver.ts'
60
66
  export { OpenAIBrainDriver } from './drivers/openai/openai_brain_driver.ts'
67
+ export { OpenAICompatBrainDriver } from './drivers/openai_compat/openai_compat_brain_driver.ts'
61
68
  export { OpenAIResponsesBrainDriver } from './drivers/openai_responses/openai_responses_brain_driver.ts'
62
- export type {
63
- BrainDriver,
64
- RunWithToolsOptions,
65
- RunWithToolsOptionsWithSuspend,
66
- } from './brain_driver.ts'
69
+ export { OpenRouterBrainDriver } from './drivers/openrouter/openrouter_brain_driver.ts'
70
+ export { QwenBrainDriver } from './drivers/qwen/qwen_brain_driver.ts'
71
+ export { type MCPClientFactory, MCPClientPool } from './mcp/pool.ts'
72
+ export type { MCPServer, MCPServerToolConfig } from './mcp_server.ts'
73
+ export type { OutputSchema } from './output_schema.ts'
67
74
  export {
68
75
  appendResumeResults,
69
76
  isSuspended,
@@ -75,14 +82,14 @@ export { Thread, type ThreadOptions, type ThreadState } from './thread.ts'
75
82
  export type { Tool, ToolContext } from './tool.ts'
76
83
  export { ToolExecutionError } from './tool_execution_error.ts'
77
84
  export type {
85
+ AudioBlock,
86
+ AudioSource,
78
87
  ChatOptions,
79
88
  ChatResult,
80
89
  ChatUsage,
81
90
  CompactConfig,
82
91
  CompactionBlock,
83
92
  ContentBlock,
84
- AudioBlock,
85
- AudioSource,
86
93
  DocumentBlock,
87
94
  EmbedOptions,
88
95
  EmbedResult,