flowneer 0.6.1 → 0.7.0
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/dist/{FlowBuilder-DssSGM7v.d.ts → FlowBuilder-C3B91Fzo.d.ts} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/patterns-C3oKxgYZ.d.ts +195 -0
- package/dist/plugins/agent/index.d.ts +129 -172
- package/dist/plugins/agent/index.js +169 -0
- package/dist/plugins/dev/index.d.ts +1 -1
- package/dist/plugins/eval/index.d.ts +1 -1
- package/dist/plugins/graph/index.d.ts +48 -2
- package/dist/plugins/graph/index.js +56 -0
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/llm/index.d.ts +1 -1
- package/dist/plugins/memory/index.d.ts +1 -1
- package/dist/plugins/messaging/index.d.ts +1 -1
- package/dist/plugins/observability/index.d.ts +1 -1
- package/dist/plugins/output/index.d.ts +1 -1
- package/dist/plugins/persistence/index.d.ts +1 -1
- package/dist/plugins/resilience/index.d.ts +54 -2
- package/dist/plugins/resilience/index.js +77 -1
- package/dist/plugins/telemetry/index.d.ts +1 -1
- package/dist/plugins/tools/index.d.ts +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -0
- package/package.json +3 -2
|
@@ -250,4 +250,4 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
|
|
|
250
250
|
private _runSub;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
export { FlowBuilder as F, type NodeFn as N, type RunOptions as R, type StepMeta as S, type Validator as V, type
|
|
253
|
+
export { type AnchorStep as A, type BatchStep as B, FlowBuilder as F, type LoopStep as L, type NodeFn as N, type ParallelStep as P, type RunOptions as R, type StepMeta as S, type Validator as V, type FlowneerPlugin as a, type NodeOptions as b, type FlowHooks as c, type BranchStep as d, type FnStep as e, type NumberOrFn as f, type Step as g, type StreamEvent as h };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { F as FlowBuilder, c as FlowHooks,
|
|
1
|
+
export { F as FlowBuilder, c as FlowHooks, a as FlowneerPlugin, N as NodeFn, b as NodeOptions, f as NumberOrFn, R as RunOptions, S as StepMeta, h as StreamEvent, V as Validator } from './FlowBuilder-C3B91Fzo.js';
|
|
2
2
|
export { FlowError, Fragment, InterruptError, fragment } from './src/index.js';
|
package/dist/index.js
CHANGED
|
@@ -453,6 +453,8 @@ var Fragment = class extends FlowBuilder {
|
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
455
|
/** @internal Fragments cannot be streamed — embed them via `.add()`. */
|
|
456
|
+
// v8 ignore next 5 — async generator has an implicit "resume after yield"
|
|
457
|
+
// branch that is unreachable here because we always throw before yielding.
|
|
456
458
|
async *stream(_shared, _params) {
|
|
457
459
|
throw new Error(
|
|
458
460
|
"Fragment cannot be streamed directly \u2014 use .add() to embed it in a FlowBuilder"
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { a as FlowneerPlugin, F as FlowBuilder, N as NodeFn } from './FlowBuilder-C3B91Fzo.js';
|
|
2
|
+
import { ToolCall, ToolResult } from './plugins/tools/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The result of a `think` function.
|
|
6
|
+
*
|
|
7
|
+
* - Return `{ action: "finish", output }` to end the loop.
|
|
8
|
+
* - Return `{ action: "tool", calls }` to invoke tools, the results of
|
|
9
|
+
* which will be available in `shared.__toolResults` on the next iteration.
|
|
10
|
+
*/
|
|
11
|
+
type ThinkResult = {
|
|
12
|
+
action: "finish";
|
|
13
|
+
output?: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
action: "tool";
|
|
16
|
+
calls: ToolCall[];
|
|
17
|
+
};
|
|
18
|
+
interface ReActLoopOptions<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
19
|
+
/**
|
|
20
|
+
* The "think" step — receives shared state (which includes tool results
|
|
21
|
+
* from the previous iteration) and returns the next action.
|
|
22
|
+
*/
|
|
23
|
+
think: (shared: S, params: P) => ThinkResult | Promise<ThinkResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of think → act iterations. Defaults to 10.
|
|
26
|
+
* When exceeded the loop breaks and `shared.__reactExhausted = true`.
|
|
27
|
+
*/
|
|
28
|
+
maxIterations?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Optional callback invoked after each tool execution round.
|
|
31
|
+
* Useful for logging, appending to conversation history, etc.
|
|
32
|
+
*/
|
|
33
|
+
onObservation?: (results: ToolResult[], shared: S, params: P) => void | Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
declare module "../../Flowneer" {
|
|
36
|
+
interface FlowBuilder<S, P> {
|
|
37
|
+
/**
|
|
38
|
+
* Inserts a ReAct agent loop into the flow.
|
|
39
|
+
*
|
|
40
|
+
* The loop runs `think → [tool calls] → observation → repeat` until
|
|
41
|
+
* the think step returns `{ action: "finish" }` or `maxIterations`
|
|
42
|
+
* is reached.
|
|
43
|
+
*
|
|
44
|
+
* Requires `.withTools(tools)` to have been called first so that
|
|
45
|
+
* `shared.__tools` is available.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* flow
|
|
49
|
+
* .withTools([calculatorTool, searchTool])
|
|
50
|
+
* .withReActLoop({
|
|
51
|
+
* think: async (s) => {
|
|
52
|
+
* const response = await callLlm(buildPrompt(s));
|
|
53
|
+
* if (response.toolCalls.length) {
|
|
54
|
+
* return { action: "tool", calls: response.toolCalls };
|
|
55
|
+
* }
|
|
56
|
+
* return { action: "finish", output: response.text };
|
|
57
|
+
* },
|
|
58
|
+
* maxIterations: 5,
|
|
59
|
+
* })
|
|
60
|
+
*/
|
|
61
|
+
withReActLoop(options: ReActLoopOptions<S, P>): this;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
declare const withReActLoop: FlowneerPlugin;
|
|
65
|
+
|
|
66
|
+
interface HumanNodeOptions<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
67
|
+
/**
|
|
68
|
+
* Key on `shared` where the prompt / question for the human is stored.
|
|
69
|
+
* The step will set `shared[promptKey]` before interrupting so the
|
|
70
|
+
* caller knows what input is expected.
|
|
71
|
+
* Defaults to `"__humanPrompt"`.
|
|
72
|
+
*/
|
|
73
|
+
promptKey?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Optional condition — when provided, the node only interrupts when
|
|
76
|
+
* the condition returns `true`. Defaults to always interrupt.
|
|
77
|
+
*/
|
|
78
|
+
condition?: (shared: S, params: P) => boolean | Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* Optional prompt message to store on `shared[promptKey]` before
|
|
81
|
+
* interrupting. If omitted, step uses whatever is already on shared.
|
|
82
|
+
*/
|
|
83
|
+
prompt?: string | ((shared: S, params: P) => string | Promise<string>);
|
|
84
|
+
}
|
|
85
|
+
declare module "../../Flowneer" {
|
|
86
|
+
interface FlowBuilder<S, P> {
|
|
87
|
+
/**
|
|
88
|
+
* Insert a human-in-the-loop pause point.
|
|
89
|
+
*
|
|
90
|
+
* When this step executes it throws an `InterruptError` carrying a
|
|
91
|
+
* deep clone of `shared`. The caller catches the interrupt, obtains
|
|
92
|
+
* human input, and calls `resumeFlow()` to continue.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const flow = new FlowBuilder<MyState>()
|
|
96
|
+
* .startWith(generateDraft)
|
|
97
|
+
* .humanNode({ prompt: "Please review the draft above." })
|
|
98
|
+
* .then(applyFeedback);
|
|
99
|
+
*
|
|
100
|
+
* try {
|
|
101
|
+
* await flow.run(shared);
|
|
102
|
+
* } catch (e) {
|
|
103
|
+
* if (e instanceof InterruptError) {
|
|
104
|
+
* const userInput = await getUserInput(e.savedShared.__humanPrompt);
|
|
105
|
+
* await resumeFlow(flow, e.savedShared, { feedback: userInput }, 2);
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
*/
|
|
109
|
+
humanNode(options?: HumanNodeOptions<S, P>): this;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
declare const withHumanNode: FlowneerPlugin;
|
|
113
|
+
/**
|
|
114
|
+
* Resume a flow from an `InterruptError`.
|
|
115
|
+
*
|
|
116
|
+
* Merges `edits` into the saved shared state, then re-runs the flow
|
|
117
|
+
* starting from step `fromStep` (defaults to 0, which replays from
|
|
118
|
+
* the beginning — combine with `withReplay(fromStep)` to skip already-
|
|
119
|
+
* completed steps).
|
|
120
|
+
*
|
|
121
|
+
* @param flow The same FlowBuilder instance that was interrupted.
|
|
122
|
+
* @param saved The `savedShared` captured by `InterruptError`.
|
|
123
|
+
* @param edits Partial state to merge (human's input / corrections).
|
|
124
|
+
* @param fromStep Step index to effectively resume from (uses `withReplay`).
|
|
125
|
+
*/
|
|
126
|
+
declare function resumeFlow<S extends Record<string, any>>(flow: FlowBuilder<S, any>, saved: S, edits?: Partial<S>, fromStep?: number): Promise<void>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a supervisor → workers pattern.
|
|
130
|
+
*
|
|
131
|
+
* 1. `supervisor` runs first to set up context / assign tasks.
|
|
132
|
+
* 2. All `workers` run in parallel (with an optional reducer).
|
|
133
|
+
* 3. `supervisor` runs again (post step) to aggregate results.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* const flow = supervisorCrew<MyState>(
|
|
137
|
+
* async (s) => { s.tasks = splitIntoChunks(s.input); },
|
|
138
|
+
* [
|
|
139
|
+
* async (s) => { s.results ??= []; s.results.push(await doWork(s)); },
|
|
140
|
+
* async (s) => { s.results ??= []; s.results.push(await doWork(s)); },
|
|
141
|
+
* ],
|
|
142
|
+
* { post: async (s) => { s.summary = combine(s.results); } },
|
|
143
|
+
* );
|
|
144
|
+
*/
|
|
145
|
+
declare function supervisorCrew<S = any, P extends Record<string, unknown> = Record<string, unknown>>(supervisor: NodeFn<S, P>, workers: NodeFn<S, P>[], options?: {
|
|
146
|
+
/** Optional post-parallel supervisor step for aggregation. */
|
|
147
|
+
post?: NodeFn<S, P>;
|
|
148
|
+
/** Optional reducer for parallel isolation. */
|
|
149
|
+
reducer?: (shared: S, drafts: S[]) => void;
|
|
150
|
+
}): FlowBuilder<S, P>;
|
|
151
|
+
/**
|
|
152
|
+
* Creates a strict sequential pipeline: each step runs in order,
|
|
153
|
+
* reading/writing to shared state as a baton pass.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const flow = sequentialCrew<MyState>([
|
|
157
|
+
* async (s) => { s.research = await research(s.query); },
|
|
158
|
+
* async (s) => { s.draft = await writeDraft(s.research); },
|
|
159
|
+
* async (s) => { s.final = await editDraft(s.draft); },
|
|
160
|
+
* ]);
|
|
161
|
+
*/
|
|
162
|
+
declare function sequentialCrew<S = any, P extends Record<string, unknown> = Record<string, unknown>>(steps: NodeFn<S, P>[]): FlowBuilder<S, P>;
|
|
163
|
+
/**
|
|
164
|
+
* Creates a hierarchical pattern: a top-level manager delegates to
|
|
165
|
+
* sub-team flows (each is its own `FlowBuilder` or supervisor crew).
|
|
166
|
+
*
|
|
167
|
+
* The manager runs first, then each team runs sequentially (for safety;
|
|
168
|
+
* use `parallel` within each team if needed), and a final aggregation
|
|
169
|
+
* step runs at the end.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* const flow = hierarchicalCrew<MyState>(
|
|
173
|
+
* async (s) => { s.plan = planTasks(s.input); },
|
|
174
|
+
* [researchTeamFlow, writingTeamFlow],
|
|
175
|
+
* async (s) => { s.output = mergeTeamResults(s); },
|
|
176
|
+
* );
|
|
177
|
+
*/
|
|
178
|
+
declare function hierarchicalCrew<S = any, P extends Record<string, unknown> = Record<string, unknown>>(manager: NodeFn<S, P>, teams: FlowBuilder<S, P>[], aggregate?: NodeFn<S, P>): FlowBuilder<S, P>;
|
|
179
|
+
/**
|
|
180
|
+
* Creates a round-robin debate: each agent step runs in sequence,
|
|
181
|
+
* repeated `rounds` times, appending perspectives to shared state.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* const flow = roundRobinDebate<MyState>(
|
|
185
|
+
* [
|
|
186
|
+
* async (s) => { s.debate.push({ agent: "optimist", text: await optimist(s) }); },
|
|
187
|
+
* async (s) => { s.debate.push({ agent: "critic", text: await critic(s) }); },
|
|
188
|
+
* async (s) => { s.debate.push({ agent: "synthesiser", text: await synth(s) }); },
|
|
189
|
+
* ],
|
|
190
|
+
* 3, // 3 rounds
|
|
191
|
+
* );
|
|
192
|
+
*/
|
|
193
|
+
declare function roundRobinDebate<S = any, P extends Record<string, unknown> = Record<string, unknown>>(agents: NodeFn<S, P>[], rounds: number): FlowBuilder<S, P>;
|
|
194
|
+
|
|
195
|
+
export { type HumanNodeOptions as H, type ReActLoopOptions as R, type ThinkResult as T, roundRobinDebate as a, supervisorCrew as b, withReActLoop as c, hierarchicalCrew as h, resumeFlow as r, sequentialCrew as s, withHumanNode as w };
|
|
@@ -1,195 +1,152 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
export { H as HumanNodeOptions, R as ReActLoopOptions, T as ThinkResult, h as hierarchicalCrew, r as resumeFlow, a as roundRobinDebate, s as sequentialCrew, b as supervisorCrew, w as withHumanNode, c as withReActLoop } from '../../patterns-C3oKxgYZ.js';
|
|
2
|
+
import { F as FlowBuilder } from '../../FlowBuilder-C3B91Fzo.js';
|
|
3
|
+
import { ToolRegistry, ToolResult, Tool, ToolCall, ToolParam } from '../tools/index.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - Return `{ action: "tool", calls }` to invoke tools, the results of
|
|
9
|
-
* which will be available in `shared.__toolResults` on the next iteration.
|
|
6
|
+
* Minimal structural interface that matches a Zod ZodObject.
|
|
7
|
+
* We duck-type against `.shape` so this plugin has zero Zod dependency —
|
|
8
|
+
* pass a real `z.object(...)` and it just works.
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The "think" step — receives shared state (which includes tool results
|
|
21
|
-
* from the previous iteration) and returns the next action.
|
|
22
|
-
*/
|
|
23
|
-
think: (shared: S, params: P) => ThinkResult | Promise<ThinkResult>;
|
|
24
|
-
/**
|
|
25
|
-
* Maximum number of think → act iterations. Defaults to 10.
|
|
26
|
-
* When exceeded the loop breaks and `shared.__reactExhausted = true`.
|
|
27
|
-
*/
|
|
28
|
-
maxIterations?: number;
|
|
29
|
-
/**
|
|
30
|
-
* Optional callback invoked after each tool execution round.
|
|
31
|
-
* Useful for logging, appending to conversation history, etc.
|
|
32
|
-
*/
|
|
33
|
-
onObservation?: (results: ToolResult[], shared: S, params: P) => void | Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
declare module "../../Flowneer" {
|
|
36
|
-
interface FlowBuilder<S, P> {
|
|
37
|
-
/**
|
|
38
|
-
* Inserts a ReAct agent loop into the flow.
|
|
39
|
-
*
|
|
40
|
-
* The loop runs `think → [tool calls] → observation → repeat` until
|
|
41
|
-
* the think step returns `{ action: "finish" }` or `maxIterations`
|
|
42
|
-
* is reached.
|
|
43
|
-
*
|
|
44
|
-
* Requires `.withTools(tools)` to have been called first so that
|
|
45
|
-
* `shared.__tools` is available.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* flow
|
|
49
|
-
* .withTools([calculatorTool, searchTool])
|
|
50
|
-
* .withReActLoop({
|
|
51
|
-
* think: async (s) => {
|
|
52
|
-
* const response = await callLlm(buildPrompt(s));
|
|
53
|
-
* if (response.toolCalls.length) {
|
|
54
|
-
* return { action: "tool", calls: response.toolCalls };
|
|
55
|
-
* }
|
|
56
|
-
* return { action: "finish", output: response.text };
|
|
57
|
-
* },
|
|
58
|
-
* maxIterations: 5,
|
|
59
|
-
* })
|
|
60
|
-
*/
|
|
61
|
-
withReActLoop(options: ReActLoopOptions<S, P>): this;
|
|
62
|
-
}
|
|
10
|
+
interface ZodLikeObject {
|
|
11
|
+
shape: Record<string, {
|
|
12
|
+
_def: {
|
|
13
|
+
typeName: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
};
|
|
16
|
+
isOptional?(): boolean;
|
|
17
|
+
}>;
|
|
63
18
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* caller knows what input is expected.
|
|
71
|
-
* Defaults to `"__humanPrompt"`.
|
|
72
|
-
*/
|
|
73
|
-
promptKey?: string;
|
|
74
|
-
/**
|
|
75
|
-
* Optional condition — when provided, the node only interrupts when
|
|
76
|
-
* the condition returns `true`. Defaults to always interrupt.
|
|
77
|
-
*/
|
|
78
|
-
condition?: (shared: S, params: P) => boolean | Promise<boolean>;
|
|
79
|
-
/**
|
|
80
|
-
* Optional prompt message to store on `shared[promptKey]` before
|
|
81
|
-
* interrupting. If omitted, step uses whatever is already on shared.
|
|
82
|
-
*/
|
|
83
|
-
prompt?: string | ((shared: S, params: P) => string | Promise<string>);
|
|
19
|
+
/** Config when using a Zod-compatible schema. */
|
|
20
|
+
interface ToolConfigSchema<TArgs> {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
schema: ZodLikeObject;
|
|
24
|
+
execute?: (args: TArgs) => unknown | Promise<unknown>;
|
|
84
25
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
* deep clone of `shared`. The caller catches the interrupt, obtains
|
|
92
|
-
* human input, and calls `resumeFlow()` to continue.
|
|
93
|
-
*
|
|
94
|
-
* @example
|
|
95
|
-
* const flow = new FlowBuilder<MyState>()
|
|
96
|
-
* .startWith(generateDraft)
|
|
97
|
-
* .humanNode({ prompt: "Please review the draft above." })
|
|
98
|
-
* .then(applyFeedback);
|
|
99
|
-
*
|
|
100
|
-
* try {
|
|
101
|
-
* await flow.run(shared);
|
|
102
|
-
* } catch (e) {
|
|
103
|
-
* if (e instanceof InterruptError) {
|
|
104
|
-
* const userInput = await getUserInput(e.savedShared.__humanPrompt);
|
|
105
|
-
* await resumeFlow(flow, e.savedShared, { feedback: userInput }, 2);
|
|
106
|
-
* }
|
|
107
|
-
* }
|
|
108
|
-
*/
|
|
109
|
-
humanNode(options?: HumanNodeOptions<S, P>): this;
|
|
110
|
-
}
|
|
26
|
+
/** Config when using plain Flowneer ToolParam definitions. */
|
|
27
|
+
interface ToolConfigParams<TArgs> {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
params: Record<string, ToolParam>;
|
|
31
|
+
execute?: (args: TArgs) => unknown | Promise<unknown>;
|
|
111
32
|
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Resume a flow from an `InterruptError`.
|
|
115
|
-
*
|
|
116
|
-
* Merges `edits` into the saved shared state, then re-runs the flow
|
|
117
|
-
* starting from step `fromStep` (defaults to 0, which replays from
|
|
118
|
-
* the beginning — combine with `withReplay(fromStep)` to skip already-
|
|
119
|
-
* completed steps).
|
|
120
|
-
*
|
|
121
|
-
* @param flow The same FlowBuilder instance that was interrupted.
|
|
122
|
-
* @param saved The `savedShared` captured by `InterruptError`.
|
|
123
|
-
* @param edits Partial state to merge (human's input / corrections).
|
|
124
|
-
* @param fromStep Step index to effectively resume from (uses `withReplay`).
|
|
125
|
-
*/
|
|
126
|
-
declare function resumeFlow<S extends Record<string, any>>(flow: FlowBuilder<S, any>, saved: S, edits?: Partial<S>, fromStep?: number): Promise<void>;
|
|
127
|
-
|
|
33
|
+
type ToolConfig<TArgs> = ToolConfigSchema<TArgs> | ToolConfigParams<TArgs>;
|
|
128
34
|
/**
|
|
129
|
-
*
|
|
35
|
+
* Create a Flowneer `Tool` from an execute function + config.
|
|
130
36
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
37
|
+
* Mirrors LangChain's `tool()` factory. Accepts either:
|
|
38
|
+
* - `schema: z.object(...)` — a Zod-compatible schema (duck-typed, no import needed)
|
|
39
|
+
* - `params: Record<string, ToolParam>` — plain Flowneer param definitions
|
|
134
40
|
*
|
|
135
41
|
* @example
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
42
|
+
* // With Zod schema:
|
|
43
|
+
* const getWeather = tool(
|
|
44
|
+
* ({ city }) => `Always sunny in ${city}!`,
|
|
45
|
+
* {
|
|
46
|
+
* name: "get_weather",
|
|
47
|
+
* description: "Get the weather for a given city",
|
|
48
|
+
* schema: z.object({ city: z.string().describe("The city name") }),
|
|
49
|
+
* },
|
|
143
50
|
* );
|
|
144
|
-
*/
|
|
145
|
-
declare function supervisorCrew<S = any, P extends Record<string, unknown> = Record<string, unknown>>(supervisor: NodeFn<S, P>, workers: NodeFn<S, P>[], options?: {
|
|
146
|
-
/** Optional post-parallel supervisor step for aggregation. */
|
|
147
|
-
post?: NodeFn<S, P>;
|
|
148
|
-
/** Optional reducer for parallel isolation. */
|
|
149
|
-
reducer?: (shared: S, drafts: S[]) => void;
|
|
150
|
-
}): FlowBuilder<S, P>;
|
|
151
|
-
/**
|
|
152
|
-
* Creates a strict sequential pipeline: each step runs in order,
|
|
153
|
-
* reading/writing to shared state as a baton pass.
|
|
154
51
|
*
|
|
155
|
-
*
|
|
156
|
-
* const
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
52
|
+
* // With plain params:
|
|
53
|
+
* const getTime = tool(
|
|
54
|
+
* () => new Date().toISOString(),
|
|
55
|
+
* {
|
|
56
|
+
* name: "get_time",
|
|
57
|
+
* description: "Get the current UTC time",
|
|
58
|
+
* params: {},
|
|
59
|
+
* },
|
|
60
|
+
* );
|
|
161
61
|
*/
|
|
162
|
-
declare function
|
|
62
|
+
declare function tool<TArgs = Record<string, unknown>>(execute: (args: TArgs) => unknown | Promise<unknown>, config: ToolConfig<TArgs>): Tool<TArgs>;
|
|
63
|
+
/** Minimal chat message shape understood by `createAgent`. */
|
|
64
|
+
interface ChatMessage {
|
|
65
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
66
|
+
content: string;
|
|
67
|
+
/** Present on assistant messages when the LLM wants to call tools. */
|
|
68
|
+
tool_calls?: Array<{
|
|
69
|
+
id: string;
|
|
70
|
+
type: "function";
|
|
71
|
+
function: {
|
|
72
|
+
name: string;
|
|
73
|
+
arguments: string;
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
/** Present on tool result messages. */
|
|
77
|
+
tool_call_id?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Tool definition shape forwarded to the LLM. */
|
|
80
|
+
interface LlmToolDef {
|
|
81
|
+
name: string;
|
|
82
|
+
description: string;
|
|
83
|
+
parameters: {
|
|
84
|
+
type: "object";
|
|
85
|
+
properties: Record<string, {
|
|
86
|
+
type: string;
|
|
87
|
+
description: string;
|
|
88
|
+
}>;
|
|
89
|
+
required: string[];
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Response returned by an `LlmAdapter`. */
|
|
93
|
+
interface LlmResponse {
|
|
94
|
+
/** Plain-text reply — present when the model is done. */
|
|
95
|
+
text?: string;
|
|
96
|
+
/** Tool calls — present when the model wants to invoke tools. */
|
|
97
|
+
toolCalls?: ToolCall[];
|
|
98
|
+
}
|
|
163
99
|
/**
|
|
164
|
-
*
|
|
165
|
-
* sub-team flows (each is its own `FlowBuilder` or supervisor crew).
|
|
100
|
+
* A vendor-agnostic LLM adapter.
|
|
166
101
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* step runs at the end.
|
|
102
|
+
* Receives the current conversation history and available tool definitions,
|
|
103
|
+
* returns either a final answer or a list of tool calls.
|
|
170
104
|
*
|
|
171
105
|
* @example
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* [researchTeamFlow, writingTeamFlow],
|
|
175
|
-
* async (s) => { s.output = mergeTeamResults(s); },
|
|
176
|
-
* );
|
|
106
|
+
* // OpenAI adapter (see examples/agentExample.ts for a full implementation)
|
|
107
|
+
* const callLlm: LlmAdapter = async (messages, tools) => { ... };
|
|
177
108
|
*/
|
|
178
|
-
|
|
109
|
+
type LlmAdapter = (messages: ChatMessage[], tools: LlmToolDef[]) => Promise<LlmResponse>;
|
|
110
|
+
interface AgentState {
|
|
111
|
+
/** The user's input prompt. */
|
|
112
|
+
input: string;
|
|
113
|
+
/** The agent's final answer — set after the flow completes. */
|
|
114
|
+
output?: string;
|
|
115
|
+
/** Conversation history accumulated during the run. */
|
|
116
|
+
messages: ChatMessage[];
|
|
117
|
+
/** Optional system prompt (can also be passed to `createAgent`). */
|
|
118
|
+
systemPrompt?: string;
|
|
119
|
+
__tools?: ToolRegistry;
|
|
120
|
+
__reactOutput?: unknown;
|
|
121
|
+
__toolResults?: ToolResult[];
|
|
122
|
+
__reactExhausted?: boolean;
|
|
123
|
+
}
|
|
124
|
+
interface CreateAgentOptions {
|
|
125
|
+
/** Tools the agent can invoke. */
|
|
126
|
+
tools: Tool[];
|
|
127
|
+
/** LLM adapter — call your preferred provider here. */
|
|
128
|
+
callLlm: LlmAdapter;
|
|
129
|
+
/** System prompt inserted before the conversation. */
|
|
130
|
+
systemPrompt?: string;
|
|
131
|
+
/** Maximum think → act iterations. Defaults to 10. */
|
|
132
|
+
maxIterations?: number;
|
|
133
|
+
}
|
|
179
134
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
135
|
+
* Create a reusable agent flow.
|
|
136
|
+
*
|
|
137
|
+
* Returns a `FlowBuilder<AgentState>`. Call `.run(state)` to execute.
|
|
182
138
|
*
|
|
183
139
|
* @example
|
|
184
|
-
* const
|
|
185
|
-
* [
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* );
|
|
140
|
+
* const agent = createAgent({
|
|
141
|
+
* tools: [getWeather, getTime],
|
|
142
|
+
* callLlm: openAiAdapter,
|
|
143
|
+
* systemPrompt: "You are a helpful assistant.",
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* const state: AgentState = { input: "What's the weather in Paris?", messages: [] };
|
|
147
|
+
* await agent.run(state);
|
|
148
|
+
* console.log(state.output);
|
|
192
149
|
*/
|
|
193
|
-
declare function
|
|
150
|
+
declare function createAgent(options: CreateAgentOptions): FlowBuilder<AgentState>;
|
|
194
151
|
|
|
195
|
-
export { type
|
|
152
|
+
export { type AgentState, type ChatMessage, type CreateAgentOptions, type LlmAdapter, type LlmResponse, type LlmToolDef, type ToolConfig, type ToolConfigParams, type ToolConfigSchema, type ZodLikeObject, createAgent, tool };
|
|
@@ -601,12 +601,181 @@ function roundRobinDebate(agents, rounds) {
|
|
|
601
601
|
);
|
|
602
602
|
return flow;
|
|
603
603
|
}
|
|
604
|
+
|
|
605
|
+
// plugins/tools/withTools.ts
|
|
606
|
+
var ToolRegistry = class {
|
|
607
|
+
_tools = /* @__PURE__ */ new Map();
|
|
608
|
+
constructor(tools) {
|
|
609
|
+
for (const t of tools) this._tools.set(t.name, t);
|
|
610
|
+
}
|
|
611
|
+
/** Get a registered tool by name. */
|
|
612
|
+
get(name) {
|
|
613
|
+
return this._tools.get(name);
|
|
614
|
+
}
|
|
615
|
+
/** Check if a tool is registered. */
|
|
616
|
+
has(name) {
|
|
617
|
+
return this._tools.has(name);
|
|
618
|
+
}
|
|
619
|
+
/** List all registered tool names. */
|
|
620
|
+
names() {
|
|
621
|
+
return [...this._tools.keys()];
|
|
622
|
+
}
|
|
623
|
+
/** Return tool definitions as plain objects (suitable for LLM schemas). */
|
|
624
|
+
definitions() {
|
|
625
|
+
return [...this._tools.values()].map((t) => {
|
|
626
|
+
const required = Object.entries(t.params).filter(([, p]) => p.required !== false).map(([k]) => k);
|
|
627
|
+
return {
|
|
628
|
+
name: t.name,
|
|
629
|
+
description: t.description,
|
|
630
|
+
parameters: {
|
|
631
|
+
type: "object",
|
|
632
|
+
properties: Object.fromEntries(
|
|
633
|
+
Object.entries(t.params).map(([k, p]) => [
|
|
634
|
+
k,
|
|
635
|
+
{ type: p.type, description: p.description }
|
|
636
|
+
])
|
|
637
|
+
),
|
|
638
|
+
required
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Execute a single tool call. Returns a `ToolResult`.
|
|
645
|
+
* Catches tool errors and returns them as `{ error }` rather than throwing.
|
|
646
|
+
*/
|
|
647
|
+
async execute(call) {
|
|
648
|
+
const tool2 = this._tools.get(call.name);
|
|
649
|
+
if (!tool2) {
|
|
650
|
+
return {
|
|
651
|
+
callId: call.id,
|
|
652
|
+
name: call.name,
|
|
653
|
+
error: `unknown tool: ${call.name}`
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const result = await tool2.execute(call.args);
|
|
658
|
+
return { callId: call.id, name: call.name, result };
|
|
659
|
+
} catch (err) {
|
|
660
|
+
return {
|
|
661
|
+
callId: call.id,
|
|
662
|
+
name: call.name,
|
|
663
|
+
error: err instanceof Error ? err.message : String(err)
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Execute multiple tool calls concurrently.
|
|
669
|
+
* Returns results in the same order as the input calls.
|
|
670
|
+
*/
|
|
671
|
+
async executeAll(calls) {
|
|
672
|
+
return Promise.all(calls.map((c) => this.execute(c)));
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
var withTools = {
|
|
676
|
+
withTools(tools) {
|
|
677
|
+
const registry = new ToolRegistry(tools);
|
|
678
|
+
this._setHooks({
|
|
679
|
+
beforeFlow: (shared) => {
|
|
680
|
+
shared.__tools = registry;
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
return this;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// plugins/agent/createAgent.ts
|
|
688
|
+
FlowBuilder.use(withTools);
|
|
689
|
+
FlowBuilder.use(withReActLoop);
|
|
690
|
+
function zodTypeToParamType(typeName) {
|
|
691
|
+
switch (typeName) {
|
|
692
|
+
case "ZodNumber":
|
|
693
|
+
return "number";
|
|
694
|
+
case "ZodBoolean":
|
|
695
|
+
return "boolean";
|
|
696
|
+
case "ZodObject":
|
|
697
|
+
return "object";
|
|
698
|
+
case "ZodArray":
|
|
699
|
+
return "array";
|
|
700
|
+
default:
|
|
701
|
+
return "string";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function zodSchemaToParams(schema) {
|
|
705
|
+
const params = {};
|
|
706
|
+
for (const [key, field] of Object.entries(schema.shape)) {
|
|
707
|
+
params[key] = {
|
|
708
|
+
type: zodTypeToParamType(field._def.typeName),
|
|
709
|
+
description: field._def.description ?? key,
|
|
710
|
+
required: field.isOptional ? !field.isOptional() : true
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
return params;
|
|
714
|
+
}
|
|
715
|
+
function isSchemaConfig(cfg) {
|
|
716
|
+
return "schema" in cfg && cfg.schema != null;
|
|
717
|
+
}
|
|
718
|
+
function tool(execute, config) {
|
|
719
|
+
const params = isSchemaConfig(config) ? zodSchemaToParams(config.schema) : config.params;
|
|
720
|
+
return {
|
|
721
|
+
name: config.name,
|
|
722
|
+
description: config.description,
|
|
723
|
+
params,
|
|
724
|
+
execute
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function createAgent(options) {
|
|
728
|
+
const { tools, callLlm, systemPrompt, maxIterations = 10 } = options;
|
|
729
|
+
return new FlowBuilder().withTools(tools).startWith((s) => {
|
|
730
|
+
s.messages = [];
|
|
731
|
+
const sys = systemPrompt ?? s.systemPrompt;
|
|
732
|
+
if (sys) s.messages.push({ role: "system", content: sys });
|
|
733
|
+
s.messages.push({ role: "user", content: s.input });
|
|
734
|
+
}).withReActLoop({
|
|
735
|
+
maxIterations,
|
|
736
|
+
think: async (s) => {
|
|
737
|
+
const toolDefs = s.__tools.definitions();
|
|
738
|
+
const response = await callLlm(s.messages, toolDefs);
|
|
739
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
740
|
+
s.messages.push({
|
|
741
|
+
role: "assistant",
|
|
742
|
+
content: "",
|
|
743
|
+
tool_calls: response.toolCalls.map((tc) => ({
|
|
744
|
+
id: tc.id ?? `call_${tc.name}_${Date.now()}`,
|
|
745
|
+
type: "function",
|
|
746
|
+
function: {
|
|
747
|
+
name: tc.name,
|
|
748
|
+
arguments: JSON.stringify(tc.args)
|
|
749
|
+
}
|
|
750
|
+
}))
|
|
751
|
+
});
|
|
752
|
+
return { action: "tool", calls: response.toolCalls };
|
|
753
|
+
}
|
|
754
|
+
s.messages.push({ role: "assistant", content: response.text ?? "" });
|
|
755
|
+
return { action: "finish", output: response.text };
|
|
756
|
+
},
|
|
757
|
+
// After each tool round, append results so the next think step has context.
|
|
758
|
+
onObservation: (results, s) => {
|
|
759
|
+
for (const r of results) {
|
|
760
|
+
s.messages.push({
|
|
761
|
+
role: "tool",
|
|
762
|
+
tool_call_id: r.callId ?? r.name,
|
|
763
|
+
content: r.error != null ? `Error: ${r.error}` : JSON.stringify(r.result)
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}).then((s) => {
|
|
768
|
+
s.output = typeof s.__reactOutput === "string" ? s.__reactOutput : JSON.stringify(s.__reactOutput ?? "");
|
|
769
|
+
});
|
|
770
|
+
}
|
|
604
771
|
export {
|
|
772
|
+
createAgent,
|
|
605
773
|
hierarchicalCrew,
|
|
606
774
|
resumeFlow,
|
|
607
775
|
roundRobinDebate,
|
|
608
776
|
sequentialCrew,
|
|
609
777
|
supervisorCrew,
|
|
778
|
+
tool,
|
|
610
779
|
withHumanNode,
|
|
611
780
|
withReActLoop
|
|
612
781
|
};
|
|
@@ -1,4 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as FlowneerPlugin, N as NodeFn, b as NodeOptions } from '../../FlowBuilder-C3B91Fzo.js';
|
|
2
|
+
|
|
3
|
+
/** A single node entry in the exported graph. */
|
|
4
|
+
interface GraphNodeExport {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Numeric options that were supplied at `.addNode()` time. */
|
|
7
|
+
options?: {
|
|
8
|
+
retries?: number | string;
|
|
9
|
+
delaySec?: number | string;
|
|
10
|
+
timeoutMs?: number | string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** A single edge entry in the exported graph. */
|
|
14
|
+
interface GraphEdgeExport {
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
/** `true` when the edge has a runtime condition (back-edge / skip-ahead). */
|
|
18
|
+
conditional: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** The root object returned by `.exportGraph("json")`. */
|
|
21
|
+
interface GraphExport {
|
|
22
|
+
format: "json";
|
|
23
|
+
nodes: GraphNodeExport[];
|
|
24
|
+
edges: GraphEdgeExport[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Supported export formats.
|
|
28
|
+
* - `"json"` — JSON-serialisable structure (implemented).
|
|
29
|
+
* - `"mermaid"` — Mermaid flowchart string (reserved for future use).
|
|
30
|
+
*/
|
|
31
|
+
type ExportFormat = "json" | "mermaid";
|
|
32
|
+
type FormatResult<F extends ExportFormat> = F extends "json" ? GraphExport : F extends "mermaid" ? string : never;
|
|
33
|
+
declare module "../../Flowneer" {
|
|
34
|
+
interface FlowBuilder<S, P> {
|
|
35
|
+
/**
|
|
36
|
+
* Export the graph (nodes + edges) in the given format.
|
|
37
|
+
*
|
|
38
|
+
* When only `withExportGraph` is loaded this returns `GraphExport`.
|
|
39
|
+
* Load `withExportFlow` to get the unified `FlowExport` shape instead.
|
|
40
|
+
*
|
|
41
|
+
* Non-JSON formats are reserved for future implementation.
|
|
42
|
+
*/
|
|
43
|
+
exportGraph<F extends ExportFormat>(format: F): FormatResult<F>;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
declare const withExportGraph: FlowneerPlugin;
|
|
2
47
|
|
|
3
48
|
interface GraphNode<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
4
49
|
name: string;
|
|
@@ -42,6 +87,7 @@ declare module "../../Flowneer" {
|
|
|
42
87
|
compile(): this;
|
|
43
88
|
}
|
|
44
89
|
}
|
|
90
|
+
|
|
45
91
|
declare const withGraph: FlowneerPlugin;
|
|
46
92
|
|
|
47
|
-
export { type GraphEdge, type GraphNode, withGraph };
|
|
93
|
+
export { type ExportFormat, type GraphEdge, type GraphEdgeExport, type GraphExport, type GraphNode, type GraphNodeExport, withExportGraph, withGraph };
|
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
// plugins/graph/withExportGraph.ts
|
|
2
|
+
function serializeOption(val) {
|
|
3
|
+
if (val === void 0 || val === null) return void 0;
|
|
4
|
+
if (typeof val === "number") return val === 0 ? void 0 : val;
|
|
5
|
+
if (typeof val === "function") return "<dynamic>";
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
function toJson(nodes, edges) {
|
|
9
|
+
const nodeExports = [];
|
|
10
|
+
for (const node of nodes.values()) {
|
|
11
|
+
const opts = node.options;
|
|
12
|
+
const optExport = opts ? {
|
|
13
|
+
retries: serializeOption(opts.retries),
|
|
14
|
+
delaySec: serializeOption(opts.delaySec),
|
|
15
|
+
timeoutMs: serializeOption(opts.timeoutMs)
|
|
16
|
+
} : void 0;
|
|
17
|
+
const hasOpts = optExport && (optExport.retries !== void 0 || optExport.delaySec !== void 0 || optExport.timeoutMs !== void 0);
|
|
18
|
+
nodeExports.push({
|
|
19
|
+
name: node.name,
|
|
20
|
+
...hasOpts ? { options: optExport } : {}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const edgeExports = edges.map((e) => ({
|
|
24
|
+
from: e.from,
|
|
25
|
+
to: e.to,
|
|
26
|
+
conditional: !!e.condition
|
|
27
|
+
}));
|
|
28
|
+
return { format: "json", nodes: nodeExports, edges: edgeExports };
|
|
29
|
+
}
|
|
30
|
+
function dispatch(format, nodes, edges) {
|
|
31
|
+
switch (format) {
|
|
32
|
+
case "json":
|
|
33
|
+
return toJson(nodes, edges);
|
|
34
|
+
case "mermaid":
|
|
35
|
+
throw new Error(
|
|
36
|
+
'exportGraph: "mermaid" format is not yet implemented. Use "json" for now.'
|
|
37
|
+
);
|
|
38
|
+
default: {
|
|
39
|
+
const _exhaustive = format;
|
|
40
|
+
throw new Error(`exportGraph: unknown format "${_exhaustive}"`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
var withExportGraph = {
|
|
45
|
+
exportGraph(format = "json") {
|
|
46
|
+
const store = this.__graphStore;
|
|
47
|
+
if (!store || store.nodes.size === 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"exportGraph: no graph nodes found. Call .addNode() / .addEdge() before .exportGraph()."
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return dispatch(format, store.nodes, store.edges);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
1
56
|
// plugins/graph/index.ts
|
|
2
57
|
function compileGraph(nodes, edges) {
|
|
3
58
|
const unconditional = edges.filter((e) => !e.condition);
|
|
@@ -132,5 +187,6 @@ function _getGraphStore(builder) {
|
|
|
132
187
|
return builder.__graphStore;
|
|
133
188
|
}
|
|
134
189
|
export {
|
|
190
|
+
withExportGraph,
|
|
135
191
|
withGraph
|
|
136
192
|
};
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ export { RateLimitOptions, withCostTracker, withRateLimit, withTokenBudget } fro
|
|
|
5
5
|
export { withAtomicUpdates, withDryRun, withMocks, withStepLimit } from './dev/index.js';
|
|
6
6
|
export { StreamSubscriber, emit, peekChannel, receiveFrom, sendTo, withChannels, withStream } from './messaging/index.js';
|
|
7
7
|
export { Tool, ToolCall, ToolParam, ToolRegistry, ToolResult, executeTool, executeTools, getTools, withTools } from './tools/index.js';
|
|
8
|
-
export { HumanNodeOptions, ReActLoopOptions, ThinkResult, hierarchicalCrew, resumeFlow, roundRobinDebate, sequentialCrew, supervisorCrew, withHumanNode, withReActLoop } from '
|
|
8
|
+
export { H as HumanNodeOptions, R as ReActLoopOptions, T as ThinkResult, h as hierarchicalCrew, r as resumeFlow, a as roundRobinDebate, s as sequentialCrew, b as supervisorCrew, w as withHumanNode, c as withReActLoop } from '../patterns-C3oKxgYZ.js';
|
|
9
9
|
export { BufferWindowMemory, BufferWindowOptions, KVMemory, Memory, MemoryMessage, SummaryMemory, SummaryMemoryOptions, withMemory } from './memory/index.js';
|
|
10
10
|
export { parseJsonOutput, parseListOutput, parseMarkdownTable, parseRegexOutput } from './output/index.js';
|
|
11
11
|
export { Span, TelemetryDaemon, TelemetryExporter, TelemetryOptions, consoleExporter, otlpExporter, withTelemetry } from './telemetry/index.js';
|
|
12
12
|
export { EvalResult, EvalSummary, ScoreFn, answerRelevance, containsMatch, exactMatch, f1Score, retrievalPrecision, retrievalRecall, runEvalSuite } from './eval/index.js';
|
|
13
13
|
export { GraphEdge, GraphNode, withGraph } from './graph/index.js';
|
|
14
|
-
import '../FlowBuilder-
|
|
14
|
+
import '../FlowBuilder-C3B91Fzo.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as FlowneerPlugin } from '../../FlowBuilder-C3B91Fzo.js';
|
|
2
2
|
|
|
3
3
|
/** Send a message to a named channel on `shared.__channels`. */
|
|
4
4
|
declare function sendTo<S extends Record<string, any>>(shared: S, channel: string, message: unknown): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { N as NodeFn,
|
|
1
|
+
import { N as NodeFn, a as FlowneerPlugin } from '../../FlowBuilder-C3B91Fzo.js';
|
|
2
2
|
|
|
3
3
|
declare module "../../Flowneer" {
|
|
4
4
|
interface FlowBuilder<S, P> {
|
|
@@ -58,4 +58,56 @@ declare module "../../Flowneer" {
|
|
|
58
58
|
}
|
|
59
59
|
declare const withCycles: FlowneerPlugin;
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
declare module "../../Flowneer" {
|
|
62
|
+
interface FlowBuilder<S, P> {
|
|
63
|
+
/**
|
|
64
|
+
* Begin a try / catch / finally block.
|
|
65
|
+
*
|
|
66
|
+
* Executes all steps in `tryFrag`. If any step throws:
|
|
67
|
+
* - the `.catch()` fragment runs (if registered), with the caught error
|
|
68
|
+
* available as `shared.__tryError`;
|
|
69
|
+
* - if no `.catch()` fragment is registered the error propagates.
|
|
70
|
+
*
|
|
71
|
+
* The `.finally()` fragment (if registered) always runs last, regardless
|
|
72
|
+
* of success or failure.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* flow
|
|
76
|
+
* .try(
|
|
77
|
+
* fragment().then(risky).then(another)
|
|
78
|
+
* )
|
|
79
|
+
* .catch(
|
|
80
|
+
* fragment()
|
|
81
|
+
* .then((s) => { s.recovery = true; })
|
|
82
|
+
* .anchor("recover")
|
|
83
|
+
* .then(recoveryStep)
|
|
84
|
+
* )
|
|
85
|
+
* .finally(
|
|
86
|
+
* fragment().then(cleanup)
|
|
87
|
+
* );
|
|
88
|
+
*/
|
|
89
|
+
try(tryFrag: FlowBuilder<S, P>): this;
|
|
90
|
+
/**
|
|
91
|
+
* Register the catch handler for the immediately preceding `.try()` block.
|
|
92
|
+
*
|
|
93
|
+
* The caught error is stored on `shared.__tryError` before the fragment
|
|
94
|
+
* runs and removed once it completes (or throws). Access the original
|
|
95
|
+
* error and its `.cause` inside the catch fragment:
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* .catch(fragment().then((s) => console.error(s.__tryError)))
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
catch(catchFrag: FlowBuilder<S, P>): this;
|
|
102
|
+
/**
|
|
103
|
+
* Register a finally handler that always runs after the preceding `.try()`
|
|
104
|
+
* (and optional `.catch()`) block — whether or not an error was thrown.
|
|
105
|
+
*
|
|
106
|
+
* Clears the pending try/catch state from the builder.
|
|
107
|
+
*/
|
|
108
|
+
finally(finallyFrag: FlowBuilder<S, P>): this;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
declare const withTryCatch: FlowneerPlugin;
|
|
112
|
+
|
|
113
|
+
export { type CircuitBreakerOptions, withCircuitBreaker, withCycles, withFallback, withTimeout, withTryCatch };
|
|
@@ -116,9 +116,85 @@ var withCycles = {
|
|
|
116
116
|
return this;
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
|
+
|
|
120
|
+
// src/errors.ts
|
|
121
|
+
var FlowError = class extends Error {
|
|
122
|
+
step;
|
|
123
|
+
cause;
|
|
124
|
+
constructor(step, cause) {
|
|
125
|
+
super(
|
|
126
|
+
`Flow failed at ${step}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
127
|
+
);
|
|
128
|
+
this.name = "FlowError";
|
|
129
|
+
this.step = step;
|
|
130
|
+
this.cause = cause;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// plugins/resilience/withTryCatch.ts
|
|
135
|
+
var PENDING_KEY = "__flowneerPendingTryCatch";
|
|
136
|
+
var withTryCatch = {
|
|
137
|
+
try(tryFrag) {
|
|
138
|
+
const block = {
|
|
139
|
+
tryFrag,
|
|
140
|
+
catchFrag: null,
|
|
141
|
+
finallyFrag: null
|
|
142
|
+
};
|
|
143
|
+
this[PENDING_KEY] = block;
|
|
144
|
+
this._addFn(async (shared, params) => {
|
|
145
|
+
let thrownError;
|
|
146
|
+
let didThrow = false;
|
|
147
|
+
try {
|
|
148
|
+
await block.tryFrag._execute(shared, params, void 0);
|
|
149
|
+
} catch (tryErr) {
|
|
150
|
+
if (block.catchFrag) {
|
|
151
|
+
shared.__tryError = tryErr instanceof FlowError ? tryErr.cause : tryErr;
|
|
152
|
+
try {
|
|
153
|
+
await block.catchFrag._execute(shared, params, void 0);
|
|
154
|
+
} catch (catchErr) {
|
|
155
|
+
thrownError = catchErr;
|
|
156
|
+
didThrow = true;
|
|
157
|
+
} finally {
|
|
158
|
+
delete shared.__tryError;
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
thrownError = tryErr;
|
|
162
|
+
didThrow = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (block.finallyFrag) {
|
|
166
|
+
await block.finallyFrag._execute(shared, params, void 0);
|
|
167
|
+
}
|
|
168
|
+
if (didThrow) throw thrownError;
|
|
169
|
+
});
|
|
170
|
+
return this;
|
|
171
|
+
},
|
|
172
|
+
catch(catchFrag) {
|
|
173
|
+
const block = this[PENDING_KEY];
|
|
174
|
+
if (!block) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"withTryCatch: .catch() must be called immediately after .try()"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
block.catchFrag = catchFrag;
|
|
180
|
+
return this;
|
|
181
|
+
},
|
|
182
|
+
finally(finallyFrag) {
|
|
183
|
+
const block = this[PENDING_KEY];
|
|
184
|
+
if (!block) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
"withTryCatch: .finally() must be called after .try() or .try().catch()"
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
block.finallyFrag = finallyFrag;
|
|
190
|
+
delete this[PENDING_KEY];
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
119
194
|
export {
|
|
120
195
|
withCircuitBreaker,
|
|
121
196
|
withCycles,
|
|
122
197
|
withFallback,
|
|
123
|
-
withTimeout
|
|
198
|
+
withTimeout,
|
|
199
|
+
withTryCatch
|
|
124
200
|
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FlowBuilder } from '../FlowBuilder-
|
|
2
|
-
export { c as FlowHooks,
|
|
1
|
+
import { F as FlowBuilder } from '../FlowBuilder-C3B91Fzo.js';
|
|
2
|
+
export { A as AnchorStep, B as BatchStep, d as BranchStep, c as FlowHooks, a as FlowneerPlugin, e as FnStep, L as LoopStep, N as NodeFn, b as NodeOptions, f as NumberOrFn, P as ParallelStep, R as RunOptions, g as Step, S as StepMeta, h as StreamEvent, V as Validator } from '../FlowBuilder-C3B91Fzo.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A reusable, composable partial flow — the Flowneer equivalent of a Zod
|
package/dist/src/index.js
CHANGED
|
@@ -453,6 +453,8 @@ var Fragment = class extends FlowBuilder {
|
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
455
|
/** @internal Fragments cannot be streamed — embed them via `.add()`. */
|
|
456
|
+
// v8 ignore next 5 — async generator has an implicit "resume after yield"
|
|
457
|
+
// branch that is unreachable here because we always throw before yielding.
|
|
456
458
|
async *stream(_shared, _params) {
|
|
457
459
|
throw new Error(
|
|
458
460
|
"Fragment cannot be streamed directly \u2014 use .add() to embed it in a FlowBuilder"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowneer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Zero-dependency fluent flow builder for AI agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"dotenv": "17.3.1",
|
|
48
48
|
"openai": "6.22.0",
|
|
49
49
|
"tsup": "^8.5.1",
|
|
50
|
-
"vitepress": "^2.0.0-alpha.16"
|
|
50
|
+
"vitepress": "^2.0.0-alpha.16",
|
|
51
|
+
"vitepress-plugin-llms": "^1.11.0"
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
53
54
|
"typescript": "^5"
|