@strav/brain 0.4.31 → 1.0.0-alpha.8

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/src/workflow.ts DELETED
@@ -1,199 +0,0 @@
1
- import { Workflow as BaseWorkflow } from '@strav/workflow'
2
- import type { WorkflowContext as BaseContext } from '@strav/workflow'
3
- import { AgentRunner } from './helpers.ts'
4
- import type { Agent } from './agent.ts'
5
- import type { AgentResult, SuspendedRun, WorkflowResult, Usage } from './types.ts'
6
-
7
- // ── AI Workflow Context ─────────────────────────────────────────────────────
8
-
9
- export interface WorkflowContext {
10
- input: Record<string, unknown>
11
- results: Record<string, AgentResult>
12
- }
13
-
14
- type StepMapInput = (ctx: WorkflowContext) => Record<string, unknown> | string
15
-
16
- // ── Utilities ───────────────────────────────────────────────────────────────
17
-
18
- function resolveInput(mapInput: StepMapInput | undefined, ctx: BaseContext): string {
19
- if (!mapInput) return JSON.stringify(ctx.input)
20
- const mapped = mapInput(ctx as unknown as WorkflowContext)
21
- return typeof mapped === 'string' ? mapped : JSON.stringify(mapped)
22
- }
23
-
24
- function addUsage(total: Usage, add: Usage): void {
25
- total.inputTokens += add.inputTokens
26
- total.outputTokens += add.outputTokens
27
- total.totalTokens += add.totalTokens
28
- }
29
-
30
- // Workflow orchestration runs agents to completion; suspension is a standalone
31
- // primitive on AgentRunner. Surface a clear error rather than silently swallowing.
32
- function assertCompleted(
33
- result: AgentResult | SuspendedRun,
34
- stepName: string
35
- ): asserts result is AgentResult {
36
- if ((result as SuspendedRun).status === 'suspended') {
37
- throw new Error(
38
- `Workflow step "${stepName}" suspended — Workflow does not support agent suspension. ` +
39
- `Use AgentRunner.run()/resume() directly, or ensure workflow agents don't define shouldSuspend.`
40
- )
41
- }
42
- }
43
-
44
- // ── Workflow Builder ────────────────────────────────────────────────────────
45
-
46
- /**
47
- * Multi-agent workflow orchestrator built on `@strav/workflow`.
48
- *
49
- * Supports sequential steps, parallel fan-out, routing, and loops.
50
- * Each step wraps an Agent execution through the general-purpose workflow engine.
51
- *
52
- * @example
53
- * const result = await brain.workflow('content-pipeline')
54
- * .step('research', ResearchAgent)
55
- * .step('write', WriterAgent, (ctx) => ({
56
- * topic: ctx.results.research.data.summary,
57
- * }))
58
- * .step('review', ReviewerAgent)
59
- * .run({ topic: 'AI in healthcare' })
60
- */
61
- export class Workflow {
62
- private pipeline: BaseWorkflow
63
- private totalUsage: Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
64
-
65
- constructor(name: string) {
66
- this.pipeline = new BaseWorkflow(name)
67
- }
68
-
69
- /**
70
- * Add a sequential step. Runs after all previous steps complete.
71
- * Use `mapInput` to transform context into the agent's input.
72
- */
73
- step(name: string, agent: new () => Agent, mapInput?: StepMapInput): this {
74
- this.pipeline.step(name, async (ctx: BaseContext) => {
75
- const inputText = resolveInput(mapInput, ctx)
76
- const result = await new AgentRunner(agent).input(inputText).run()
77
- assertCompleted(result, name)
78
- addUsage(this.totalUsage, result.usage)
79
- return result
80
- })
81
- return this
82
- }
83
-
84
- /**
85
- * Run multiple agents in parallel. All agents receive the same context.
86
- * Each agent's result is stored under its name in the workflow results.
87
- */
88
- parallel(
89
- name: string,
90
- agents: { name: string; agent: new () => Agent; mapInput?: StepMapInput }[]
91
- ): this {
92
- this.pipeline.parallel(
93
- name,
94
- agents.map(a => ({
95
- name: a.name,
96
- handler: async (ctx: BaseContext) => {
97
- const inputText = resolveInput(a.mapInput, ctx)
98
- const result = await new AgentRunner(a.agent).input(inputText).run()
99
- assertCompleted(result, `${name}.${a.name}`)
100
- addUsage(this.totalUsage, result.usage)
101
- return result
102
- },
103
- }))
104
- )
105
- return this
106
- }
107
-
108
- /**
109
- * Route to a specialized agent based on a router agent's output.
110
- * The router agent should return structured output with a `route` field
111
- * that matches one of the branch keys.
112
- */
113
- route(
114
- name: string,
115
- router: new () => Agent,
116
- branches: Record<string, new () => Agent>,
117
- mapInput?: StepMapInput
118
- ): this {
119
- // Router step: run the router agent, store as `${name}:router`
120
- this.pipeline.step(`${name}:router`, async (ctx: BaseContext) => {
121
- const inputText = resolveInput(mapInput, ctx)
122
- const result = await new AgentRunner(router).input(inputText).run()
123
- assertCompleted(result, `${name}:router`)
124
- addUsage(this.totalUsage, result.usage)
125
- return result
126
- })
127
-
128
- // Branch step: dispatch to the matching branch agent
129
- this.pipeline.route(
130
- name,
131
- (ctx: BaseContext) => {
132
- const routerResult = ctx.results[`${name}:router`] as AgentResult
133
- return routerResult.data?.route ?? routerResult.text?.trim() ?? ''
134
- },
135
- Object.fromEntries(
136
- Object.entries(branches).map(([key, BranchAgent]) => [
137
- key,
138
- async (ctx: BaseContext) => {
139
- const inputText = resolveInput(mapInput, ctx)
140
- const result = await new AgentRunner(BranchAgent).input(inputText).run()
141
- assertCompleted(result, `${name}:${key}`)
142
- addUsage(this.totalUsage, result.usage)
143
- return result
144
- },
145
- ])
146
- )
147
- )
148
- return this
149
- }
150
-
151
- /**
152
- * Run an agent in a loop until a condition is met or max iterations reached.
153
- * Use `feedback` to transform the result into the next iteration's input.
154
- */
155
- loop(
156
- name: string,
157
- agent: new () => Agent,
158
- options: {
159
- maxIterations: number
160
- until?: (result: AgentResult, iteration: number) => boolean
161
- feedback?: (result: AgentResult) => string
162
- mapInput?: StepMapInput
163
- }
164
- ): this {
165
- this.pipeline.loop(
166
- name,
167
- async (input: unknown, _ctx: BaseContext) => {
168
- const result = await new AgentRunner(agent).input(String(input)).run()
169
- assertCompleted(result, name)
170
- addUsage(this.totalUsage, result.usage)
171
- return result
172
- },
173
- {
174
- maxIterations: options.maxIterations,
175
- until: options.until
176
- ? (result: unknown, iteration: number) => options.until!(result as AgentResult, iteration)
177
- : undefined,
178
- feedback: options.feedback
179
- ? (result: unknown) => options.feedback!(result as AgentResult)
180
- : undefined,
181
- mapInput: options.mapInput
182
- ? (ctx: BaseContext) => resolveInput(options.mapInput, ctx)
183
- : (ctx: BaseContext) => JSON.stringify(ctx.input),
184
- }
185
- )
186
- return this
187
- }
188
-
189
- /** Execute the workflow. */
190
- async run(input: Record<string, unknown>): Promise<WorkflowResult> {
191
- this.totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
192
- const result = await this.pipeline.run(input)
193
- return {
194
- results: result.results as Record<string, AgentResult>,
195
- usage: this.totalUsage,
196
- duration: result.duration,
197
- }
198
- }
199
- }
package/tsconfig.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "include": ["src/**/*.ts"],
4
- "exclude": ["node_modules", "tests"]
5
- }