@strav/brain 1.0.0-alpha.15 → 1.0.0-alpha.17
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 +2 -2
- package/src/agent.ts +34 -5
- package/src/agent_generate_result.ts +30 -0
- package/src/agent_runner.ts +140 -14
- package/src/agent_stream_event.ts +100 -0
- package/src/brain_config.ts +91 -1
- package/src/brain_manager.ts +168 -4
- package/src/brain_provider.ts +25 -1
- package/src/index.ts +19 -1
- package/src/mcp/client.ts +82 -13
- package/src/mcp/index.ts +6 -0
- package/src/mcp/oauth.ts +227 -0
- package/src/mcp/resolve_mcp_tools.ts +6 -2
- package/src/mcp_server.ts +16 -0
- package/src/provider.ts +109 -0
- package/src/providers/anthropic_provider.ts +596 -28
- package/src/providers/deepseek_provider.ts +117 -0
- package/src/providers/gemini_provider.ts +590 -21
- package/src/providers/ollama_provider.ts +86 -0
- package/src/providers/openai_compat_provider.ts +187 -0
- package/src/providers/openai_provider.ts +735 -32
- package/src/providers/openai_responses_provider.ts +700 -0
- package/src/tool.ts +7 -0
- package/src/tool_runner.ts +81 -0
- package/src/types.ts +233 -0
package/src/mcp/oauth.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth support for the local MCP client.
|
|
3
|
+
*
|
|
4
|
+
* Most real-world MCP servers (Linear, Notion, GitHub, Asana,
|
|
5
|
+
* Atlassian) are OAuth-protected. The local client at
|
|
6
|
+
* `@strav/brain/mcp` already supports static bearer tokens via
|
|
7
|
+
* `MCPServer.authorizationToken` — fine for self-hosted servers,
|
|
8
|
+
* useless against any commercial server. This module closes the
|
|
9
|
+
* gap.
|
|
10
|
+
*
|
|
11
|
+
* The flow apps see:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const store = new MemoryOAuthStore()
|
|
15
|
+
* const linear: MCPServer = {
|
|
16
|
+
* name: 'linear',
|
|
17
|
+
* url: 'https://mcp.linear.app',
|
|
18
|
+
* oauth: {
|
|
19
|
+
* redirectUri: 'https://myapp.com/mcp/linear/callback',
|
|
20
|
+
* scope: 'read',
|
|
21
|
+
* store,
|
|
22
|
+
* },
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* try {
|
|
26
|
+
* const client = new MCPClient(linear)
|
|
27
|
+
* await client.connect()
|
|
28
|
+
* } catch (err) {
|
|
29
|
+
* if (err instanceof MCPAuthRequiredError) {
|
|
30
|
+
* // Redirect the user to err.authorizationUrl and remember
|
|
31
|
+
* // who they were (so the callback handler can rebuild the
|
|
32
|
+
* // store with the right per-user state).
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* // Later, in the callback handler:
|
|
37
|
+
* const client = new MCPClient(linear)
|
|
38
|
+
* await client.completeAuthorization(req.query.code)
|
|
39
|
+
* // The store now has tokens; subsequent connect()s succeed.
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* The framework is server-side and headless — it can't redirect
|
|
43
|
+
* the user inline. So instead of blocking on `connect()`, we
|
|
44
|
+
* surface `MCPAuthRequiredError` carrying the authorization URL.
|
|
45
|
+
* Apps redirect the user themselves, then call
|
|
46
|
+
* `MCPClient.completeAuthorization(code)` from their callback
|
|
47
|
+
* route.
|
|
48
|
+
*
|
|
49
|
+
* Multi-tenancy: build a fresh `MCPOAuthStore` per `(user, server)`
|
|
50
|
+
* with the user id baked into the storage keys. The store
|
|
51
|
+
* interface is intentionally per-server (no `userId` arg) so apps
|
|
52
|
+
* pick the boundary that matches their data model.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import type {
|
|
56
|
+
OAuthClientInformation,
|
|
57
|
+
OAuthClientInformationFull,
|
|
58
|
+
OAuthClientMetadata,
|
|
59
|
+
OAuthTokens,
|
|
60
|
+
} from '@modelcontextprotocol/sdk/shared/auth.js'
|
|
61
|
+
import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'
|
|
62
|
+
import { BrainError } from '../brain_error.ts'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Persistence contract for one MCP server's OAuth state.
|
|
66
|
+
*
|
|
67
|
+
* Methods may be sync or async — implementations are free to call
|
|
68
|
+
* a DB, a Redis cache, a file, or hold the state in memory. The
|
|
69
|
+
* framework awaits whichever shape returns.
|
|
70
|
+
*
|
|
71
|
+
* Per-(server) only by design — apps that need per-(user, server)
|
|
72
|
+
* scoping construct a fresh store per request with the user id
|
|
73
|
+
* baked into the underlying storage keys.
|
|
74
|
+
*/
|
|
75
|
+
export interface MCPOAuthStore {
|
|
76
|
+
/** Load the dynamic-client-registration record, or undefined if not registered. */
|
|
77
|
+
clientInformation():
|
|
78
|
+
| OAuthClientInformation
|
|
79
|
+
| undefined
|
|
80
|
+
| Promise<OAuthClientInformation | undefined>
|
|
81
|
+
/** Persist the dynamic-client-registration record after registration succeeds. */
|
|
82
|
+
saveClientInformation(info: OAuthClientInformationFull): void | Promise<void>
|
|
83
|
+
/** Load the active token set, or undefined if the user hasn't authorized. */
|
|
84
|
+
tokens(): OAuthTokens | undefined | Promise<OAuthTokens | undefined>
|
|
85
|
+
/** Persist tokens after authorization completes or a refresh succeeds. */
|
|
86
|
+
saveTokens(tokens: OAuthTokens): void | Promise<void>
|
|
87
|
+
/** Load the PKCE code verifier saved during the authorize step. */
|
|
88
|
+
codeVerifier(): string | Promise<string>
|
|
89
|
+
/** Persist the PKCE code verifier before redirecting to authorize. */
|
|
90
|
+
saveCodeVerifier(verifier: string): void | Promise<void>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Per-server OAuth configuration on `MCPServer.oauth`. */
|
|
94
|
+
export interface MCPOAuthConfig {
|
|
95
|
+
/** Where the user comes back after authorizing. Must match a registered redirect URI on the OAuth server. */
|
|
96
|
+
redirectUri: string
|
|
97
|
+
/** Optional OAuth scopes to request. Some servers require specific scopes. */
|
|
98
|
+
scope?: string
|
|
99
|
+
/** Per-server token + client-info storage. */
|
|
100
|
+
store: MCPOAuthStore
|
|
101
|
+
/** Optional client metadata for dynamic client registration. Defaults to a minimal sane shape. */
|
|
102
|
+
clientMetadata?: Partial<OAuthClientMetadata>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* `MemoryOAuthStore` — in-memory `MCPOAuthStore` implementation.
|
|
107
|
+
*
|
|
108
|
+
* Fine for tests and single-process dev. Production apps with
|
|
109
|
+
* multiple processes or restarts persist to a DB / Redis / KV.
|
|
110
|
+
*/
|
|
111
|
+
export class MemoryOAuthStore implements MCPOAuthStore {
|
|
112
|
+
private _clientInfo: OAuthClientInformationFull | undefined
|
|
113
|
+
private _tokens: OAuthTokens | undefined
|
|
114
|
+
private _verifier: string | undefined
|
|
115
|
+
|
|
116
|
+
clientInformation(): OAuthClientInformation | undefined {
|
|
117
|
+
return this._clientInfo
|
|
118
|
+
}
|
|
119
|
+
saveClientInformation(info: OAuthClientInformationFull): void {
|
|
120
|
+
this._clientInfo = info
|
|
121
|
+
}
|
|
122
|
+
tokens(): OAuthTokens | undefined {
|
|
123
|
+
return this._tokens
|
|
124
|
+
}
|
|
125
|
+
saveTokens(tokens: OAuthTokens): void {
|
|
126
|
+
this._tokens = tokens
|
|
127
|
+
}
|
|
128
|
+
codeVerifier(): string {
|
|
129
|
+
if (this._verifier === undefined) {
|
|
130
|
+
throw new BrainError(
|
|
131
|
+
'MemoryOAuthStore.codeVerifier(): no PKCE verifier saved. The authorization flow must call saveCodeVerifier before requesting the verifier.',
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
return this._verifier
|
|
135
|
+
}
|
|
136
|
+
saveCodeVerifier(verifier: string): void {
|
|
137
|
+
this._verifier = verifier
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Thrown when an MCP server requires the user to authorize before
|
|
143
|
+
* the framework can connect. Apps catch this on `MCPClient.connect()`,
|
|
144
|
+
* redirect the user to `authorizationUrl`, and on the OAuth callback
|
|
145
|
+
* route call `MCPClient.completeAuthorization(code)` to finish the
|
|
146
|
+
* flow.
|
|
147
|
+
*
|
|
148
|
+
* `BrainError` subclass so the typed-exception handler renders it
|
|
149
|
+
* cleanly through the standard pathway.
|
|
150
|
+
*/
|
|
151
|
+
export class MCPAuthRequiredError extends BrainError {
|
|
152
|
+
/** URL the user should be redirected to in order to authorize. */
|
|
153
|
+
readonly authorizationUrl: string
|
|
154
|
+
|
|
155
|
+
constructor(serverName: string, authorizationUrl: string) {
|
|
156
|
+
super(
|
|
157
|
+
`MCPClient(${serverName}): authorization required. Redirect the user to authorizationUrl and call completeAuthorization(code) from your callback route.`,
|
|
158
|
+
{
|
|
159
|
+
context: { server: serverName, authorizationUrl },
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
this.authorizationUrl = authorizationUrl
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Default client metadata used when dynamic client registration
|
|
168
|
+
* fires. Apps override per-server via
|
|
169
|
+
* `MCPOAuthConfig.clientMetadata`.
|
|
170
|
+
*/
|
|
171
|
+
function defaultClientMetadata(redirectUri: string): OAuthClientMetadata {
|
|
172
|
+
return {
|
|
173
|
+
redirect_uris: [redirectUri],
|
|
174
|
+
token_endpoint_auth_method: 'none',
|
|
175
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
176
|
+
response_types: ['code'],
|
|
177
|
+
client_name: 'strav-brain',
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Internal — implements the SDK's `OAuthClientProvider` against an
|
|
183
|
+
* `MCPOAuthStore`. Holds the auth URL captured from the SDK so
|
|
184
|
+
* `MCPClient.connect()` can surface it on `MCPAuthRequiredError`.
|
|
185
|
+
*/
|
|
186
|
+
export class StoreBackedOAuthProvider implements OAuthClientProvider {
|
|
187
|
+
/** Captured auth URL — populated by the SDK when authorization is needed. */
|
|
188
|
+
capturedAuthorizationUrl: URL | undefined
|
|
189
|
+
|
|
190
|
+
constructor(
|
|
191
|
+
private readonly config: MCPOAuthConfig,
|
|
192
|
+
) {}
|
|
193
|
+
|
|
194
|
+
get redirectUrl(): string {
|
|
195
|
+
return this.config.redirectUri
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get clientMetadata(): OAuthClientMetadata {
|
|
199
|
+
return {
|
|
200
|
+
...defaultClientMetadata(this.config.redirectUri),
|
|
201
|
+
...(this.config.scope !== undefined ? { scope: this.config.scope } : {}),
|
|
202
|
+
...(this.config.clientMetadata ?? {}),
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async clientInformation(): Promise<OAuthClientInformation | undefined> {
|
|
207
|
+
return await this.config.store.clientInformation()
|
|
208
|
+
}
|
|
209
|
+
async saveClientInformation(info: OAuthClientInformationFull): Promise<void> {
|
|
210
|
+
await this.config.store.saveClientInformation(info)
|
|
211
|
+
}
|
|
212
|
+
async tokens(): Promise<OAuthTokens | undefined> {
|
|
213
|
+
return await this.config.store.tokens()
|
|
214
|
+
}
|
|
215
|
+
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
216
|
+
await this.config.store.saveTokens(tokens)
|
|
217
|
+
}
|
|
218
|
+
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
|
219
|
+
this.capturedAuthorizationUrl = authorizationUrl
|
|
220
|
+
}
|
|
221
|
+
async saveCodeVerifier(verifier: string): Promise<void> {
|
|
222
|
+
await this.config.store.saveCodeVerifier(verifier)
|
|
223
|
+
}
|
|
224
|
+
async codeVerifier(): Promise<string> {
|
|
225
|
+
return await this.config.store.codeVerifier()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -75,8 +75,12 @@ function buildTool(
|
|
|
75
75
|
name: `${serverName}${NAME_SEPARATOR}${descriptor.name}`,
|
|
76
76
|
description: descriptor.description,
|
|
77
77
|
inputSchema: descriptor.inputSchema,
|
|
78
|
-
async execute(input: unknown,
|
|
79
|
-
const result = await client.callTool(
|
|
78
|
+
async execute(input: unknown, ctx: ToolContext): Promise<string> {
|
|
79
|
+
const result = await client.callTool(
|
|
80
|
+
descriptor.name,
|
|
81
|
+
input,
|
|
82
|
+
ctx.signal !== undefined ? { signal: ctx.signal } : {},
|
|
83
|
+
)
|
|
80
84
|
if (result.isError) {
|
|
81
85
|
return `MCP tool error: ${result.content}`
|
|
82
86
|
}
|
package/src/mcp_server.ts
CHANGED
|
@@ -40,8 +40,24 @@ export interface MCPServer {
|
|
|
40
40
|
* Optional bearer token. Apps source from env vars / secrets
|
|
41
41
|
* managers — never hardcode. The framework forwards this verbatim
|
|
42
42
|
* to the provider's `authorization_token` field.
|
|
43
|
+
*
|
|
44
|
+
* Mutually exclusive with `oauth`. Use `authorizationToken` for
|
|
45
|
+
* self-hosted servers where you control the token; use `oauth`
|
|
46
|
+
* for commercial servers (Linear, Notion, GitHub, ...).
|
|
43
47
|
*/
|
|
44
48
|
authorizationToken?: string
|
|
49
|
+
/**
|
|
50
|
+
* OAuth configuration for servers that require it. When set, the
|
|
51
|
+
* local MCP client (`@strav/brain/mcp`) drives the
|
|
52
|
+
* authorization-code-with-PKCE flow against the server's OAuth
|
|
53
|
+
* endpoints, storing tokens via the supplied `store`. The
|
|
54
|
+
* Anthropic server-side path doesn't use this — Anthropic's
|
|
55
|
+
* connector handles its own auth.
|
|
56
|
+
*
|
|
57
|
+
* Mutually exclusive with `authorizationToken`. See the OAuth
|
|
58
|
+
* section of `docs/brain/guides/mcp.md`.
|
|
59
|
+
*/
|
|
60
|
+
oauth?: import('./mcp/oauth.ts').MCPOAuthConfig
|
|
45
61
|
/** Per-server tool config (allowlist / enable flag). */
|
|
46
62
|
tools?: MCPServerToolConfig
|
|
47
63
|
}
|
package/src/provider.ts
CHANGED
|
@@ -12,16 +12,24 @@
|
|
|
12
12
|
* subclassing.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import type { AgentGenerateResult } from './agent_generate_result.ts'
|
|
15
16
|
import type { AgentResult } from './agent_result.ts'
|
|
17
|
+
import type { AgentStreamEvent } from './agent_stream_event.ts'
|
|
16
18
|
import type { MCPServer } from './mcp_server.ts'
|
|
17
19
|
import type { OutputSchema } from './output_schema.ts'
|
|
18
20
|
import type { Tool } from './tool.ts'
|
|
21
|
+
import type { ToolExecutionError } from './tool_execution_error.ts'
|
|
19
22
|
import type {
|
|
23
|
+
AudioSource,
|
|
20
24
|
ChatOptions,
|
|
21
25
|
ChatResult,
|
|
26
|
+
EmbedOptions,
|
|
27
|
+
EmbedResult,
|
|
22
28
|
GenerateResult,
|
|
23
29
|
Message,
|
|
24
30
|
StreamEvent,
|
|
31
|
+
TranscribeOptions,
|
|
32
|
+
TranscribeResult,
|
|
25
33
|
} from './types.ts'
|
|
26
34
|
|
|
27
35
|
export interface RunWithToolsOptions extends ChatOptions {
|
|
@@ -37,6 +45,30 @@ export interface RunWithToolsOptions extends ChatOptions {
|
|
|
37
45
|
* resulting `mcp_tool_use` / `mcp_tool_result` blocks.
|
|
38
46
|
*/
|
|
39
47
|
mcpServers?: readonly MCPServer[]
|
|
48
|
+
/**
|
|
49
|
+
* Tool-error recovery hook. Called when a tool's `execute` throws
|
|
50
|
+
* — OR when the model called a tool that isn't registered. Two
|
|
51
|
+
* outcomes:
|
|
52
|
+
*
|
|
53
|
+
* - Return a string → the loop continues. The string lands as
|
|
54
|
+
* `tool_result.content` with `isError: true`, the model sees
|
|
55
|
+
* the error and can adapt (try a different approach, ask the
|
|
56
|
+
* user, give up). Recommended for production agents that
|
|
57
|
+
* should survive transient failures.
|
|
58
|
+
*
|
|
59
|
+
* - Return `undefined` (the default when this option is unset)
|
|
60
|
+
* → the framework throws `ToolExecutionError` and the loop
|
|
61
|
+
* aborts. Same behavior as before this option existed.
|
|
62
|
+
*
|
|
63
|
+
* The hook may inspect `error.cause` to filter — e.g., feed back
|
|
64
|
+
* transient HTTP errors but rethrow programmer errors:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* onToolError: (err) =>
|
|
68
|
+
* err.cause instanceof TransientError ? err.cause.message : undefined
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
onToolError?(error: ToolExecutionError): string | undefined
|
|
40
72
|
}
|
|
41
73
|
|
|
42
74
|
export interface Provider {
|
|
@@ -99,4 +131,81 @@ export interface Provider {
|
|
|
99
131
|
schema: OutputSchema<T>,
|
|
100
132
|
options?: ChatOptions,
|
|
101
133
|
): Promise<GenerateResult<T>>
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Tool-loop + structured output combined. Runs the agentic loop
|
|
137
|
+
* with the same tool-handling as `runWithTools`, but pins a
|
|
138
|
+
* JSON-Schema constraint on every turn — so when the model
|
|
139
|
+
* finally answers without calling a tool, its text is JSON
|
|
140
|
+
* matching the schema. Returns the parsed value alongside the
|
|
141
|
+
* loop bookkeeping.
|
|
142
|
+
*
|
|
143
|
+
* Optional on the interface; `BrainManager.generateWithTools`
|
|
144
|
+
* throws `BrainError` when the configured provider lacks it.
|
|
145
|
+
*/
|
|
146
|
+
runWithToolsAndSchema?<T>(
|
|
147
|
+
messages: readonly Message[],
|
|
148
|
+
tools: readonly Tool[],
|
|
149
|
+
schema: OutputSchema<T>,
|
|
150
|
+
options?: RunWithToolsOptions,
|
|
151
|
+
): Promise<AgentGenerateResult<T>>
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Streaming variant of `runWithToolsAndSchema`. Same agentic loop,
|
|
155
|
+
* same schema constraint on every turn — yielded as
|
|
156
|
+
* `AgentStreamEvent<T>`s. The terminal `stop` event carries the
|
|
157
|
+
* parsed `value` + raw `text` alongside the loop bookkeeping.
|
|
158
|
+
*
|
|
159
|
+
* Optional; `BrainManager.streamGenerateWithTools` throws
|
|
160
|
+
* `BrainError` when the chosen provider doesn't implement it.
|
|
161
|
+
*/
|
|
162
|
+
streamWithToolsAndSchema?<T>(
|
|
163
|
+
messages: readonly Message[],
|
|
164
|
+
tools: readonly Tool[],
|
|
165
|
+
schema: OutputSchema<T>,
|
|
166
|
+
options?: RunWithToolsOptions,
|
|
167
|
+
): AsyncIterable<AgentStreamEvent<T>>
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Streaming variant of `runWithTools`. Yields `AgentStreamEvent`s
|
|
171
|
+
* as the loop progresses — text deltas during model turns,
|
|
172
|
+
* `tool_use` / `tool_result` boundaries around tool execution,
|
|
173
|
+
* `iteration_start` / `iteration_end` per round, a terminal
|
|
174
|
+
* `stop` with the full trace + usage.
|
|
175
|
+
*
|
|
176
|
+
* Optional — providers without a streaming tool-loop implementation
|
|
177
|
+
* can omit it; `BrainManager.streamTools` throws `BrainError` in
|
|
178
|
+
* that case.
|
|
179
|
+
*/
|
|
180
|
+
streamWithTools?(
|
|
181
|
+
messages: readonly Message[],
|
|
182
|
+
tools: readonly Tool[],
|
|
183
|
+
options?: RunWithToolsOptions,
|
|
184
|
+
): AsyncIterable<AgentStreamEvent>
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Embeddings — turn one or more text inputs into vectors for
|
|
188
|
+
* similarity search / RAG / clustering. Optional because not
|
|
189
|
+
* every provider exposes an embeddings endpoint (V1: Anthropic
|
|
190
|
+
* and DeepSeek don't; OpenAI, Gemini, Ollama do).
|
|
191
|
+
*/
|
|
192
|
+
embed?(
|
|
193
|
+
texts: readonly string[],
|
|
194
|
+
options?: EmbedOptions,
|
|
195
|
+
): Promise<EmbedResult>
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Audio transcription — convert an audio clip to text.
|
|
199
|
+
* Complements `AudioBlock` (which sends audio + text together
|
|
200
|
+
* to a multimodal chat model) by exposing the dedicated
|
|
201
|
+
* transcription endpoint where the provider has one. V1:
|
|
202
|
+
* OpenAI (Whisper / gpt-4o-transcribe), Ollama (inherits via
|
|
203
|
+
* OpenAI-compat), Gemini (chat-wrap fallback — internally
|
|
204
|
+
* sends an AudioBlock with a "transcribe verbatim" prompt).
|
|
205
|
+
* Anthropic + DeepSeek throw — no transcription API.
|
|
206
|
+
*/
|
|
207
|
+
transcribe?(
|
|
208
|
+
audio: AudioSource,
|
|
209
|
+
options?: TranscribeOptions,
|
|
210
|
+
): Promise<TranscribeResult>
|
|
102
211
|
}
|