@strav/brain 1.0.0-alpha.17 → 1.0.0-alpha.18
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 +4 -2
- package/src/agent_generate_result.ts +2 -0
- package/src/agent_result.ts +7 -0
- package/src/agent_runner.ts +80 -4
- package/src/brain_manager.ts +119 -2
- package/src/index.ts +20 -2
- package/src/mcp/client.ts +17 -0
- package/src/mcp/index.ts +1 -0
- package/src/mcp/pool.ts +106 -0
- package/src/mcp/resolve_mcp_tools.ts +25 -7
- package/src/persistence/brain_message.ts +34 -0
- package/src/persistence/brain_message_repository.ts +106 -0
- package/src/persistence/brain_store.ts +166 -0
- package/src/persistence/brain_suspended_run.ts +30 -0
- package/src/persistence/brain_suspended_run_repository.ts +68 -0
- package/src/persistence/brain_thread.ts +30 -0
- package/src/persistence/brain_thread_repository.ts +65 -0
- package/src/persistence/database_brain_store.ts +190 -0
- package/src/persistence/index.ts +48 -0
- package/src/persistence/schema/brain_message_schema.ts +61 -0
- package/src/persistence/schema/brain_suspended_run_schema.ts +58 -0
- package/src/persistence/schema/brain_thread_schema.ts +50 -0
- package/src/persistence/schema/index.ts +3 -0
- package/src/provider.ts +36 -1
- package/src/providers/anthropic_provider.ts +140 -23
- package/src/providers/gemini_provider.ts +55 -32
- package/src/providers/openai_compat_provider.ts +452 -23
- package/src/providers/openai_provider.ts +87 -32
- package/src/providers/openai_responses_provider.ts +365 -50
- package/src/suspended_run.ts +153 -0
- package/src/thread.ts +40 -1
- package/src/types.ts +110 -0
|
@@ -57,7 +57,12 @@ import type { AgentStreamEvent } from '../agent_stream_event.ts'
|
|
|
57
57
|
import { resolveMcpTools, type ResolveMcpToolsOptions } from '../mcp/resolve_mcp_tools.ts'
|
|
58
58
|
import { parseGenerated, type OutputSchema } from '../output_schema.ts'
|
|
59
59
|
import { recoverOrThrow, runToolWithRecovery } from '../tool_runner.ts'
|
|
60
|
-
import type {
|
|
60
|
+
import type {
|
|
61
|
+
Provider,
|
|
62
|
+
RunWithToolsOptions,
|
|
63
|
+
RunWithToolsOptionsWithSuspend,
|
|
64
|
+
} from '../provider.ts'
|
|
65
|
+
import type { SuspendedRun } from '../suspended_run.ts'
|
|
61
66
|
import type { Tool } from '../tool.ts'
|
|
62
67
|
import { ToolExecutionError } from '../tool_execution_error.ts'
|
|
63
68
|
import type {
|
|
@@ -92,6 +97,16 @@ export interface OpenAIProviderOptions {
|
|
|
92
97
|
* unset; the provider uses the default `MCPClient`.
|
|
93
98
|
*/
|
|
94
99
|
mcpClientFactory?: ResolveMcpToolsOptions['clientFactory']
|
|
100
|
+
/**
|
|
101
|
+
* Optional MCP connection pool. When set, every `runWithTools`
|
|
102
|
+
* call (and its schema / streaming variants) borrows MCP clients
|
|
103
|
+
* from the pool instead of constructing fresh ones — and the
|
|
104
|
+
* per-call cleanup becomes a no-op so transports survive across
|
|
105
|
+
* calls. Apps construct one pool at boot and pass it to every
|
|
106
|
+
* provider that needs local MCP; pool ownership stays on the app
|
|
107
|
+
* via `pool.close()` at shutdown.
|
|
108
|
+
*/
|
|
109
|
+
mcpPool?: ResolveMcpToolsOptions['pool']
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
export class OpenAIProvider implements Provider {
|
|
@@ -108,6 +123,7 @@ export class OpenAIProvider implements Provider {
|
|
|
108
123
|
protected readonly defaultEmbedModel: string
|
|
109
124
|
protected readonly defaultTranscribeModel: string
|
|
110
125
|
protected readonly mcpClientFactory?: ResolveMcpToolsOptions['clientFactory']
|
|
126
|
+
protected readonly mcpPool?: ResolveMcpToolsOptions['pool']
|
|
111
127
|
|
|
112
128
|
constructor(
|
|
113
129
|
name: string,
|
|
@@ -120,6 +136,7 @@ export class OpenAIProvider implements Provider {
|
|
|
120
136
|
this.defaultEmbedModel = config.defaultEmbedModel ?? DEFAULT_OPENAI_EMBED_MODEL
|
|
121
137
|
this.defaultTranscribeModel = config.defaultTranscribeModel ?? DEFAULT_OPENAI_TRANSCRIBE_MODEL
|
|
122
138
|
this.mcpClientFactory = options.mcpClientFactory
|
|
139
|
+
this.mcpPool = options.mcpPool
|
|
123
140
|
this.client =
|
|
124
141
|
options.client ??
|
|
125
142
|
new OpenAI({
|
|
@@ -164,18 +181,22 @@ export class OpenAIProvider implements Provider {
|
|
|
164
181
|
}
|
|
165
182
|
}
|
|
166
183
|
|
|
184
|
+
runWithTools(
|
|
185
|
+
messages: readonly Message[],
|
|
186
|
+
tools: readonly Tool[],
|
|
187
|
+
options: RunWithToolsOptionsWithSuspend,
|
|
188
|
+
): Promise<AgentResult | SuspendedRun>
|
|
189
|
+
runWithTools(
|
|
190
|
+
messages: readonly Message[],
|
|
191
|
+
tools: readonly Tool[],
|
|
192
|
+
options?: RunWithToolsOptions,
|
|
193
|
+
): Promise<AgentResult>
|
|
167
194
|
async runWithTools(
|
|
168
195
|
messages: readonly Message[],
|
|
169
196
|
tools: readonly Tool[],
|
|
170
197
|
options: RunWithToolsOptions = {},
|
|
171
|
-
): Promise<AgentResult> {
|
|
172
|
-
const
|
|
173
|
-
const resolved =
|
|
174
|
-
mcpServers.length > 0
|
|
175
|
-
? await resolveMcpTools(mcpServers, {
|
|
176
|
-
...(this.mcpClientFactory ? { clientFactory: this.mcpClientFactory } : {}),
|
|
177
|
-
})
|
|
178
|
-
: { tools: [] as Tool[], close: async () => {} }
|
|
198
|
+
): Promise<AgentResult | SuspendedRun> {
|
|
199
|
+
const resolved = await this.resolveMcp(options.mcpServers ?? [])
|
|
179
200
|
try {
|
|
180
201
|
return await this._runLoop(messages, [...tools, ...resolved.tools], options)
|
|
181
202
|
} finally {
|
|
@@ -187,7 +208,7 @@ export class OpenAIProvider implements Provider {
|
|
|
187
208
|
messages: readonly Message[],
|
|
188
209
|
tools: readonly Tool[],
|
|
189
210
|
options: RunWithToolsOptions,
|
|
190
|
-
): Promise<AgentResult> {
|
|
211
|
+
): Promise<AgentResult | SuspendedRun> {
|
|
191
212
|
const maxIterations = options.maxIterations ?? 10
|
|
192
213
|
const toolMap = new Map<string, Tool>(tools.map((t) => [t.name, t]))
|
|
193
214
|
const workingMessages: Message[] = [...messages]
|
|
@@ -230,7 +251,8 @@ export class OpenAIProvider implements Provider {
|
|
|
230
251
|
}
|
|
231
252
|
|
|
232
253
|
const resultBlocks: ContentBlock[] = []
|
|
233
|
-
for (
|
|
254
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
255
|
+
const call = toolCalls[i]!
|
|
234
256
|
if (call.type !== 'function') continue
|
|
235
257
|
let parsedInput: unknown
|
|
236
258
|
let parseFailed: { content: string; isError: boolean } | undefined
|
|
@@ -246,6 +268,38 @@ export class OpenAIProvider implements Provider {
|
|
|
246
268
|
options,
|
|
247
269
|
)
|
|
248
270
|
}
|
|
271
|
+
if (options.shouldSuspend && !parseFailed) {
|
|
272
|
+
const frameworkCall: ToolUseBlock = {
|
|
273
|
+
type: 'tool_use',
|
|
274
|
+
id: call.id,
|
|
275
|
+
name: call.function.name,
|
|
276
|
+
input: (parsedInput ?? {}) as Record<string, unknown>,
|
|
277
|
+
}
|
|
278
|
+
if (await options.shouldSuspend(frameworkCall, options.context)) {
|
|
279
|
+
const pending: ToolUseBlock[] = []
|
|
280
|
+
for (let j = i; j < toolCalls.length; j++) {
|
|
281
|
+
const c = toolCalls[j]!
|
|
282
|
+
if (c.type !== 'function') continue
|
|
283
|
+
let pInput: unknown = {}
|
|
284
|
+
try {
|
|
285
|
+
pInput = c.function.arguments ? JSON.parse(c.function.arguments) : {}
|
|
286
|
+
} catch {
|
|
287
|
+
pInput = c.function.arguments ?? {}
|
|
288
|
+
}
|
|
289
|
+
pending.push({
|
|
290
|
+
type: 'tool_use',
|
|
291
|
+
id: c.id,
|
|
292
|
+
name: c.function.name,
|
|
293
|
+
input: pInput as Record<string, unknown>,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
status: 'suspended',
|
|
298
|
+
pendingToolCalls: pending,
|
|
299
|
+
state: { messages: workingMessages, iterations, usage: aggregated },
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
249
303
|
const { content, isError } = parseFailed
|
|
250
304
|
?? (await runToolWithRecovery(
|
|
251
305
|
toolMap.get(call.function.name),
|
|
@@ -282,13 +336,7 @@ export class OpenAIProvider implements Provider {
|
|
|
282
336
|
schema: OutputSchema<T>,
|
|
283
337
|
options: RunWithToolsOptions = {},
|
|
284
338
|
): Promise<AgentGenerateResult<T>> {
|
|
285
|
-
const
|
|
286
|
-
const resolved =
|
|
287
|
-
mcpServers.length > 0
|
|
288
|
-
? await resolveMcpTools(mcpServers, {
|
|
289
|
-
...(this.mcpClientFactory ? { clientFactory: this.mcpClientFactory } : {}),
|
|
290
|
-
})
|
|
291
|
-
: { tools: [] as Tool[], close: async () => {} }
|
|
339
|
+
const resolved = await this.resolveMcp(options.mcpServers ?? [])
|
|
292
340
|
try {
|
|
293
341
|
return await this._runLoopWithSchema([...tools, ...resolved.tools], messages, schema, options)
|
|
294
342
|
} finally {
|
|
@@ -404,13 +452,7 @@ export class OpenAIProvider implements Provider {
|
|
|
404
452
|
tools: readonly Tool[],
|
|
405
453
|
options: RunWithToolsOptions = {},
|
|
406
454
|
): AsyncIterable<AgentStreamEvent> {
|
|
407
|
-
const
|
|
408
|
-
const resolved =
|
|
409
|
-
mcpServers.length > 0
|
|
410
|
-
? await resolveMcpTools(mcpServers, {
|
|
411
|
-
...(this.mcpClientFactory ? { clientFactory: this.mcpClientFactory } : {}),
|
|
412
|
-
})
|
|
413
|
-
: { tools: [] as Tool[], close: async () => {} }
|
|
455
|
+
const resolved = await this.resolveMcp(options.mcpServers ?? [])
|
|
414
456
|
try {
|
|
415
457
|
yield* this._streamLoop(messages, [...tools, ...resolved.tools], options)
|
|
416
458
|
} finally {
|
|
@@ -598,13 +640,7 @@ export class OpenAIProvider implements Provider {
|
|
|
598
640
|
schema: OutputSchema<T>,
|
|
599
641
|
options: RunWithToolsOptions = {},
|
|
600
642
|
): AsyncIterable<AgentStreamEvent<T>> {
|
|
601
|
-
const
|
|
602
|
-
const resolved =
|
|
603
|
-
mcpServers.length > 0
|
|
604
|
-
? await resolveMcpTools(mcpServers, {
|
|
605
|
-
...(this.mcpClientFactory ? { clientFactory: this.mcpClientFactory } : {}),
|
|
606
|
-
})
|
|
607
|
-
: { tools: [] as Tool[], close: async () => {} }
|
|
643
|
+
const resolved = await this.resolveMcp(options.mcpServers ?? [])
|
|
608
644
|
try {
|
|
609
645
|
yield* this._streamLoopWithSchema(
|
|
610
646
|
[...tools, ...resolved.tools],
|
|
@@ -897,6 +933,25 @@ export class OpenAIProvider implements Provider {
|
|
|
897
933
|
}
|
|
898
934
|
}
|
|
899
935
|
|
|
936
|
+
/**
|
|
937
|
+
* Single resolve-MCP entry point used by every tool-loop variant.
|
|
938
|
+
* Threads both the test-only `clientFactory` and the optional
|
|
939
|
+
* `mcpPool` through. Caller invokes `resolved.close()` in
|
|
940
|
+
* `finally`; that's a no-op when the pool owns the lifetime.
|
|
941
|
+
*/
|
|
942
|
+
protected resolveMcp(servers: readonly MCPServer[]): Promise<{
|
|
943
|
+
tools: Tool[]
|
|
944
|
+
close: () => Promise<void>
|
|
945
|
+
}> {
|
|
946
|
+
if (servers.length === 0) {
|
|
947
|
+
return Promise.resolve({ tools: [], close: async () => {} })
|
|
948
|
+
}
|
|
949
|
+
return resolveMcpTools(servers, {
|
|
950
|
+
...(this.mcpClientFactory ? { clientFactory: this.mcpClientFactory } : {}),
|
|
951
|
+
...(this.mcpPool ? { pool: this.mcpPool } : {}),
|
|
952
|
+
})
|
|
953
|
+
}
|
|
954
|
+
|
|
900
955
|
// ─── Param translation ──────────────────────────────────────────────────
|
|
901
956
|
|
|
902
957
|
protected buildParams(
|