@strav/brain 1.0.0-alpha.17 → 1.0.0-alpha.19
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
package/src/thread.ts
CHANGED
|
@@ -35,6 +35,14 @@ export interface ThreadState {
|
|
|
35
35
|
messages: Message[]
|
|
36
36
|
system?: SystemPrompt
|
|
37
37
|
options?: ChatOptions
|
|
38
|
+
/**
|
|
39
|
+
* Last provider response id captured by `send(...)` — restored on
|
|
40
|
+
* `fromJSON` so subsequent sends thread it via
|
|
41
|
+
* `ChatOptions.previousResponseId` automatically. Only ever set
|
|
42
|
+
* when the underlying provider surfaces `responseId` (OpenAI
|
|
43
|
+
* Responses API today).
|
|
44
|
+
*/
|
|
45
|
+
lastResponseId?: string
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export class Thread {
|
|
@@ -42,6 +50,13 @@ export class Thread {
|
|
|
42
50
|
readonly messages: Message[] = []
|
|
43
51
|
readonly system?: SystemPrompt
|
|
44
52
|
readonly options?: ChatOptions
|
|
53
|
+
/**
|
|
54
|
+
* Last response id returned by the provider on this thread. Used to
|
|
55
|
+
* thread stateful-conversation hints (OpenAI Responses API) into
|
|
56
|
+
* the next `send(...)` so apps don't have to manage it manually.
|
|
57
|
+
* `undefined` for providers that don't surface a response id.
|
|
58
|
+
*/
|
|
59
|
+
lastResponseId?: string
|
|
45
60
|
private readonly brain: BrainManager
|
|
46
61
|
|
|
47
62
|
constructor(brain: BrainManager, opts: ThreadOptions = {}) {
|
|
@@ -54,6 +69,11 @@ export class Thread {
|
|
|
54
69
|
* Append a user turn, call the model, append the assistant reply,
|
|
55
70
|
* and return the reply text. Per-call options override the
|
|
56
71
|
* thread's defaults; `system` always comes from the thread.
|
|
72
|
+
*
|
|
73
|
+
* When the underlying provider supports stateful conversations
|
|
74
|
+
* (OpenAI Responses API), `previousResponseId` is auto-threaded
|
|
75
|
+
* from the prior turn — apps don't need to manage it. Per-call
|
|
76
|
+
* `options.previousResponseId` wins if supplied explicitly.
|
|
57
77
|
*/
|
|
58
78
|
async send(text: string, options: ChatOptions = {}): Promise<string> {
|
|
59
79
|
this.messages.push({ role: 'user', content: text })
|
|
@@ -65,8 +85,25 @@ export class Thread {
|
|
|
65
85
|
// mid-thread by changing the system prompt every turn.
|
|
66
86
|
...(this.system !== undefined ? { system: this.system } : {}),
|
|
67
87
|
}
|
|
88
|
+
if (
|
|
89
|
+
merged.previousResponseId === undefined &&
|
|
90
|
+
this.lastResponseId !== undefined
|
|
91
|
+
) {
|
|
92
|
+
merged.previousResponseId = this.lastResponseId
|
|
93
|
+
}
|
|
68
94
|
const result = await this.brain.chat(this.messages, merged)
|
|
69
|
-
|
|
95
|
+
// Preserve structured assistant content when present (compaction
|
|
96
|
+
// blocks today; reasoning blocks later). Round-tripping these
|
|
97
|
+
// back to the provider on subsequent sends is what makes
|
|
98
|
+
// server-side compaction actually save tokens — once a turn
|
|
99
|
+
// carries a `compaction` block, the older raw turns drop out
|
|
100
|
+
// and the model only re-reads the summary.
|
|
101
|
+
if (result.content !== undefined && result.content.length > 0) {
|
|
102
|
+
this.messages.push({ role: 'assistant', content: result.content })
|
|
103
|
+
} else {
|
|
104
|
+
this.messages.push({ role: 'assistant', content: result.text })
|
|
105
|
+
}
|
|
106
|
+
if (result.responseId !== undefined) this.lastResponseId = result.responseId
|
|
70
107
|
return result.text
|
|
71
108
|
}
|
|
72
109
|
|
|
@@ -80,6 +117,7 @@ export class Thread {
|
|
|
80
117
|
const state: ThreadState = { messages: [...this.messages] }
|
|
81
118
|
if (this.system !== undefined) state.system = this.system
|
|
82
119
|
if (this.options !== undefined) state.options = this.options
|
|
120
|
+
if (this.lastResponseId !== undefined) state.lastResponseId = this.lastResponseId
|
|
83
121
|
return state
|
|
84
122
|
}
|
|
85
123
|
|
|
@@ -94,6 +132,7 @@ export class Thread {
|
|
|
94
132
|
if (state.options !== undefined) options.options = state.options
|
|
95
133
|
const thread = new Thread(brain, options)
|
|
96
134
|
for (const m of state.messages) thread.messages.push(m)
|
|
135
|
+
if (state.lastResponseId !== undefined) thread.lastResponseId = state.lastResponseId
|
|
97
136
|
return thread
|
|
98
137
|
}
|
|
99
138
|
}
|
package/src/types.ts
CHANGED
|
@@ -173,6 +173,35 @@ export interface AudioBlock {
|
|
|
173
173
|
| { type: 'url'; url: string }
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Server-side compaction block. Anthropic's `compact-2026-01-12`
|
|
178
|
+
* beta returns a `compaction` block when an auto-compaction trigger
|
|
179
|
+
* fires during a request. The framework surfaces it on
|
|
180
|
+
* `result.content` and Thread persists it on the assistant turn so
|
|
181
|
+
* subsequent requests echo it back verbatim — the model only sees
|
|
182
|
+
* the summary + opaque blob from then on, and the older raw turns
|
|
183
|
+
* stay out of context.
|
|
184
|
+
*
|
|
185
|
+
* V1 produces these on Anthropic only. Other providers ignore the
|
|
186
|
+
* `compact` option silently, and never emit a `CompactionBlock`.
|
|
187
|
+
*
|
|
188
|
+
* Round-trip invariant: pass the block back unchanged. The
|
|
189
|
+
* `encryptedContent` blob is opaque metadata the server uses to
|
|
190
|
+
* stitch the compaction history together; the framework never
|
|
191
|
+
* mutates it.
|
|
192
|
+
*
|
|
193
|
+
* `content === null` means a compaction attempt failed (e.g.,
|
|
194
|
+
* malformed model output). The server treats these as no-ops on
|
|
195
|
+
* the next request, so apps don't need to special-case them.
|
|
196
|
+
*/
|
|
197
|
+
export interface CompactionBlock {
|
|
198
|
+
type: 'compaction'
|
|
199
|
+
/** Summary of compacted content. Null when compaction failed. */
|
|
200
|
+
content: string | null
|
|
201
|
+
/** Opaque metadata round-tripped verbatim on subsequent requests. */
|
|
202
|
+
encryptedContent: string | null
|
|
203
|
+
}
|
|
204
|
+
|
|
176
205
|
export type ContentBlock =
|
|
177
206
|
| TextBlock
|
|
178
207
|
| ImageBlock
|
|
@@ -182,6 +211,7 @@ export type ContentBlock =
|
|
|
182
211
|
| ToolResultBlock
|
|
183
212
|
| MCPToolUseBlock
|
|
184
213
|
| MCPToolResultBlock
|
|
214
|
+
| CompactionBlock
|
|
185
215
|
|
|
186
216
|
/** A single conversation turn. `content` can be a bare string or a typed block list. */
|
|
187
217
|
export interface Message {
|
|
@@ -254,6 +284,36 @@ export type ServerTool =
|
|
|
254
284
|
/** Gemini fetches the URL and surfaces grounded answers from it. */
|
|
255
285
|
}
|
|
256
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Per-call compaction configuration. Maps to Anthropic's
|
|
289
|
+
* `compact-2026-01-12` beta `edits[]` entry. All fields optional —
|
|
290
|
+
* omitting one falls back to the server's default (trigger:
|
|
291
|
+
* 150,000 input tokens; no extra instructions; no pause).
|
|
292
|
+
*/
|
|
293
|
+
export interface CompactConfig {
|
|
294
|
+
/**
|
|
295
|
+
* Trigger threshold in input tokens. Compaction fires once the
|
|
296
|
+
* conversation crosses this token count. Default 150,000 — same
|
|
297
|
+
* as the server-side default.
|
|
298
|
+
*/
|
|
299
|
+
trigger?: number
|
|
300
|
+
/**
|
|
301
|
+
* Extra hint to the summarization model. Useful for biasing the
|
|
302
|
+
* compaction toward what your app actually cares to preserve
|
|
303
|
+
* ("keep all customer ids referenced", "preserve every diff
|
|
304
|
+
* hunk", ...).
|
|
305
|
+
*/
|
|
306
|
+
instructions?: string
|
|
307
|
+
/**
|
|
308
|
+
* When `true`, the server returns the compaction block in-line
|
|
309
|
+
* but does NOT continue generation — the next assistant turn
|
|
310
|
+
* waits for an explicit re-prompt. Apps that want to inspect or
|
|
311
|
+
* gate compaction set this; default `false` (compaction is
|
|
312
|
+
* transparent).
|
|
313
|
+
*/
|
|
314
|
+
pauseAfterCompaction?: boolean
|
|
315
|
+
}
|
|
316
|
+
|
|
257
317
|
export interface ChatOptions {
|
|
258
318
|
/** Override the configured default model. Wins over `tier`. */
|
|
259
319
|
model?: string
|
|
@@ -308,6 +368,36 @@ export interface ChatOptions {
|
|
|
308
368
|
* route to Anthropic / Gemini).
|
|
309
369
|
*/
|
|
310
370
|
serverTools?: readonly ServerTool[]
|
|
371
|
+
/**
|
|
372
|
+
* Server-side conversation compaction. When set, the provider
|
|
373
|
+
* auto-summarizes the older part of the message history once the
|
|
374
|
+
* `trigger` token threshold is reached; the summary lives on the
|
|
375
|
+
* response as a `CompactionBlock` that apps round-trip on
|
|
376
|
+
* subsequent requests (Thread does this automatically). Saves
|
|
377
|
+
* tokens on long threads without lossy client-side pruning.
|
|
378
|
+
*
|
|
379
|
+
* Only honored by `AnthropicProvider` (driver `'anthropic'`),
|
|
380
|
+
* via the `compact-2026-01-12` beta. Silently ignored by every
|
|
381
|
+
* other provider so apps targeting multiple providers with the
|
|
382
|
+
* same options object don't have to special-case.
|
|
383
|
+
*/
|
|
384
|
+
compact?: CompactConfig
|
|
385
|
+
/**
|
|
386
|
+
* Stateful conversation pointer — OpenAI Responses API. When set,
|
|
387
|
+
* the provider sends only the new turn(s); the server picks up
|
|
388
|
+
* from the prior `Response` identified by this id and replays
|
|
389
|
+
* the conversation server-side. Saves tokens on long threads.
|
|
390
|
+
*
|
|
391
|
+
* Only honored by `OpenAIResponsesProvider` (driver
|
|
392
|
+
* `'openai-responses'`); silently ignored by every other provider
|
|
393
|
+
* — apps that target multiple providers with the same options
|
|
394
|
+
* object don't have to special-case.
|
|
395
|
+
*
|
|
396
|
+
* Pair with `ChatResult.responseId` (returned by every call) to
|
|
397
|
+
* thread the conversation forward. `Thread` does this
|
|
398
|
+
* automatically when its underlying provider supports it.
|
|
399
|
+
*/
|
|
400
|
+
previousResponseId?: string
|
|
311
401
|
}
|
|
312
402
|
|
|
313
403
|
/** Token usage for a single call. Cache-hit fields are populated when caching is in play. */
|
|
@@ -330,6 +420,24 @@ export interface ChatResult<Raw = unknown> {
|
|
|
330
420
|
stopReason: string | null
|
|
331
421
|
usage: ChatUsage
|
|
332
422
|
raw: Raw
|
|
423
|
+
/**
|
|
424
|
+
* Structured assistant content blocks — populated when the model
|
|
425
|
+
* emitted more than plain text on this turn (compaction blocks
|
|
426
|
+
* today; reasoning blocks once those surface). Apps that
|
|
427
|
+
* persist the conversation (`Thread`, custom stores) push this
|
|
428
|
+
* onto the message history when present so round-trippable
|
|
429
|
+
* blocks survive subsequent requests. Undefined when the turn
|
|
430
|
+
* was plain text only.
|
|
431
|
+
*/
|
|
432
|
+
content?: ContentBlock[]
|
|
433
|
+
/**
|
|
434
|
+
* Provider response id when the provider exposes stateful
|
|
435
|
+
* conversations (currently OpenAI Responses API). Apps thread
|
|
436
|
+
* this forward via `ChatOptions.previousResponseId` so the
|
|
437
|
+
* server replays prior turns without re-sending them.
|
|
438
|
+
* Undefined for providers that don't support the pattern.
|
|
439
|
+
*/
|
|
440
|
+
responseId?: string
|
|
333
441
|
}
|
|
334
442
|
|
|
335
443
|
/**
|
|
@@ -447,4 +555,6 @@ export interface GenerateResult<T = unknown, Raw = unknown> {
|
|
|
447
555
|
stopReason: string | null
|
|
448
556
|
usage: ChatUsage
|
|
449
557
|
raw: Raw
|
|
558
|
+
/** See `ChatResult.responseId`. */
|
|
559
|
+
responseId?: string
|
|
450
560
|
}
|