@falai/agent 2.0.0 → 2.1.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.
Files changed (161) hide show
  1. package/dist/cjs/core/Agent.d.ts +1 -1
  2. package/dist/cjs/core/Agent.js +3 -3
  3. package/dist/cjs/core/Agent.js.map +1 -1
  4. package/dist/cjs/core/AutoChainExecutor.d.ts +2 -2
  5. package/dist/cjs/core/AutoChainExecutor.js +2 -2
  6. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  7. package/dist/cjs/core/PromptComposer.d.ts +1 -1
  8. package/dist/cjs/core/PromptComposer.js +2 -2
  9. package/dist/cjs/core/PromptComposer.js.map +1 -1
  10. package/dist/cjs/core/ResponseEngine.d.ts +1 -1
  11. package/dist/cjs/core/ResponseModal.js +6 -6
  12. package/dist/cjs/core/ResponseModal.js.map +1 -1
  13. package/dist/cjs/core/ResponsePipeline.d.ts +2 -2
  14. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  15. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  16. package/dist/cjs/core/SignalProcessor.d.ts +3 -3
  17. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  18. package/dist/cjs/core/SignalProcessor.js +1 -1
  19. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  20. package/dist/cjs/core/Step.d.ts +1 -1
  21. package/dist/cjs/core/Step.js +1 -1
  22. package/dist/cjs/core/ToolManager.d.ts +1 -1
  23. package/dist/cjs/core/ToolManager.js +1 -1
  24. package/dist/cjs/core/flow-namespace.d.ts +4 -4
  25. package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
  26. package/dist/cjs/core/flow-namespace.js +14 -25
  27. package/dist/cjs/core/flow-namespace.js.map +1 -1
  28. package/dist/cjs/index.d.ts +3 -3
  29. package/dist/cjs/index.d.ts.map +1 -1
  30. package/dist/cjs/index.js +2 -2
  31. package/dist/cjs/index.js.map +1 -1
  32. package/dist/cjs/providers/AnthropicProvider.d.ts +1 -1
  33. package/dist/cjs/providers/AnthropicProvider.js +1 -1
  34. package/dist/cjs/providers/GeminiProvider.d.ts +1 -1
  35. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  36. package/dist/cjs/providers/GeminiProvider.js +1 -1
  37. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  38. package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
  39. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  40. package/dist/cjs/providers/OpenAIProvider.js +1 -1
  41. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  42. package/dist/cjs/types/agent.d.ts +1 -1
  43. package/dist/cjs/types/agent.d.ts.map +1 -1
  44. package/dist/cjs/types/ai.d.ts +1 -1
  45. package/dist/cjs/types/ai.js +1 -1
  46. package/dist/cjs/types/flow.d.ts +33 -25
  47. package/dist/cjs/types/flow.d.ts.map +1 -1
  48. package/dist/cjs/types/index.d.ts +1 -1
  49. package/dist/cjs/types/index.d.ts.map +1 -1
  50. package/dist/cjs/types/index.js.map +1 -1
  51. package/dist/cjs/types/signals.d.ts +4 -5
  52. package/dist/cjs/types/signals.d.ts.map +1 -1
  53. package/dist/cjs/utils/session.d.ts +1 -1
  54. package/dist/cjs/utils/session.js +4 -4
  55. package/dist/cjs/utils/session.js.map +1 -1
  56. package/dist/core/Agent.d.ts +1 -1
  57. package/dist/core/Agent.js +3 -3
  58. package/dist/core/Agent.js.map +1 -1
  59. package/dist/core/AutoChainExecutor.d.ts +2 -2
  60. package/dist/core/AutoChainExecutor.js +2 -2
  61. package/dist/core/AutoChainExecutor.js.map +1 -1
  62. package/dist/core/PromptComposer.d.ts +1 -1
  63. package/dist/core/PromptComposer.js +2 -2
  64. package/dist/core/PromptComposer.js.map +1 -1
  65. package/dist/core/ResponseEngine.d.ts +1 -1
  66. package/dist/core/ResponseModal.js +6 -6
  67. package/dist/core/ResponseModal.js.map +1 -1
  68. package/dist/core/ResponsePipeline.d.ts +2 -2
  69. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  70. package/dist/core/ResponsePipeline.js.map +1 -1
  71. package/dist/core/SignalProcessor.d.ts +3 -3
  72. package/dist/core/SignalProcessor.d.ts.map +1 -1
  73. package/dist/core/SignalProcessor.js +1 -1
  74. package/dist/core/SignalProcessor.js.map +1 -1
  75. package/dist/core/Step.d.ts +1 -1
  76. package/dist/core/Step.js +1 -1
  77. package/dist/core/ToolManager.d.ts +1 -1
  78. package/dist/core/ToolManager.js +1 -1
  79. package/dist/core/flow-namespace.d.ts +4 -4
  80. package/dist/core/flow-namespace.d.ts.map +1 -1
  81. package/dist/core/flow-namespace.js +14 -25
  82. package/dist/core/flow-namespace.js.map +1 -1
  83. package/dist/index.d.ts +3 -3
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +2 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/providers/AnthropicProvider.d.ts +1 -1
  88. package/dist/providers/AnthropicProvider.js +1 -1
  89. package/dist/providers/GeminiProvider.d.ts +1 -1
  90. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  91. package/dist/providers/GeminiProvider.js +1 -1
  92. package/dist/providers/GeminiProvider.js.map +1 -1
  93. package/dist/providers/OpenAIProvider.d.ts +1 -1
  94. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  95. package/dist/providers/OpenAIProvider.js +1 -1
  96. package/dist/providers/OpenAIProvider.js.map +1 -1
  97. package/dist/types/agent.d.ts +1 -1
  98. package/dist/types/agent.d.ts.map +1 -1
  99. package/dist/types/ai.d.ts +1 -1
  100. package/dist/types/ai.js +1 -1
  101. package/dist/types/flow.d.ts +33 -25
  102. package/dist/types/flow.d.ts.map +1 -1
  103. package/dist/types/index.d.ts +1 -1
  104. package/dist/types/index.d.ts.map +1 -1
  105. package/dist/types/index.js.map +1 -1
  106. package/dist/types/signals.d.ts +4 -5
  107. package/dist/types/signals.d.ts.map +1 -1
  108. package/dist/utils/session.d.ts +1 -1
  109. package/dist/utils/session.js +4 -4
  110. package/dist/utils/session.js.map +1 -1
  111. package/docs/README.md +12 -3
  112. package/docs/concepts/architecture.md +19 -26
  113. package/docs/concepts/directives.md +53 -84
  114. package/docs/concepts/pipeline.md +4 -4
  115. package/docs/guides/flow-control.md +12 -13
  116. package/docs/guides/streaming.md +1 -1
  117. package/docs/migration/README.md +9 -10
  118. package/docs/migration/v1-to-v2.md +508 -6
  119. package/docs/reference/adapters.md +1 -1
  120. package/docs/reference/directive.md +9 -10
  121. package/docs/reference/flow.md +3 -3
  122. package/docs/reference/instruction.md +1 -1
  123. package/docs/reference/providers.md +14 -14
  124. package/docs/reference/signals.md +3 -4
  125. package/docs/reference/step.md +9 -10
  126. package/docs/reference/tool.md +1 -1
  127. package/docs/start/01-install.md +1 -1
  128. package/docs/start/02-first-agent.md +2 -3
  129. package/examples/01-quickstart.ts +1 -1
  130. package/examples/02-data-extraction.ts +1 -1
  131. package/examples/03-tools.ts +1 -1
  132. package/examples/04-instructions.ts +1 -1
  133. package/examples/05-branching.ts +1 -1
  134. package/examples/06-flow-control.ts +1 -1
  135. package/examples/07-streaming.ts +1 -1
  136. package/examples/08-persistence.ts +1 -1
  137. package/examples/09-signals.ts +1 -1
  138. package/examples/tsconfig.json +1 -3
  139. package/package.json +9 -5
  140. package/src/core/Agent.ts +4 -4
  141. package/src/core/AutoChainExecutor.ts +3 -3
  142. package/src/core/PromptComposer.ts +2 -2
  143. package/src/core/ResponseEngine.ts +1 -1
  144. package/src/core/ResponseModal.ts +18 -18
  145. package/src/core/ResponsePipeline.ts +1 -2
  146. package/src/core/SignalProcessor.ts +7 -7
  147. package/src/core/Step.ts +1 -1
  148. package/src/core/ToolManager.ts +1 -1
  149. package/src/core/flow-namespace.ts +32 -53
  150. package/src/index.ts +2 -3
  151. package/src/providers/AnthropicProvider.ts +2 -2
  152. package/src/providers/GeminiProvider.ts +2 -2
  153. package/src/providers/OpenAIProvider.ts +2 -2
  154. package/src/types/agent.ts +1 -1
  155. package/src/types/ai.ts +1 -1
  156. package/src/types/flow.ts +41 -26
  157. package/src/types/index.ts +0 -1
  158. package/src/types/signals.ts +4 -5
  159. package/src/utils/session.ts +5 -5
  160. package/docs/migration/route-to-flow.md +0 -560
  161. package/docs/reference/pre-directive.md +0 -131
@@ -15,7 +15,7 @@ import type { AiProvider } from "../types/ai";
15
15
  import type { Event } from "../types/history";
16
16
  import { MessageRole as MessageRoleEnum } from "../types/history";
17
17
  import type { SessionState } from "../types/session";
18
- import type { PreDirective, Directive } from "../types/flow";
18
+ import type { Directive } from "../types/flow";
19
19
  import type {
20
20
  Signal,
21
21
  SignalContext,
@@ -295,14 +295,14 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
295
295
 
296
296
  /**
297
297
  * Pre-signal phase. Delegates to `runPhase('pre', ...)`.
298
- * Return type narrows `mergedDirective` to `PreDirective | undefined`.
298
+ * Return type narrows `mergedDirective` to `Directive | undefined`.
299
299
  */
300
300
  async runPreSignalPhase(params: {
301
301
  session: SessionState<TData>;
302
302
  history: Event[];
303
303
  context: TContext;
304
304
  }): Promise<{
305
- mergedDirective?: PreDirective<TContext, TData>;
305
+ mergedDirective?: Directive<TContext, TData>;
306
306
  firings: SignalFiring<TContext, TData>[];
307
307
  updatedSession: SessionState<TData>;
308
308
  }> {
@@ -335,7 +335,7 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
335
335
  phase: 'pre' | 'post',
336
336
  params: { session: SessionState<TData>; history: Event[]; context: TContext },
337
337
  ): Promise<{
338
- mergedDirective?: PreDirective<TContext, TData>;
338
+ mergedDirective?: Directive<TContext, TData>;
339
339
  firings: SignalFiring<TContext, TData>[];
340
340
  updatedSession: SessionState<TData>;
341
341
  }> {
@@ -584,7 +584,7 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
584
584
  }
585
585
 
586
586
  // ── STEP 9: merge directives ─────────────────────────────────────────
587
- let mergedDirective: PreDirective<TContext, TData> | undefined;
587
+ let mergedDirective: Directive<TContext, TData> | undefined;
588
588
 
589
589
  if (bus.length > 0) {
590
590
  mergedDirective = this.mergeDirectives(bus);
@@ -633,8 +633,8 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
633
633
  */
634
634
  private mergeDirectives(
635
635
  directives: SignalDirective<TContext, TData>[],
636
- ): PreDirective<TContext, TData> {
637
- const merged: PreDirective<TContext, TData> = {};
636
+ ): Directive<TContext, TData> {
637
+ const merged: Directive<TContext, TData> = {};
638
638
 
639
639
  for (const d of directives) {
640
640
  // Position fields — last-write-wins
package/src/core/Step.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Step in the route DSL
2
+ * Step in a conversational flow
3
3
  */
4
4
 
5
5
  import type {
@@ -62,7 +62,7 @@ export class ToolManager<TContext = unknown, TData = unknown> {
62
62
  private toolRegistry: Map<string, Tool<TContext, TData>>;
63
63
 
64
64
  /**
65
- * Per-turn transient tool layer populated from PreDirective.injectTools.
65
+ * Per-turn transient tool layer populated from Directive.injectTools.
66
66
  * Resolution order: transient → step → flow → agent.
67
67
  * Drained at end of turn via try/finally guard.
68
68
  *
@@ -25,7 +25,12 @@ const POSITION_PRECEDENCE: Record<PositionField, number> = {
25
25
 
26
26
  // ─── Helpers ─────────────────────────────────────────────────────────────────
27
27
 
28
- function getSetPositionFields(d: Directive): PositionField[] {
28
+ /** Structural type for anything that has position fields (read-only access). */
29
+ type HasPositionFields = {
30
+ readonly [K in PositionField]?: unknown;
31
+ };
32
+
33
+ function getSetPositionFields(d: HasPositionFields): PositionField[] {
29
34
  return POSITION_FIELDS.filter((f) => d[f] !== undefined && d[f] !== null);
30
35
  }
31
36
 
@@ -44,7 +49,7 @@ function beatsCurrent(
44
49
  // ─── Public API ──────────────────────────────────────────────────────────────
45
50
 
46
51
  /**
47
- * Type guard: is `x` a Directive (or any subtype like PreDirective)?
52
+ * Type guard: is `x` a Directive (or any subtype like SignalDirective)?
48
53
  *
49
54
  * A value is considered a Directive if it is a non-null object. The Directive
50
55
  * interface has all-optional fields, so any plain object qualifies structurally.
@@ -65,31 +70,31 @@ function isDirective(x: unknown): x is Directive {
65
70
  * ties broken by emission order (b wins over a — last wins).
66
71
  * - reply: last-wins (b.reply overrides a.reply if set).
67
72
  * - dataUpdate / contextUpdate: shallow-merge (b overrides a on key collision).
68
- * - appendPrompt / injectTools (PreDirective fields): concatenate then dedupe.
73
+ * - appendPrompt / injectTools (pre-LLM fields): concatenate then dedupe.
69
74
  * - halt: logical-OR.
70
75
  */
71
- function merge<T extends Directive>(a: T, b: T): T {
76
+ function merge<TContext, TData>(a: Directive<TContext, TData>, b: Directive<TContext, TData>): Directive<TContext, TData> {
72
77
  const result = {} as Record<string, unknown>;
73
78
 
74
79
  // ── Position field: winner-takes-all by precedence, b wins ties ──
75
- const aPos = getSetPositionFields(a as Directive);
76
- const bPos = getSetPositionFields(b as Directive);
80
+ const aPos = getSetPositionFields(a);
81
+ const bPos = getSetPositionFields(b);
77
82
 
78
83
  // Pick the highest-priority position field across both directives.
79
84
  // b's fields are evaluated after a's, so b wins on same precedence (last-wins).
80
85
  let winnerField: PositionField | null = null;
81
- let winnerSource: Directive | null = null;
86
+ let winnerSource: Directive<TContext, TData> | null = null;
82
87
 
83
88
  for (const field of aPos) {
84
89
  if (beatsCurrent(field, winnerField)) {
85
90
  winnerField = field;
86
- winnerSource = a as Directive;
91
+ winnerSource = a;
87
92
  }
88
93
  }
89
94
  for (const field of bPos) {
90
95
  if (beatsCurrent(field, winnerField)) {
91
96
  winnerField = field;
92
- winnerSource = b as Directive;
97
+ winnerSource = b;
93
98
  }
94
99
  }
95
100
 
@@ -98,54 +103,30 @@ function merge<T extends Directive>(a: T, b: T): T {
98
103
  }
99
104
 
100
105
  // ── reply: last-wins ──
101
- if ((b as Directive).reply !== undefined) {
102
- result.reply = (b as Directive).reply;
103
- } else if ((a as Directive).reply !== undefined) {
104
- result.reply = (a as Directive).reply;
106
+ if (b.reply !== undefined) {
107
+ result.reply = b.reply;
108
+ } else if (a.reply !== undefined) {
109
+ result.reply = a.reply;
105
110
  }
106
111
 
107
112
  // ── dataUpdate: shallow merge ──
108
- const aData = (a as Record<string, unknown>).dataUpdate as
109
- | Record<string, unknown>
110
- | undefined;
111
- const bData = (b as Record<string, unknown>).dataUpdate as
112
- | Record<string, unknown>
113
- | undefined;
114
- if (aData || bData) {
115
- result.dataUpdate = { ...aData, ...bData };
113
+ if (a.dataUpdate || b.dataUpdate) {
114
+ result.dataUpdate = { ...a.dataUpdate, ...b.dataUpdate };
116
115
  }
117
116
 
118
117
  // ── contextUpdate: shallow merge ──
119
- const aCtx = (a as Record<string, unknown>).contextUpdate as
120
- | Record<string, unknown>
121
- | undefined;
122
- const bCtx = (b as Record<string, unknown>).contextUpdate as
123
- | Record<string, unknown>
124
- | undefined;
125
- if (aCtx || bCtx) {
126
- result.contextUpdate = { ...aCtx, ...bCtx };
118
+ if (a.contextUpdate || b.contextUpdate) {
119
+ result.contextUpdate = { ...a.contextUpdate, ...b.contextUpdate };
127
120
  }
128
121
 
129
- // ── appendPrompt (PreDirective): concatenate ──
130
- const aPrompt = (a as Record<string, unknown>).appendPrompt as
131
- | string[]
132
- | undefined;
133
- const bPrompt = (b as Record<string, unknown>).appendPrompt as
134
- | string[]
135
- | undefined;
136
- if (aPrompt || bPrompt) {
137
- result.appendPrompt = [...(aPrompt ?? []), ...(bPrompt ?? [])];
122
+ // ── appendPrompt (pre-LLM): concatenate ──
123
+ if (a.appendPrompt || b.appendPrompt) {
124
+ result.appendPrompt = [...(a.appendPrompt ?? []), ...(b.appendPrompt ?? [])];
138
125
  }
139
126
 
140
- // ── injectTools (PreDirective): concatenate then dedupe by id (last wins) ──
141
- const aTools = (a as Record<string, unknown>).injectTools as
142
- | Array<{ id: string;[k: string]: unknown }>
143
- | undefined;
144
- const bTools = (b as Record<string, unknown>).injectTools as
145
- | Array<{ id: string;[k: string]: unknown }>
146
- | undefined;
147
- if (aTools || bTools) {
148
- const combined = [...(aTools ?? []), ...(bTools ?? [])];
127
+ // ── injectTools (pre-LLM): concatenate then dedupe by id (last wins) ──
128
+ if (a.injectTools || b.injectTools) {
129
+ const combined = [...(a.injectTools ?? []), ...(b.injectTools ?? [])];
149
130
  // Dedupe by id — last definition wins
150
131
  const seen = new Map<string, (typeof combined)[number]>();
151
132
  for (const tool of combined) {
@@ -154,14 +135,12 @@ function merge<T extends Directive>(a: T, b: T): T {
154
135
  result.injectTools = Array.from(seen.values());
155
136
  }
156
137
 
157
- // ── halt (PreDirective): logical OR ──
158
- const aHalt = (a as Record<string, unknown>).halt as boolean | undefined;
159
- const bHalt = (b as Record<string, unknown>).halt as boolean | undefined;
160
- if (aHalt || bHalt) {
138
+ // ── halt (pre-LLM): logical OR ──
139
+ if (a.halt || b.halt) {
161
140
  result.halt = true;
162
141
  }
163
142
 
164
- return result as T;
143
+ return result as Directive<TContext, TData>;
165
144
  }
166
145
 
167
146
  /**
@@ -170,7 +149,7 @@ function merge<T extends Directive>(a: T, b: T): T {
170
149
  * - `goTo` set as empty object `{}` (no flow target).
171
150
  * - `reply` co-existing with `abort` (abort ends the conversation; a reply is nonsensical).
172
151
  */
173
- function validate(d: Directive): void {
152
+ function validate<TContext, TData>(d: Directive<TContext, TData>): void {
174
153
  // ── Multiple position fields ──
175
154
  const setFields = getSetPositionFields(d);
176
155
  if (setFields.length > 1) {
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @falai/agent - Standalone AI Agent framework
2
+ * @falai/agent Conversational state engine for TypeScript
3
3
  *
4
- * A strongly-typed, modular agent framework with flow DSL and AI provider strategy
4
+ * The AI understands. The code is in control.
5
5
  */
6
6
 
7
7
  // Core
@@ -173,7 +173,6 @@ export type {
173
173
  StoppedReason,
174
174
  PrepareResult,
175
175
  Directive,
176
- PreDirective,
177
176
  BranchEntry,
178
177
  BranchMap,
179
178
  BranchPredicate,
@@ -30,7 +30,7 @@ const DEFAULT_RETRY_CONFIG = {
30
30
  export interface AnthropicProviderOptions {
31
31
  /** Anthropic API key */
32
32
  apiKey: string;
33
- /** Model to use (required) - e.g., "claude-sonnet-4-5", "claude-opus-4-1" */
33
+ /** Model to use (required) - e.g., "claude-sonnet-4-6", "claude-opus-4-7" */
34
34
  model: string;
35
35
  /** Backup models to try if primary fails (default: []) */
36
36
  backupModels?: string[];
@@ -138,7 +138,7 @@ export class AnthropicProvider implements AiProvider {
138
138
  }
139
139
 
140
140
  if (!model) {
141
- throw new Error("Model is required. Example: 'claude-sonnet-4-5'");
141
+ throw new Error("Model is required. Example: 'claude-sonnet-4-6'");
142
142
  }
143
143
 
144
144
  this.client = new Anthropic({
@@ -36,7 +36,7 @@ const DEFAULT_RETRY_CONFIG = {
36
36
  export interface GeminiProviderOptions {
37
37
  /** Gemini API key */
38
38
  apiKey: string;
39
- /** Model to use (required) - e.g., "models/gemini-2.5-pro" */
39
+ /** Model to use (required) - e.g., "gemini-3.1-pro-preview" */
40
40
  model: string;
41
41
  /** Backup models to try if primary fails (default: []) */
42
42
  backupModels?: string[];
@@ -140,7 +140,7 @@ export class GeminiProvider implements AiProvider {
140
140
  }
141
141
 
142
142
  if (!model) {
143
- throw new Error("Model is required. Example: 'models/gemini-2.5-pro'");
143
+ throw new Error("Model is required. Example: 'gemini-3.1-pro-preview'");
144
144
  }
145
145
 
146
146
  this.genAI = new GoogleGenAI({ apiKey });
@@ -31,7 +31,7 @@ export interface OpenAIProviderOptions {
31
31
  apiKey: string;
32
32
  /** Organization ID (optional) */
33
33
  organization?: string;
34
- /** Model to use (required) - e.g., "gpt-5", "gpt-5-mini" */
34
+ /** Model to use (required) - e.g., "gpt-5.5", "gpt-5.4" */
35
35
  model: string;
36
36
  /** Backup models to try if primary fails (default: []) */
37
37
  backupModels?: string[];
@@ -144,7 +144,7 @@ export class OpenAIProvider implements AiProvider {
144
144
  }
145
145
 
146
146
  if (!model) {
147
- throw new Error("Model is required. Example: 'gpt-5' or 'gpt-5-mini'");
147
+ throw new Error("Model is required. Example: 'gpt-5.5' or 'gpt-5.4'");
148
148
  }
149
149
 
150
150
  // Dynamic import to avoid bundling issues
@@ -146,7 +146,7 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
146
146
  contextProvider?: ContextProvider<TContext>;
147
147
  /** Lifecycle hooks for context management */
148
148
  hooks?: ContextLifecycleHooks<TContext, TData>;
149
- /** AI provider strategy for generating responses */
149
+ /** AI provider for generating responses */
150
150
  provider: AiProvider;
151
151
  /** Initial terms for domain glossary */
152
152
  terms?: Term<TContext, TData>[];
package/src/types/ai.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AI provider strategy types
2
+ * AI provider types
3
3
  */
4
4
 
5
5
  import type { HistoryItem } from "./history";
package/src/types/flow.ts CHANGED
@@ -79,54 +79,61 @@ export interface PrepareResult {
79
79
  // ─── Branch types ────────────────────────────────────────────────────────────
80
80
 
81
81
  /**
82
- * A programmatic transition directive. Encodes where to go and what state
83
- * writes to apply. Used as the `then` target of a `BranchEntry` when the
84
- * destination is more complex than a simple step/flow id string.
82
+ * A programmatic transition directive. The single shape any tool, hook, branch,
83
+ * or signal handler returns to write state, redirect the conversation, or speak
84
+ * verbatim.
85
85
  *
86
86
  * At most one position field (`goTo`, `goToStep`, `complete`, `abort`, `reset`)
87
87
  * may be set per Directive. Non-position fields (`reply`, `contextUpdate`,
88
88
  * `dataUpdate`) may accompany any position field.
89
+ *
90
+ * Pre-LLM augmentation fields (`appendPrompt`, `injectTools`, `halt`) are
91
+ * one-turn-lifetime: they only take effect in pre-LLM hooks (`onEnter`,
92
+ * `prepare`). When emitted from post-LLM hooks (`finalize`, `onComplete`) or
93
+ * persisted to `session.pendingDirective`, these fields are ignored and a WARN
94
+ * log is emitted. They are never serialized across turns.
89
95
  */
90
96
  export interface Directive<TContext = unknown, TData = unknown> {
97
+ // ── Position fields (mutually exclusive: at most one) ────────────
91
98
  /** Jump to a flow (string) or a flow with options (object). */
92
99
  goTo?: string | { flow?: string; step?: string; data?: Partial<TData>; reason?: string; carry?: 'preserve' | 'reset'; };
93
100
  /** Jump to a specific step, optionally in another flow. */
94
101
  goToStep?: string | { step: string; flow?: string; data?: Partial<TData>; reason?: string; };
95
102
  /** Mark the current flow as complete. */
96
- complete?: true | { next?: Directive<TContext, TData>; reason?: string; };
103
+ complete?: true | { next?: Directive<unknown, unknown>; reason?: string; };
97
104
  /** Abort the current flow. */
98
105
  abort?: string | { reason: string; clearSession?: boolean; };
99
106
  /** Reset the current flow (or jump to a step within it). */
100
107
  reset?: true | { step?: string; clearData?: boolean; reason?: string; };
108
+
109
+ // ── Verbatim utterance ───────────────────────────────────────────
101
110
  /** Verbatim reply text to send to the user. */
102
111
  reply?: string;
112
+
113
+ // ── State writes ─────────────────────────────────────────────────
103
114
  /** Partial context update applied before the next turn. */
104
115
  contextUpdate?: Partial<TContext>;
105
116
  /** Partial data update applied before the next turn. */
106
117
  dataUpdate?: Partial<TData>;
107
- }
108
118
 
109
- /**
110
- * PreDirective — what `prepare` hooks and `onEnter` hooks return.
111
- * Adds prompt/tool shaping that only makes sense BEFORE the LLM call this turn.
112
- * Extends Directive — anything a Directive can do, a PreDirective can do too.
113
- *
114
- * NOT persistable: contains live Tool references and a halt flag that
115
- * has no meaning across turns. This type is transient (one-turn lifetime).
116
- */
117
- export interface PreDirective<TContext = unknown, TData = unknown>
118
- extends Directive<TContext, TData> {
119
+ // ── Pre-LLM augmentation (one-turn lifetime) ─────────────────────
119
120
  /**
120
121
  * Sentences to append to the system prompt for THIS turn only.
121
122
  * Wired through PromptComposer's per-turn appendage slot.
123
+ *
124
+ * Only meaningful in pre-LLM hooks (`onEnter`, `prepare`). Ignored with
125
+ * a WARN log when emitted from post-LLM hooks or persisted.
122
126
  */
123
127
  appendPrompt?: string[];
124
128
 
125
129
  /**
126
130
  * Tools available for THIS turn only. Stacked on top of agent/flow/step
127
131
  * tool scopes via ToolManager's transient layer.
132
+ *
133
+ * Only meaningful in pre-LLM hooks (`onEnter`, `prepare`). Ignored with
134
+ * a WARN log when emitted from post-LLM hooks or persisted.
128
135
  */
129
- injectTools?: Array<Tool<TContext, TData>>;
136
+ injectTools?: Tool[];
130
137
 
131
138
  /**
132
139
  * If true, skip the LLM call entirely this turn.
@@ -135,10 +142,15 @@ export interface PreDirective<TContext = unknown, TData = unknown>
135
142
  * assistant output (`stoppedReason: 'reply'`). When `halt` is true
136
143
  * without `reply`, the turn produces an empty assistant message
137
144
  * (`stoppedReason: 'halt'`).
145
+ *
146
+ * Only meaningful in pre-LLM hooks (`onEnter`, `prepare`). Ignored with
147
+ * a WARN log when emitted from post-LLM hooks or persisted.
138
148
  */
139
149
  halt?: boolean;
140
150
  }
141
151
 
152
+
153
+
142
154
  /**
143
155
  * Context passed to a `BranchPredicate` function.
144
156
  *
@@ -241,7 +253,7 @@ export interface StepRef {
241
253
  /**
242
254
  * Flow lifecycle hooks for managing flow-specific data and behavior.
243
255
  *
244
- * Pre-LLM hooks (`onEnter`) return `void | PreDirective`.
256
+ * Pre-LLM hooks (`onEnter`) return `void | Directive`.
245
257
  * Post-LLM hooks (`onComplete`) return `void | Directive`.
246
258
  * Informational hooks (`onExit`) return `void`.
247
259
  * Data hooks (`onDataUpdate`, `onContextUpdate`) retain their v1 signatures.
@@ -249,11 +261,12 @@ export interface StepRef {
249
261
  export interface FlowLifecycleHooks<TContext = unknown, TData = unknown> {
250
262
  /**
251
263
  * Called when the flow is first entered.
252
- * May return a PreDirective to augment the prompt, inject tools, or halt.
264
+ * May return a Directive to augment the prompt, inject tools, halt, or redirect.
265
+ * Pre-LLM fields (`appendPrompt`, `injectTools`, `halt`) are honored here.
253
266
  */
254
267
  onEnter?: (
255
268
  ctx: HookContext<TContext, TData>
256
- ) => void | PreDirective<TContext, TData> | Promise<void | PreDirective<TContext, TData>>;
269
+ ) => void | Directive<TContext, TData> | Promise<void | Directive<TContext, TData>>;
257
270
 
258
271
  /**
259
272
  * Called when the flow is exited. Informational only — cannot influence flow control.
@@ -401,18 +414,19 @@ export interface FlowOptions<TContext = unknown, TData = unknown> {
401
414
  /**
402
415
  * Step lifecycle hooks for managing step-specific behavior.
403
416
  *
404
- * Pre-LLM hooks (`onEnter`, `prepare`) return `void | PreDirective`.
417
+ * Pre-LLM hooks (`onEnter`, `prepare`) return `void | Directive`.
405
418
  * Post-LLM hooks (`finalize`) return `void | Directive`.
406
419
  * Informational hooks (`onExit`) return `void`.
407
420
  */
408
421
  export interface StepLifecycleHooks<TContext = unknown, TData = unknown> {
409
422
  /**
410
- * Called on step entry. May return a PreDirective to augment the prompt,
411
- * inject tools, or halt.
423
+ * Called on step entry. May return a Directive to augment the prompt,
424
+ * inject tools, halt, or redirect.
425
+ * Pre-LLM fields (`appendPrompt`, `injectTools`, `halt`) are honored here.
412
426
  */
413
427
  onEnter?: (
414
428
  ctx: HookContext<TContext, TData>
415
- ) => void | PreDirective<TContext, TData> | Promise<void | PreDirective<TContext, TData>>;
429
+ ) => void | Directive<TContext, TData> | Promise<void | Directive<TContext, TData>>;
416
430
 
417
431
  /**
418
432
  * Called when the step is exited. Informational only — cannot influence flow control.
@@ -424,12 +438,13 @@ export interface StepLifecycleHooks<TContext = unknown, TData = unknown> {
424
438
  ) => void | Promise<void>;
425
439
 
426
440
  /**
427
- * Called pre-LLM. May return a PreDirective to augment the prompt,
441
+ * Called pre-LLM. May return a Directive to augment the prompt,
428
442
  * inject tools, or halt the LLM call.
443
+ * Pre-LLM fields (`appendPrompt`, `injectTools`, `halt`) are honored here.
429
444
  */
430
445
  prepare?: (
431
446
  ctx: HookContext<TContext, TData>
432
- ) => void | PreDirective<TContext, TData> | Promise<void | PreDirective<TContext, TData>>;
447
+ ) => void | Directive<TContext, TData> | Promise<void | Directive<TContext, TData>>;
433
448
 
434
449
  /**
435
450
  * Called post-LLM. May return a Directive to redirect flow, write state,
@@ -524,7 +539,7 @@ export interface StepOptions<TContext = unknown, TData = unknown> {
524
539
  * at construction time.
525
540
  *
526
541
  * `onEnter` and `prepare` hooks fire normally before the reply is rendered.
527
- * If `prepare` returns a `PreDirective` with its own `reply` field, the
542
+ * If `prepare` returns a Directive with its own `reply` field, the
528
543
  * hook-emitted reply wins (last-emission-wins per Algorithm 4).
529
544
  *
530
545
  * After emission, `onExit` fires and `branches` are resolved for next step.
@@ -52,7 +52,6 @@ export type {
52
52
  FlowLifecycleHooks,
53
53
  StepLifecycleHooks,
54
54
  Directive,
55
- PreDirective,
56
55
  BranchEntry,
57
56
  BranchMap,
58
57
  BranchPredicate,
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { SessionState } from "./session";
11
- import type { PreDirective } from "./flow";
11
+ import type { Directive } from "./flow";
12
12
  import type { Event } from "./history";
13
13
 
14
14
  // ──────────────────────────────────────────────────────────────────────────────
@@ -100,19 +100,18 @@ export type SignalPredicate<TContext = unknown, TData = unknown> = (
100
100
 
101
101
  /**
102
102
  * SignalDirective — what signal handlers return.
103
- * Extends `PreDirective` (which extends `Directive`). Adds signal-specific
104
- * fields: `stopOtherSignals` and `replyWith`.
103
+ * Extends `Directive`. Adds signal-specific fields: `stopOtherSignals` and `replyWith`.
105
104
  *
106
105
  * All position-control (`goTo`, `goToStep`, `complete`, `abort`, `reset`),
107
106
  * state writes (`dataUpdate`, `contextUpdate`), prompt augmentation
108
107
  * (`appendPrompt`, `injectTools`), `reply`, and `halt` are inherited unchanged.
109
108
  *
110
109
  * Post-phase drop rules: when returned in the post-phase, `appendPrompt`,
111
- * `injectTools`, and `halt` are dropped with a debug warning — they have
110
+ * `injectTools`, and `halt` are dropped with a WARN log — they have
112
111
  * no meaning after the LLM call has already completed.
113
112
  */
114
113
  export interface SignalDirective<TContext = unknown, TData = unknown>
115
- extends PreDirective<TContext, TData> {
114
+ extends Directive<TContext, TData> {
116
115
  /**
117
116
  * Stop processing remaining signals for this phase after this handler.
118
117
  * Does not affect the other phase.
@@ -262,7 +262,7 @@ export function sessionStepToData<TData = Record<string, unknown>>(
262
262
  currentStep?: string;
263
263
  collectedData: CollectedStateData<TData>;
264
264
  } {
265
- // Strip PreDirective fields before persisting pendingDirective
265
+ // Strip pre-LLM-only fields before persisting pendingDirective
266
266
  let pendingDirective: SessionState<TData>["pendingDirective"] | undefined;
267
267
  if (session.pendingDirective) {
268
268
  pendingDirective = stripPreDirectiveFields(session.pendingDirective);
@@ -354,7 +354,7 @@ export function sessionDataToStep<TData = Record<string, unknown>>(
354
354
 
355
355
 
356
356
  /**
357
- * Strip PreDirective-only fields from a directive before persistence.
357
+ * Strip pre-LLM-only fields from a directive before persistence.
358
358
  *
359
359
  * `appendPrompt`, `injectTools`, and `halt` are transient (one-turn lifetime)
360
360
  * and must not be serialized. This is a belt-and-suspenders safety net —
@@ -380,8 +380,8 @@ function stripPreDirectiveFields<TData>(
380
380
  ].filter(Boolean);
381
381
 
382
382
  if (droppedFields.length > 0) {
383
- logger.debug(
384
- `[createPersistedState] Stripped PreDirective-only fields before persistence: ${droppedFields.join(", ")}`
383
+ logger.warn(
384
+ `[createPersistedState] Ignoring pre-LLM-only fields before persistence (these have no effect outside pre-LLM hooks): ${droppedFields.join(", ")}`
385
385
  );
386
386
  }
387
387
 
@@ -393,7 +393,7 @@ function stripPreDirectiveFields<TData>(
393
393
  *
394
394
  * This is the shared helper that every persistence adapter should call before
395
395
  * writing session state. It ensures:
396
- * - `pendingDirective` has PreDirective-only fields (`appendPrompt`,
396
+ * - `pendingDirective` has pre-LLM-only fields (`appendPrompt`,
397
397
  * `injectTools`, `halt`) stripped (these are one-turn-lifetime and not
398
398
  * serializable across turns).
399
399
  * - `pendingDirective` is omitted from the result when `undefined` (adapters