@strav/brain 1.0.0-alpha.15 → 1.0.0-alpha.16

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.15",
3
+ "version": "1.0.0-alpha.16",
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",
@@ -24,7 +24,7 @@
24
24
  "@anthropic-ai/sdk": "^0.100.0",
25
25
  "@google/genai": "^2.7.0",
26
26
  "@modelcontextprotocol/sdk": "^1.29.0",
27
- "@strav/kernel": "1.0.0-alpha.15",
27
+ "@strav/kernel": "1.0.0-alpha.16",
28
28
  "openai": "^6.0.0"
29
29
  },
30
30
  "peerDependencies": {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `AgentGenerateResult<T>` — what an Agent run returns when the
3
+ * runner was switched into structured-output mode via
4
+ * `.output(schema)`.
5
+ *
6
+ * Combines the structured-output payload (`value` + raw `text`) with
7
+ * the agent-loop bookkeeping (`messages`, `iterations`, `stopReason`,
8
+ * `usage`) so apps can still render the trace + report token spend
9
+ * the same way they do for `AgentResult`. `iterations` is always `0`
10
+ * in V1 because the structured-output path doesn't engage the
11
+ * tool-use loop — see the docs for the (deferred) "tools + schema"
12
+ * combined slice.
13
+ */
14
+
15
+ import type { ChatUsage, Message } from './types.ts'
16
+
17
+ export interface AgentGenerateResult<T = unknown> {
18
+ /** Parsed structured value matching the supplied `OutputSchema<T>`. */
19
+ value: T
20
+ /** Raw JSON text the model produced — handy for logging when `parse` rejects. */
21
+ text: string
22
+ /** Full message history of the run (single user → assistant turn in V1). */
23
+ messages: Message[]
24
+ /** Always `0` in V1 — the schema path doesn't engage the tool-use loop. */
25
+ iterations: number
26
+ /** Provider-reported terminal stop reason. */
27
+ stopReason: string
28
+ /** Token usage from the single underlying `generate` call. */
29
+ usage: ChatUsage
30
+ }
@@ -2,24 +2,49 @@
2
2
  * `AgentRunner` — fluent builder returned by `BrainManager.agent(Class)`.
3
3
  *
4
4
  * Carries the agent instance + an input message + an optional
5
- * per-run context bag. `run()` translates the agent's declarative
6
- * configuration into a `runWithTools` call and returns the
7
- * `AgentResult`.
5
+ * per-run context bag + an optional structured-output schema.
6
+ * `run()` translates the agent's declarative configuration into
7
+ * either a `runWithTools` call (default) or a `generate` call (when
8
+ * `.output(schema)` was used) and returns the matching result type.
9
+ *
10
+ * Designed to chain:
11
+ *
12
+ * ```ts
13
+ * brain.agent(R).input(text).context({...}).run()
14
+ * brain.agent(R).input(text).output(schema).run() // → AgentGenerateResult<T>
15
+ * ```
8
16
  *
9
- * Designed to chain: `brain.agent(R).input(text).context({...}).run()`.
10
17
  * Apps that need the full Message-array surface bypass the runner
11
- * and call `BrainManager.runTools(messages, tools, options)` directly.
18
+ * and call `BrainManager.runTools(messages, tools, options)` or
19
+ * `BrainManager.generate(input, schema, options)` directly.
12
20
  */
13
21
 
14
22
  import type { Agent } from './agent.ts'
23
+ import type { AgentGenerateResult } from './agent_generate_result.ts'
15
24
  import type { AgentResult } from './agent_result.ts'
16
25
  import type { BrainManager } from './brain_manager.ts'
26
+ import { BrainError } from './brain_error.ts'
27
+ import type { OutputSchema } from './output_schema.ts'
28
+ import type { ChatOptions, Message } from './types.ts'
17
29
  import type { RunWithToolsOptions } from './provider.ts'
18
- import type { Message } from './types.ts'
19
30
 
20
- export class AgentRunner {
31
+ /**
32
+ * Conditional return shape for `AgentRunner.run()`. With the default
33
+ * generic (`T = never`), `run()` returns `AgentResult` — the
34
+ * tool-loop shape. When the runner has been switched into
35
+ * structured-output mode via `.output(schema)`, `T` carries the
36
+ * inferred type and `run()` returns `AgentGenerateResult<T>`.
37
+ *
38
+ * The `[T] extends [never]` form is the standard "is this still the
39
+ * default never?" check — `T extends never` would distribute over
40
+ * union types and break.
41
+ */
42
+ export type AgentRunResult<T> = [T] extends [never] ? AgentResult : AgentGenerateResult<T>
43
+
44
+ export class AgentRunner<T = never> {
21
45
  private prompt: string | undefined
22
46
  private contextBag: Record<string, unknown> = {}
47
+ private schema: OutputSchema<T> | undefined
23
48
 
24
49
  constructor(
25
50
  private readonly brain: BrainManager,
@@ -43,21 +68,79 @@ export class AgentRunner {
43
68
  return this
44
69
  }
45
70
 
46
- async run(): Promise<AgentResult> {
71
+ /**
72
+ * Switch the runner into structured-output mode. `run()` then
73
+ * delegates to `BrainManager.generate(...)` and returns an
74
+ * `AgentGenerateResult<U>` shaped to the supplied schema.
75
+ *
76
+ * V1 caveat: structured output and tool use can't be combined yet.
77
+ * Agents that declare `tools` or `mcpServers` AND call `.output()`
78
+ * throw a `BrainError` at `run()` with a clear "this combination is
79
+ * deferred" message. Apps that need both today run them in two
80
+ * steps — `runTools(...)` for the loop, then `generate(...)` for
81
+ * the structured summary.
82
+ */
83
+ output<U>(schema: OutputSchema<U>): AgentRunner<U> {
84
+ // Mutate in place + cast — the runtime state is a single object;
85
+ // the generic narrows only the static return type. This avoids
86
+ // cloning the prompt + contextBag fields.
87
+ this.schema = schema as unknown as OutputSchema<T>
88
+ return this as unknown as AgentRunner<U>
89
+ }
90
+
91
+ async run(): Promise<AgentRunResult<T>> {
47
92
  if (this.prompt === undefined) {
48
- throw new Error('AgentRunner.run: input() must be called before run().')
93
+ throw new BrainError('AgentRunner.run: input() must be called before run().')
49
94
  }
50
95
  const messages: Message[] = [{ role: 'user', content: this.prompt }]
96
+
97
+ if (this.schema !== undefined) {
98
+ if (this.agent.tools.length > 0 || this.agent.mcpServers.length > 0) {
99
+ throw new BrainError(
100
+ 'AgentRunner.output() does not yet support tool use. The agent declares tools or mcpServers — drop them on the agent, or run runTools(...) and generate(...) as two separate calls. Combined tool + schema lands in a later slice.',
101
+ {
102
+ context: {
103
+ agent: this.agent.constructor.name,
104
+ tools: this.agent.tools.length,
105
+ mcpServers: this.agent.mcpServers.length,
106
+ },
107
+ },
108
+ )
109
+ }
110
+ const generateOptions = this.buildChatOptions()
111
+ const result = await this.brain.generate<T>(messages, this.schema, generateOptions)
112
+ const generateResult: AgentGenerateResult<T> = {
113
+ value: result.value,
114
+ text: result.text,
115
+ messages: [
116
+ ...messages,
117
+ { role: 'assistant', content: result.text },
118
+ ],
119
+ iterations: 0,
120
+ stopReason: result.stopReason ?? 'stop',
121
+ usage: result.usage,
122
+ }
123
+ return generateResult as AgentRunResult<T>
124
+ }
125
+
51
126
  const options: RunWithToolsOptions = {
127
+ ...this.buildChatOptions(),
128
+ maxIterations: this.agent.maxIterations,
129
+ context: this.contextBag,
130
+ }
131
+ if (this.agent.mcpServers.length > 0) options.mcpServers = this.agent.mcpServers
132
+ const result = await this.brain.runTools(messages, this.agent.tools, options)
133
+ return result as AgentRunResult<T>
134
+ }
135
+
136
+ private buildChatOptions(): ChatOptions {
137
+ const options: ChatOptions = {
52
138
  tier: this.agent.tier,
53
139
  maxTokens: this.agent.maxTokens,
54
140
  system: this.agent.instructions,
55
- maxIterations: this.agent.maxIterations,
56
- context: this.contextBag,
57
141
  }
58
142
  if (this.agent.model !== undefined) options.model = this.agent.model
59
143
  if (this.agent.provider !== undefined) options.provider = this.agent.provider
60
- if (this.agent.mcpServers.length > 0) options.mcpServers = this.agent.mcpServers
61
- return this.brain.runTools(messages, this.agent.tools, options)
144
+ return options
62
145
  }
63
146
  }
package/src/index.ts CHANGED
@@ -8,8 +8,9 @@
8
8
  // tools, structured outputs, other providers.
9
9
 
10
10
  export { Agent } from './agent.ts'
11
+ export type { AgentGenerateResult } from './agent_generate_result.ts'
11
12
  export type { AgentResult } from './agent_result.ts'
12
- export { AgentRunner } from './agent_runner.ts'
13
+ export { AgentRunner, type AgentRunResult } from './agent_runner.ts'
13
14
  export {
14
15
  type AnthropicProviderConfig,
15
16
  type BrainCacheConfig,