@falai/agent 2.1.1 → 2.2.1
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/LICENSE +21 -0
- package/dist/adapters/SQLiteAdapter.d.ts +3 -3
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +3 -3
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/index.d.ts +1 -1
- package/dist/cjs/adapters/index.d.ts.map +1 -1
- package/dist/cjs/core/BranchEvaluator.d.ts +2 -2
- package/dist/cjs/core/BranchEvaluator.js +2 -2
- package/dist/cjs/core/BranchEvaluator.js.map +1 -1
- package/dist/cjs/core/Flow.d.ts +3 -7
- package/dist/cjs/core/Flow.d.ts.map +1 -1
- package/dist/cjs/core/Flow.js +2 -8
- package/dist/cjs/core/Flow.js.map +1 -1
- package/dist/cjs/core/FlowRouter.js +2 -2
- package/dist/cjs/core/FlowRouter.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +8 -20
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +2 -11
- package/dist/cjs/core/PromptSectionCache.d.ts.map +1 -1
- package/dist/cjs/core/PromptSectionCache.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +5 -5
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +38 -182
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/Step.d.ts +2 -1
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +2 -1
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +1 -33
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +2 -189
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +6 -11
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js +0 -9
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/flow.d.ts +4 -4
- package/dist/cjs/types/history.d.ts +0 -2
- package/dist/cjs/types/history.d.ts.map +1 -1
- package/dist/cjs/types/history.js.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -6
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +1 -20
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/prompt-cache.d.ts +15 -0
- package/dist/cjs/types/prompt-cache.d.ts.map +1 -0
- package/dist/cjs/types/prompt-cache.js +6 -0
- package/dist/cjs/types/prompt-cache.js.map +1 -0
- package/dist/cjs/types/schema.d.ts +0 -7
- package/dist/cjs/types/schema.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/utils/history.d.ts +0 -5
- package/dist/cjs/utils/history.d.ts.map +1 -1
- package/dist/cjs/utils/history.js +0 -8
- package/dist/cjs/utils/history.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +2 -2
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +1 -4
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/template.d.ts +0 -2
- package/dist/cjs/utils/template.d.ts.map +1 -1
- package/dist/cjs/utils/template.js +0 -2
- package/dist/cjs/utils/template.js.map +1 -1
- package/dist/core/BranchEvaluator.d.ts +2 -2
- package/dist/core/BranchEvaluator.js +2 -2
- package/dist/core/BranchEvaluator.js.map +1 -1
- package/dist/core/Flow.d.ts +3 -7
- package/dist/core/Flow.d.ts.map +1 -1
- package/dist/core/Flow.js +2 -8
- package/dist/core/Flow.js.map +1 -1
- package/dist/core/FlowRouter.js +2 -2
- package/dist/core/FlowRouter.js.map +1 -1
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +8 -20
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +2 -11
- package/dist/core/PromptSectionCache.d.ts.map +1 -1
- package/dist/core/PromptSectionCache.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +5 -5
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +38 -182
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/Step.d.ts +2 -1
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +2 -1
- package/dist/core/Step.js.map +1 -1
- package/dist/core/ToolManager.d.ts +1 -33
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +2 -189
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +6 -11
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +1 -8
- package/dist/types/agent.js.map +1 -1
- package/dist/types/flow.d.ts +4 -4
- package/dist/types/history.d.ts +0 -2
- package/dist/types/history.d.ts.map +1 -1
- package/dist/types/history.js.map +1 -1
- package/dist/types/index.d.ts +1 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +1 -1
- package/dist/types/prompt-cache.d.ts +15 -0
- package/dist/types/prompt-cache.d.ts.map +1 -0
- package/dist/types/prompt-cache.js +5 -0
- package/dist/types/prompt-cache.js.map +1 -0
- package/dist/types/schema.d.ts +0 -7
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/history.d.ts +0 -5
- package/dist/utils/history.d.ts.map +1 -1
- package/dist/utils/history.js +0 -7
- package/dist/utils/history.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/template.d.ts +0 -2
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +0 -2
- package/dist/utils/template.js.map +1 -1
- package/docs/guides/branching.md +3 -1
- package/docs/guides/conditions.md +9 -8
- package/docs/guides/instructions.md +7 -7
- package/docs/reference/branches.md +2 -2
- package/docs/reference/flow.md +1 -1
- package/docs/reference/instruction.md +7 -5
- package/docs/reference/step.md +2 -2
- package/package.json +17 -27
- package/src/adapters/SQLiteAdapter.ts +3 -3
- package/src/adapters/index.ts +1 -1
- package/src/core/BranchEvaluator.ts +4 -4
- package/src/core/Flow.ts +2 -10
- package/src/core/FlowRouter.ts +2 -2
- package/src/core/PromptComposer.ts +9 -20
- package/src/core/PromptSectionCache.ts +2 -13
- package/src/core/ResponseModal.ts +39 -197
- package/src/core/Step.ts +2 -1
- package/src/core/ToolManager.ts +2 -234
- package/src/index.ts +2 -3
- package/src/types/agent.ts +6 -12
- package/src/types/flow.ts +4 -4
- package/src/types/history.ts +0 -4
- package/src/types/index.ts +2 -9
- package/src/types/prompt-cache.ts +17 -0
- package/src/types/schema.ts +0 -8
- package/src/types/tool.ts +0 -2
- package/src/utils/history.ts +1 -7
- package/src/utils/index.ts +0 -3
- package/src/utils/template.ts +0 -2
- package/dist/cjs/types/routing.d.ts +0 -12
- package/dist/cjs/types/routing.d.ts.map +0 -1
- package/dist/cjs/types/routing.js +0 -3
- package/dist/cjs/types/routing.js.map +0 -1
- package/dist/types/routing.d.ts +0 -12
- package/dist/types/routing.d.ts.map +0 -1
- package/dist/types/routing.js +0 -2
- package/dist/types/routing.js.map +0 -1
- package/src/types/routing.ts +0 -13
|
@@ -7,7 +7,7 @@ order: 1
|
|
|
7
7
|
|
|
8
8
|
# When and if
|
|
9
9
|
|
|
10
|
-
Conditions decide whether a flow activates, a step runs, an instruction
|
|
10
|
+
Conditions decide whether a flow activates, a step runs, an instruction applies, or a branch fires. v2 splits them into two distinct fields with different evaluators:
|
|
11
11
|
|
|
12
12
|
- `when` — strings the LLM evaluates against intent and the conversation. Costs tokens.
|
|
13
13
|
- `if` — TypeScript predicates the engine evaluates locally. Free.
|
|
@@ -38,19 +38,19 @@ If the answer lives in `data`, `context`, or `session`, it's `if`. If the answer
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Multiple strings combine with **
|
|
41
|
+
Multiple strings combine with **OR** semantics — any clause may pass for the condition to match. Use the array form for alternative natural-language expressions of the same intent:
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
44
|
{
|
|
45
|
-
title: "
|
|
45
|
+
title: "Address",
|
|
46
46
|
when: [
|
|
47
|
-
"the user
|
|
48
|
-
"the user
|
|
47
|
+
"the user asked about the address",
|
|
48
|
+
"the user asked where we are located",
|
|
49
49
|
],
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
The strings are sent to the LLM as part of the routing or activation prompt. Keep
|
|
53
|
+
The strings are sent to the LLM as part of the routing or activation prompt. For instructions, the string is appended to the instruction bullet so the response model can apply the instruction conditionally. Keep conditions short, intent-shaped, and free of code-style boolean expressions — `"the user wants to cancel"` lands; `"data.cancelRequested === true"` does not.
|
|
54
54
|
|
|
55
55
|
## `if` — code predicates
|
|
56
56
|
|
|
@@ -120,7 +120,7 @@ The same `when` / `if` shape attaches to four primitives. Semantics are identica
|
|
|
120
120
|
| --------------- | ----------------------- | -------------------------------------------------- |
|
|
121
121
|
| Flow | `FlowOptions.when/if` | Whether the router selects this flow this turn |
|
|
122
122
|
| Step | `StepOptions.when/if` | Whether the step is reachable in the current flow |
|
|
123
|
-
| Instruction | `Instruction.when/if` | Whether the instruction
|
|
123
|
+
| Instruction | `Instruction.when/if` | Whether the instruction applies to the response |
|
|
124
124
|
| BranchEntry | `BranchEntry.when/if` | Whether this branch entry matches inside `step.branches` |
|
|
125
125
|
|
|
126
126
|
```typescript
|
|
@@ -160,7 +160,8 @@ A short checklist before shipping a condition:
|
|
|
160
160
|
|
|
161
161
|
- Does it read a field, flag, or context value? Use `if`.
|
|
162
162
|
- Does it interpret natural language? Use `when`.
|
|
163
|
-
- Multiple
|
|
163
|
+
- Multiple natural-language alternatives where any may match? Put them in `when` — arrays use OR.
|
|
164
|
+
- Multiple code predicates that must all pass? Put them in `if` — arrays use AND.
|
|
164
165
|
- Need to skip when a value is already collected? `step.skip` (OR semantics).
|
|
165
166
|
- Both fields set? `if` runs first, free; `when` only fires if `if` passes.
|
|
166
167
|
|
|
@@ -62,7 +62,7 @@ createAgent({
|
|
|
62
62
|
});
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
Reach narrows as you nest. Agent-scope instructions render on every turn for every flow. Flow-scope instructions render only while that flow is active. Step-scope instructions render only while that step is current. Conditions on
|
|
65
|
+
Reach narrows as you nest. Agent-scope instructions render on every turn for every flow. Flow-scope instructions render only while that flow is active. Step-scope instructions render only while that step is current. Conditions apply on top: `if` removes an instruction locally when its predicate fails, while `when` is rendered with the instruction so the model can decide whether the natural-language condition applies.
|
|
66
66
|
|
|
67
67
|
The composer renders the resolved set under a single `## Instructions` heading and tags each line with a scope caption so the model can read both *what* and *where from* in one pass:
|
|
68
68
|
|
|
@@ -74,28 +74,28 @@ The composer renders the resolved set under a single `## Instructions` heading a
|
|
|
74
74
|
|
|
75
75
|
## Rendering format
|
|
76
76
|
|
|
77
|
-
Each
|
|
77
|
+
Each eligible instruction lands in the prompt as a single bullet:
|
|
78
78
|
|
|
79
79
|
```
|
|
80
|
-
- [<kind>] [<scope>] <prompt>
|
|
80
|
+
- [<kind>] [<scope>] <prompt> (apply only when: <when-clause> OR <when-clause>)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
A composed block looks like this:
|
|
83
|
+
The condition suffix is omitted when `when` is not set. A composed block looks like this:
|
|
84
84
|
|
|
85
85
|
```
|
|
86
86
|
## Instructions
|
|
87
87
|
|
|
88
88
|
- [must] [Always] Validate dates are in the future before booking.
|
|
89
89
|
- [never] [Always] Promise rates you have not looked up.
|
|
90
|
-
- [should] [In: Booking] Offer to compare two options before committing.
|
|
90
|
+
- [should] [In: Booking] Offer to compare two options before committing. (apply only when: the user is comparing hotel options)
|
|
91
91
|
- [must] [Step: payment] If the card is declined, never retry without confirmation.
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
The format is fixed. The kind prefix is always present (defaulting to `[should]`), the scope caption is always present, and the prompt text follows verbatim.
|
|
94
|
+
The format is fixed. The kind prefix is always present (defaulting to `[should]`), the scope caption is always present, and the prompt text follows verbatim. When present, `when` clauses are joined with `OR` and appended for the model to evaluate.
|
|
95
95
|
|
|
96
96
|
## `appliedInstructions` on the response
|
|
97
97
|
|
|
98
|
-
Every `respond()` call returns an `appliedInstructions` array listing exactly which instructions were rendered into that turn's prompt. The set is deterministic — it comes from the prompt composer, not the model — so you can use it for observability, audits, and tests:
|
|
98
|
+
Every `respond()` call returns an `appliedInstructions` array listing exactly which instructions were rendered into that turn's prompt. The set is deterministic — it comes from the prompt composer, not the model — so you can use it for observability, audits, and tests. For an instruction with `when`, inclusion means the conditional instruction reached the model; it does not claim that the model judged the condition true:
|
|
99
99
|
|
|
100
100
|
```typescript
|
|
101
101
|
const response = await agent.respond("I want to book a room.");
|
|
@@ -19,7 +19,7 @@ Branches resolve **after** the step's post-LLM phase (tool execution, `finalize`
|
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
21
|
interface BranchEntry<TContext = unknown, TData = unknown> {
|
|
22
|
-
/** AI-evaluated condition. String or array of strings (
|
|
22
|
+
/** AI-evaluated condition. String or array of strings (OR semantics). */
|
|
23
23
|
when?: string | string[];
|
|
24
24
|
|
|
25
25
|
/** Code predicate. Function or array of functions (AND semantics). */
|
|
@@ -64,7 +64,7 @@ interface BranchPredicateContext<TContext = unknown, TData = unknown> {
|
|
|
64
64
|
|
|
65
65
|
| Field | Type | Required | Default | Notes |
|
|
66
66
|
|-------|------|----------|---------|-------|
|
|
67
|
-
| `when` | `string \| string[]` | no | — | AI-evaluated condition. Reuses the same machinery as `step.when`. Only evaluated if `if` passes (or is absent). Costs LLM tokens. |
|
|
67
|
+
| `when` | `string \| string[]` | no | — | AI-evaluated condition. Arrays use OR semantics. Reuses the same machinery as `step.when`. Only evaluated if `if` passes (or is absent). Costs LLM tokens. |
|
|
68
68
|
| `if` | `BranchPredicate \| BranchPredicate[]` | no | — | Code predicate. Free to evaluate. When both `when` and `if` are set, `if` runs first; `when` is only evaluated if all `if` predicates pass. |
|
|
69
69
|
| `then` | `string \| Directive` | yes | — | Target. See [Resolution of `then`](#resolution-of-then) below. |
|
|
70
70
|
| `label` | `string` | no | — | Optional label surfaced in event traces and flow visualization. |
|
package/docs/reference/flow.md
CHANGED
|
@@ -77,7 +77,7 @@ class Flow<TContext = unknown, TData = unknown> {
|
|
|
77
77
|
| `id` | `string` | no | derived from `title` | Stable identifier. Auto-generated deterministically from the title when omitted. |
|
|
78
78
|
| `title` | `string` | yes | — | Human-readable name. Shown to the router and used as the default flow id. |
|
|
79
79
|
| `description` | `string` | no | — | One-line summary surfaced to the router prompt. |
|
|
80
|
-
| `when` | `string \| string[]` | no | — | AI-evaluated activation condition(s). Strings only — functions belong on `if`. Multiple strings combine with
|
|
80
|
+
| `when` | `string \| string[]` | no | — | AI-evaluated activation condition(s). Strings only — functions belong on `if`. Multiple strings combine with OR semantics. |
|
|
81
81
|
| `if` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated activation condition(s). Free to evaluate. When both are set, `if` runs first; `when` only evaluates if `if` passes. |
|
|
82
82
|
| `instructions` | `Instruction<TContext, TData>[]` | no | `[]` | Flow-scoped instructions. Apply only while this flow is active. See [Instruction](./instruction.md). |
|
|
83
83
|
| `tools` | `(string \| Tool)[]` | no | `[]` | Tool ids (resolved via the agent's tool registry) or inline `Tool` objects. Available only while this flow is active. |
|
|
@@ -11,7 +11,7 @@ order: 5
|
|
|
11
11
|
|
|
12
12
|
An `Instruction` is a single statement of behavior the agent should follow. v2 collapses three v1 types into one — every instruction now carries a `kind` discriminator (`'must'`, `'never'`, or `'should'`) and a `prompt` that is rendered into the system prompt with a scope caption. The same shape works at agent, flow, and step scope; only its position in the configuration changes.
|
|
13
13
|
|
|
14
|
-
The set of instructions actually rendered into a given turn's prompt is reported back on the response as `appliedInstructions` — observability is deterministic, derived from rendering, not self-reported by the model.
|
|
14
|
+
The set of instructions actually rendered into a given turn's prompt is reported back on the response as `appliedInstructions` — observability is deterministic, derived from rendering, not self-reported by the model. For instructions with a textual `when`, this means the condition was presented to the model, not that the model reported a match.
|
|
15
15
|
|
|
16
16
|
## Signature
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ The set of instructions actually rendered into a given turn's prompt is reported
|
|
|
19
19
|
interface Instruction<TContext = unknown, TData = unknown> {
|
|
20
20
|
id?: string;
|
|
21
21
|
kind?: 'must' | 'never' | 'should'; // default: 'should'
|
|
22
|
-
when?: ConditionWhen; // AI-evaluated string(s),
|
|
22
|
+
when?: ConditionWhen; // AI-evaluated string(s), OR semantics
|
|
23
23
|
if?: ConditionIf<TContext, TData>; // code-evaluated function(s), AND semantics
|
|
24
24
|
prompt: Template<TContext, TData>;
|
|
25
25
|
enabled?: boolean; // default: true
|
|
@@ -48,7 +48,7 @@ interface AppliedInstruction {
|
|
|
48
48
|
|-------|------|----------|---------|-------|
|
|
49
49
|
| `prompt` | `Template<TContext, TData>` | yes | — | Behavioral text rendered into the prompt under the `## Instructions` section. |
|
|
50
50
|
| `kind` | `'must' \| 'never' \| 'should'` | no | `'should'` | Severity. `'must'` = absolute do, `'never'` = absolute don't, `'should'` = conditional nudge. |
|
|
51
|
-
| `when` | `ConditionWhen` | no | — | AI-evaluated activation string (or array,
|
|
51
|
+
| `when` | `ConditionWhen` | no | — | AI-evaluated activation string (or array, OR semantics). Functions are not allowed here; use `if`. |
|
|
52
52
|
| `if` | `ConditionIf<TContext, TData>` | no | — | Code-evaluated activation function (or array). Free to evaluate. When both `when` and `if` are set, `if` runs first; `when` is only evaluated if `if` passes. |
|
|
53
53
|
| `id` | `string` | no | auto | Stable identifier used in `AppliedInstruction.id`. Auto-generated when omitted. |
|
|
54
54
|
| `enabled` | `boolean` | no | `true` | Set `false` to skip the instruction without removing it from configuration. |
|
|
@@ -71,12 +71,14 @@ The same `Instruction` shape attaches at three positions:
|
|
|
71
71
|
- **Flow:** `FlowOptions.instructions` — considered when the active flow matches.
|
|
72
72
|
- **Step:** `StepOptions.instructions` — considered when the active step matches.
|
|
73
73
|
|
|
74
|
-
At prompt-build time the composer renders each
|
|
74
|
+
At prompt-build time the composer renders each eligible instruction as a single bullet:
|
|
75
75
|
|
|
76
76
|
```
|
|
77
|
-
- [<kind>] [<scope-caption>] <prompt>
|
|
77
|
+
- [<kind>] [<scope-caption>] <prompt> (apply only when: <when-clause> OR <when-clause>)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
The parenthesized condition is omitted when `when` is not set. Code-evaluated `if` predicates run first; a failing predicate removes the entire bullet before the prompt reaches the model.
|
|
81
|
+
|
|
80
82
|
Scope captions are fixed by where the instruction was declared:
|
|
81
83
|
|
|
82
84
|
| Scope | Caption |
|
package/docs/reference/step.md
CHANGED
|
@@ -96,7 +96,7 @@ interface StepLifecycleHooks<TContext = unknown, TData = unknown> {
|
|
|
96
96
|
| `auto` | `boolean` | no | `false` | When `true`, the step runs without an LLM call — only `onEnter`, `prepare`, and `branches` execute. Cannot coexist with `prompt`, `collect`, `tools`, or `finalize`. Counts against `maxAutoStepsPerTurn`. |
|
|
97
97
|
| `collect` | `(keyof TData)[]` | no | `[]` | Schema field keys this step is responsible for extracting from the user message. Every key must exist in the agent's `schema`. The engine skips the step automatically when every listed key is already present in `session.data` (pre-extraction). |
|
|
98
98
|
| `requires` | `(keyof TData)[]` | no | `[]` | Prerequisite field keys. The engine refuses to enter the step until every key is present in `session.data`. When fields covered by `requires` are read inside `branches[].if` predicates, they are guaranteed to be defined. |
|
|
99
|
-
| `when` | `string \| string[]` | no | — | AI-evaluated activation strings (
|
|
99
|
+
| `when` | `string \| string[]` | no | — | AI-evaluated activation strings (OR semantics). Evaluated by the LLM at routing time. Functions are not allowed here — the constructor throws `FlowConfigurationError` if a function is found. |
|
|
100
100
|
| `if` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated activation predicates (AND semantics). Evaluated locally — no LLM cost. When both `when` and `if` are set, `if` runs first; `when` is only evaluated if every `if` predicate passes. |
|
|
101
101
|
| `skip` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated skip predicates (OR semantics). When any predicate returns `true`, the step is bypassed. Only code predicates — no AI strings. |
|
|
102
102
|
| `tools` | `(string \| Tool<TContext, TData>)[]` | no | `[]` | Tools available during this step. Strings are resolved against the agent's tool registry; objects are inline tools. Stacked on top of agent and flow scopes. |
|
|
@@ -130,7 +130,7 @@ or the full `Directive` surface (`appendPrompt`,
|
|
|
130
130
|
|
|
131
131
|
For one step, the engine walks this sequence per turn:
|
|
132
132
|
|
|
133
|
-
1. Evaluate `if` (code, AND) and `when` (AI,
|
|
133
|
+
1. Evaluate `if` (code, AND) and `when` (AI, OR) — fails skip the step entirely.
|
|
134
134
|
2. Evaluate `skip` (code, OR) — true means bypass and fall through.
|
|
135
135
|
3. Check `requires` — refuse entry if any required field is missing.
|
|
136
136
|
4. Run `onEnter`, then `prepare` / `hooks.prepare`. May emit a `Directive` (pre-LLM fields honored).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falai/agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Conversational state engine for TypeScript where the AI understands, but the code is in control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://falai.dev",
|
|
40
40
|
"scripts": {
|
|
41
|
-
"build": "
|
|
41
|
+
"build": "bun run build:esm && bun run build:cjs && bun run fix:cjs",
|
|
42
42
|
"build:esm": "tsc",
|
|
43
43
|
"build:cjs": "tsc --project tsconfig.cjs.json",
|
|
44
44
|
"fix:cjs": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
@@ -48,15 +48,14 @@
|
|
|
48
48
|
"lint": "eslint src/**/*.ts",
|
|
49
49
|
"lint:fix": "eslint src/**/*.ts --fix",
|
|
50
50
|
"clean": "rm -rf dist",
|
|
51
|
-
"prebuild": "
|
|
52
|
-
"prepublishOnly": "
|
|
53
|
-
"release:patch": "
|
|
54
|
-
"release:minor": "
|
|
55
|
-
"release:major": "
|
|
56
|
-
"release:alpha": "
|
|
57
|
-
"release": "
|
|
58
|
-
"test": "bun test tests/*.test.ts"
|
|
59
|
-
"preinstall": "node preinstall.js"
|
|
51
|
+
"prebuild": "bun run clean",
|
|
52
|
+
"prepublishOnly": "bun run lint && bun run typecheck && bun run build && bun run test",
|
|
53
|
+
"release:patch": "bun pm version patch && bun publish",
|
|
54
|
+
"release:minor": "bun pm version minor && bun publish",
|
|
55
|
+
"release:major": "bun pm version major && bun publish",
|
|
56
|
+
"release:alpha": "bun publish --tag alpha",
|
|
57
|
+
"release": "bun publish",
|
|
58
|
+
"test": "bun test tests/*.test.ts"
|
|
60
59
|
},
|
|
61
60
|
"keywords": [
|
|
62
61
|
"ai",
|
|
@@ -81,17 +80,15 @@
|
|
|
81
80
|
"@eslint/js": "^9.17.0",
|
|
82
81
|
"@types/bun": "^1.3.0",
|
|
83
82
|
"@types/node": "^20.19.22",
|
|
83
|
+
"@types/pg": "^8.15.5",
|
|
84
84
|
"eslint": "^9.17.0",
|
|
85
85
|
"fast-check": "^4.5.3",
|
|
86
86
|
"typescript": "^5.3.3",
|
|
87
|
-
"typescript-eslint": "^8.18.2"
|
|
88
|
-
"vitest": "^3.2.4"
|
|
87
|
+
"typescript-eslint": "^8.18.2"
|
|
89
88
|
},
|
|
90
89
|
"dependencies": {
|
|
91
90
|
"@anthropic-ai/sdk": "^0.65.0",
|
|
92
|
-
"@google/genai": "^
|
|
93
|
-
"@types/pg": "^8.15.5",
|
|
94
|
-
"@types/redis": "^4.0.11",
|
|
91
|
+
"@google/genai": "^2.7.0",
|
|
95
92
|
"loglevel": "^1.9.2",
|
|
96
93
|
"openai": "^6.3.0"
|
|
97
94
|
},
|
|
@@ -99,10 +96,9 @@
|
|
|
99
96
|
"@opensearch-project/opensearch": "^2.0.0",
|
|
100
97
|
"@prisma/client": "^6.0.0",
|
|
101
98
|
"better-sqlite3": "^11.0.0 || ^12.0.0",
|
|
102
|
-
"ioredis": "",
|
|
103
|
-
"mongodb": "",
|
|
104
|
-
"
|
|
105
|
-
"pg": ""
|
|
99
|
+
"ioredis": "^5.0.0",
|
|
100
|
+
"mongodb": "^6.0.0",
|
|
101
|
+
"pg": "^8.0.0"
|
|
106
102
|
},
|
|
107
103
|
"peerDependenciesMeta": {
|
|
108
104
|
"@prisma/client": {
|
|
@@ -111,18 +107,12 @@
|
|
|
111
107
|
"ioredis": {
|
|
112
108
|
"optional": true
|
|
113
109
|
},
|
|
114
|
-
"redis": {
|
|
115
|
-
"optional": true
|
|
116
|
-
},
|
|
117
110
|
"mongodb": {
|
|
118
111
|
"optional": true
|
|
119
112
|
},
|
|
120
113
|
"pg": {
|
|
121
114
|
"optional": true
|
|
122
115
|
},
|
|
123
|
-
"mysql2": {
|
|
124
|
-
"optional": true
|
|
125
|
-
},
|
|
126
116
|
"better-sqlite3": {
|
|
127
117
|
"optional": true
|
|
128
118
|
},
|
|
@@ -130,4 +120,4 @@
|
|
|
130
120
|
"optional": true
|
|
131
121
|
}
|
|
132
122
|
}
|
|
133
|
-
}
|
|
123
|
+
}
|
|
@@ -36,15 +36,15 @@ import { createSessionId } from "../utils";
|
|
|
36
36
|
* SQLite database interface - matches better-sqlite3
|
|
37
37
|
*/
|
|
38
38
|
export interface SqliteDatabase {
|
|
39
|
-
prepare(sql: string):
|
|
39
|
+
prepare(sql: string): SqliteStatement;
|
|
40
40
|
exec(sql: string): void;
|
|
41
41
|
close(): void;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* SQLite
|
|
45
|
+
* SQLite statement interface - matches better-sqlite3 Statement
|
|
46
46
|
*/
|
|
47
|
-
export interface
|
|
47
|
+
export interface SqliteStatement {
|
|
48
48
|
run(...params: unknown[]): { changes: number; lastInsertRowid: number };
|
|
49
49
|
get(...params: unknown[]): Record<string, unknown> | undefined;
|
|
50
50
|
all(...params: unknown[]): Array<Record<string, unknown>>;
|
package/src/adapters/index.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { eventsToHistory, logger } from "../utils";
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* The AI condition evaluator function signature.
|
|
23
|
-
* Accepts an array of condition strings (
|
|
24
|
-
* whether
|
|
23
|
+
* Accepts an array of condition strings (OR semantics) and returns
|
|
24
|
+
* whether any condition is satisfied.
|
|
25
25
|
*
|
|
26
26
|
* This is the same evaluation mechanism used by `step.when` — condition
|
|
27
27
|
* strings are evaluated by the AI provider against the conversation context.
|
|
@@ -65,7 +65,7 @@ export function createAiConditionEvaluator<TContext = unknown>(
|
|
|
65
65
|
"Condition(s):",
|
|
66
66
|
conditionText,
|
|
67
67
|
"",
|
|
68
|
-
"Return JSON with a single boolean field `result`: true if
|
|
68
|
+
"Return JSON with a single boolean field `result`: true if ANY condition is satisfied, false otherwise.",
|
|
69
69
|
].join("\n");
|
|
70
70
|
|
|
71
71
|
const result = await provider.generateMessage<TContext, { result: boolean }>({
|
|
@@ -78,7 +78,7 @@ export function createAiConditionEvaluator<TContext = unknown>(
|
|
|
78
78
|
properties: {
|
|
79
79
|
result: {
|
|
80
80
|
type: "boolean",
|
|
81
|
-
description: "Whether
|
|
81
|
+
description: "Whether any condition is met based on the conversation context",
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
84
|
required: ["result"],
|
package/src/core/Flow.ts
CHANGED
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
FlowLifecycleHooks,
|
|
10
10
|
StructuredSchema,
|
|
11
11
|
Instruction,
|
|
12
|
-
Term,
|
|
13
12
|
Tool,
|
|
14
13
|
TemplateContext,
|
|
15
14
|
ConditionEvaluationResult,
|
|
@@ -247,7 +246,8 @@ export class Flow<TContext = unknown, TData = unknown> {
|
|
|
247
246
|
/**
|
|
248
247
|
* Evaluate when/if conditions using the v2 split logic.
|
|
249
248
|
* `if` (code predicate) evaluates first (free); `when` (AI) evaluates only when `if` passes.
|
|
250
|
-
*
|
|
249
|
+
* `if` predicates use AND semantics. `when` strings use OR semantics.
|
|
250
|
+
* When both fields are set, the passing `if` gate and AI match are combined with AND.
|
|
251
251
|
*/
|
|
252
252
|
async evaluateWhen(
|
|
253
253
|
templateContext: TemplateContext<TContext, TData>
|
|
@@ -367,14 +367,6 @@ export class Flow<TContext = unknown, TData = unknown> {
|
|
|
367
367
|
return [...this.instructions];
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
/**
|
|
371
|
-
* Get all terms for this flow
|
|
372
|
-
* @deprecated Flow-level terms removed in v2. Always returns empty array.
|
|
373
|
-
*/
|
|
374
|
-
getTerms(): Term<TContext>[] {
|
|
375
|
-
return [];
|
|
376
|
-
}
|
|
377
|
-
|
|
378
370
|
/**
|
|
379
371
|
* Get all tools for this flow
|
|
380
372
|
*/
|
package/src/core/FlowRouter.ts
CHANGED
|
@@ -1151,7 +1151,7 @@ export class FlowRouter<TContext = unknown, TData = unknown> {
|
|
|
1151
1151
|
if (candidate.step.when) {
|
|
1152
1152
|
const whenResult = await candidate.step.evaluateWhen(templateContext);
|
|
1153
1153
|
if (whenResult.aiContextStrings.length > 0) {
|
|
1154
|
-
parts.push(` When
|
|
1154
|
+
parts.push(` When any condition matches: ${whenResult.aiContextStrings.join(" OR ")}`);
|
|
1155
1155
|
} else if (typeof candidate.step.when === 'string') {
|
|
1156
1156
|
parts.push(` When this step should be completed: ${candidate.step.when}`);
|
|
1157
1157
|
}
|
|
@@ -1436,7 +1436,7 @@ export class FlowRouter<TContext = unknown, TData = unknown> {
|
|
|
1436
1436
|
if (step.when) {
|
|
1437
1437
|
const whenResult = await step.evaluateWhen(templateContext);
|
|
1438
1438
|
if (whenResult.aiContextStrings.length > 0) {
|
|
1439
|
-
stepInfo.push(` When
|
|
1439
|
+
stepInfo.push(` When any condition matches: ${whenResult.aiContextStrings.join(" OR ")}`);
|
|
1440
1440
|
activeStepConditionContext.push(...whenResult.aiContextStrings);
|
|
1441
1441
|
} else if (typeof step.when === 'string') {
|
|
1442
1442
|
stepInfo.push(` When this step should be completed: ${step.when}`);
|
|
@@ -2,7 +2,6 @@ import type { Event, Term, Instruction, AgentOptions, ScopedInstructions, Applie
|
|
|
2
2
|
import type { Flow } from "./Flow";
|
|
3
3
|
import { render, renderMany, formatKnowledgeBase, createTemplateContext } from "../utils/template";
|
|
4
4
|
import { TemplateContext } from "../types/template";
|
|
5
|
-
import { ConditionEvaluator } from "../utils/condition";
|
|
6
5
|
import { PromptSectionCache } from "./PromptSectionCache";
|
|
7
6
|
import { logger } from "../utils";
|
|
8
7
|
|
|
@@ -155,8 +154,6 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
|
|
|
155
154
|
// Reset the per-turn applied set
|
|
156
155
|
this.lastAppliedInstructions = [];
|
|
157
156
|
|
|
158
|
-
const evaluator = new ConditionEvaluator(this.renderContext);
|
|
159
|
-
|
|
160
157
|
/**
|
|
161
158
|
* Evaluate a single instruction's `if` code predicate(s).
|
|
162
159
|
* Returns true if all predicates pass (AND semantics).
|
|
@@ -182,17 +179,6 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
|
|
|
182
179
|
return true;
|
|
183
180
|
};
|
|
184
181
|
|
|
185
|
-
/**
|
|
186
|
-
* Evaluate a single instruction's `when` condition.
|
|
187
|
-
* Returns true if the instruction should be active.
|
|
188
|
-
*/
|
|
189
|
-
const evaluateWhen = async (when: Instruction<TContext, TData>['when']): Promise<boolean> => {
|
|
190
|
-
if (when === undefined) return true;
|
|
191
|
-
const evaluation = await evaluator.evaluateCondition(when, 'AND');
|
|
192
|
-
if (!evaluation.hasProgrammaticConditions) return true;
|
|
193
|
-
return evaluation.programmaticResult;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
182
|
/**
|
|
197
183
|
* Resolve the kind prefix for rendering.
|
|
198
184
|
* Default kind is 'should'.
|
|
@@ -203,7 +189,7 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
|
|
|
203
189
|
};
|
|
204
190
|
|
|
205
191
|
/**
|
|
206
|
-
* Process a scope bucket: filter enabled,
|
|
192
|
+
* Process a scope bucket: filter enabled, gate by `if`, render `when`, and collect applied records.
|
|
207
193
|
*/
|
|
208
194
|
const processScope = async (
|
|
209
195
|
items: Instruction<TContext, TData>[],
|
|
@@ -219,14 +205,17 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
|
|
|
219
205
|
const ifPassed = await evaluateIf(g.if);
|
|
220
206
|
if (!ifPassed) continue;
|
|
221
207
|
|
|
222
|
-
// Evaluate `when` (AI condition) only when `if` passed
|
|
223
|
-
const active = await evaluateWhen(g.when);
|
|
224
|
-
if (!active) continue;
|
|
225
|
-
|
|
226
208
|
const text = await render(g.prompt, this.renderContext);
|
|
227
209
|
if (!text) continue;
|
|
228
210
|
|
|
229
|
-
|
|
211
|
+
const whenContextStrings = g.when
|
|
212
|
+
? (Array.isArray(g.when) ? g.when : [g.when])
|
|
213
|
+
: [];
|
|
214
|
+
const condition = whenContextStrings.length > 0
|
|
215
|
+
? ` (apply only when: ${whenContextStrings.join(" OR ")})`
|
|
216
|
+
: "";
|
|
217
|
+
|
|
218
|
+
lines.push(`- ${kindPrefix(g.kind)} ${caption} ${text}${condition}`);
|
|
230
219
|
this.lastAppliedInstructions.push({
|
|
231
220
|
id: g.id || '',
|
|
232
221
|
scope,
|
|
@@ -7,19 +7,8 @@
|
|
|
7
7
|
* directives, available tools) are recomputed on every resolveAll() call.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
export type PromptSectionType
|
|
12
|
-
|
|
13
|
-
/** Configuration for prompt section caching behavior */
|
|
14
|
-
export interface PromptCacheConfig {
|
|
15
|
-
/** Whether to enable section memoization (default: true) */
|
|
16
|
-
enabled?: boolean;
|
|
17
|
-
/** Keys of sections that should always recompute every turn, even if registered as static */
|
|
18
|
-
volatileKeys?: string[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Compute function that produces a section's content */
|
|
22
|
-
export type SectionCompute = () => string | null | Promise<string | null>;
|
|
10
|
+
import type { PromptSectionType, PromptCacheConfig, SectionCompute } from "../types/prompt-cache";
|
|
11
|
+
export type { PromptSectionType, PromptCacheConfig, SectionCompute } from "../types/prompt-cache";
|
|
23
12
|
|
|
24
13
|
/** Internal entry tracking a registered section */
|
|
25
14
|
interface PromptSectionEntry {
|