reactive-fsm 1.1.0 → 1.2.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/README.md +135 -329
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +1 -0
- package/dist/adapters/openai.js.map +1 -1
- package/dist/adapters/vercel-ai.d.ts.map +1 -1
- package/dist/adapters/vercel-ai.js +1 -0
- package/dist/adapters/vercel-ai.js.map +1 -1
- package/dist/core/machine.d.ts +11 -0
- package/dist/core/machine.d.ts.map +1 -1
- package/dist/core/machine.js +32 -3
- package/dist/core/machine.js.map +1 -1
- package/dist/core/tool-gating.d.ts +8 -0
- package/dist/core/tool-gating.d.ts.map +1 -1
- package/dist/core/tool-gating.js +14 -0
- package/dist/core/tool-gating.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
# reactive-fsm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/reactive-fsm)
|
|
4
|
+
[](https://github.com/roddcode/reactive-fsm/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
**Deterministic control for AI agents with tool calling.** Define what tools an LLM can call at each step of a conversation. Zero dependencies. TypeScript.
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
stateDiagram-v2
|
|
10
|
+
GREETING: GREETING<br/><i>greet, show_services</i>
|
|
11
|
+
IDENTITY: IDENTITY<br/><i>collect_name, validate_dni</i>
|
|
12
|
+
BOOKING: BOOKING<br/><i>check_slots, reserve</i>
|
|
13
|
+
PAYMENT: PAYMENT<br/><i>process_payment</i>
|
|
14
|
+
CONFIRMED: CONFIRMED<br/><i>send_receipt</i>
|
|
15
|
+
[*] --> GREETING
|
|
16
|
+
[*] --> IDENTITY
|
|
17
|
+
[*] --> BOOKING
|
|
18
|
+
[*] --> PAYMENT
|
|
19
|
+
[*] --> CONFIRMED
|
|
20
|
+
GREETING --> [*]
|
|
21
|
+
IDENTITY --> [*]
|
|
22
|
+
BOOKING --> [*]
|
|
23
|
+
PAYMENT --> [*]
|
|
24
|
+
CONFIRMED --> [*]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> Generated by `fsm.toMermaid()`. Paste into any README, PR, or Notion.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
- **Tool gating per state.** In `PAYMENT`, the LLM sees only `process_payment` and `cancel_order`. No browsing, no upselling, no hallucinating tools that don't belong.
|
|
32
|
+
- **Loop shield.** Detects when the LLM enters a tool-calling loop and forces it to stop. Configurable threshold. Prevents runaway token costs.
|
|
33
|
+
- **Gate refresh mid-turn.** If a tool execution changes the conversation state (e.g., identity validated → move to booking), tools update immediately without restarting the FSM.
|
|
34
|
+
- **Cross-session resume.** `snapshot()` returns `{ state }`. Store it anywhere. Next serverless invocation picks up where it left off.
|
|
35
|
+
- **5 providers, 1 API.** Vercel AI SDK, OpenAI, Anthropic, LangChain, Google Gemini. Same FSM config, different adapter.
|
|
36
|
+
|
|
37
|
+
## Get started
|
|
4
38
|
|
|
5
39
|
```bash
|
|
6
40
|
pnpm add reactive-fsm
|
|
7
|
-
# o npm install reactive-fsm
|
|
8
41
|
```
|
|
9
42
|
|
|
10
|
-
## Concepts
|
|
11
|
-
|
|
12
|
-
| Concept | Role |
|
|
13
|
-
|---------|------|
|
|
14
|
-
| **FSM** | Manages states and which tool names are allowed at each state |
|
|
15
|
-
| **Tool Gating** | Registry of tool entries with runtime conditions and builders, filtered by gate |
|
|
16
|
-
| **Loop Shield** | Detects consecutive tool-call loops and signals the adapter to force-stop |
|
|
17
|
-
| **Adapters** | Translate the FSM into SDK-native format (Vercel AI SDK, OpenAI) |
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Quick Start
|
|
22
|
-
|
|
23
|
-
### 1. Define your FSM
|
|
24
|
-
|
|
25
43
|
```typescript
|
|
26
44
|
import { createFSM } from 'reactive-fsm'
|
|
27
45
|
|
|
@@ -38,394 +56,182 @@ const fsm = createFSM({
|
|
|
38
56
|
|
|
39
57
|
fsm.currentState // 'IDENTITY'
|
|
40
58
|
fsm.allowedTools // ['validate_dni']
|
|
59
|
+
|
|
41
60
|
fsm.transitionTo('BOOKING')
|
|
42
61
|
fsm.allowedTools // ['check_availability', 'reserve_slot']
|
|
43
62
|
```
|
|
44
63
|
|
|
45
|
-
###
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
import type { ToolEntry } from 'reactive-fsm'
|
|
49
|
-
|
|
50
|
-
interface AppCtx { contactId: string; role: 'user' | 'admin' }
|
|
51
|
-
|
|
52
|
-
const registry: ToolEntry<AppCtx>[] = [
|
|
53
|
-
{
|
|
54
|
-
name: 'validate_dni',
|
|
55
|
-
gates: ['IDENTITY'],
|
|
56
|
-
build: (ctx) => ({
|
|
57
|
-
description: `Validate DNI for ${ctx.contactId}`,
|
|
58
|
-
parameters: {},
|
|
59
|
-
execute: async () => ({ valid: true }),
|
|
60
|
-
}),
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'check_availability',
|
|
64
|
-
gates: ['BOOKING'],
|
|
65
|
-
build: () => ({
|
|
66
|
-
description: 'Check slot availability',
|
|
67
|
-
parameters: {},
|
|
68
|
-
execute: async () => ({ slots: ['10:00', '11:00'] }),
|
|
69
|
-
}),
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: 'reserve_slot',
|
|
73
|
-
gates: ['BOOKING'],
|
|
74
|
-
condition: (ctx) => ctx.contactId !== '',
|
|
75
|
-
build: () => ({
|
|
76
|
-
description: 'Reserve a time slot',
|
|
77
|
-
parameters: {},
|
|
78
|
-
execute: async () => ({ booked: true }),
|
|
79
|
-
}),
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'admin_panel',
|
|
83
|
-
gates: ['IDENTITY', 'BOOKING', 'PAYMENT'],
|
|
84
|
-
condition: (ctx) => ctx.role === 'admin',
|
|
85
|
-
build: () => ({
|
|
86
|
-
description: 'Access admin controls',
|
|
87
|
-
parameters: {},
|
|
88
|
-
execute: async () => ({ panel: 'open' }),
|
|
89
|
-
}),
|
|
90
|
-
},
|
|
91
|
-
]
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### 3. Use an adapter
|
|
95
|
-
|
|
96
|
-
#### Vercel AI SDK
|
|
64
|
+
### With Vercel AI SDK (3 lines)
|
|
97
65
|
|
|
98
66
|
```typescript
|
|
99
67
|
import { createVercelAdapter } from 'reactive-fsm/adapters/vercel-ai'
|
|
100
|
-
import { generateText } from 'ai'
|
|
101
|
-
import { openai } from '@ai-sdk/openai'
|
|
102
68
|
|
|
103
|
-
const adapter = createVercelAdapter(fsm, registry,
|
|
69
|
+
const adapter = createVercelAdapter(fsm, registry, context)
|
|
104
70
|
|
|
105
71
|
const response = await generateText({
|
|
106
72
|
model: openai('gpt-4o'),
|
|
107
|
-
messages
|
|
108
|
-
...adapter.inject(), //
|
|
73
|
+
messages,
|
|
74
|
+
...adapter.inject(), // { tools, prepareStep }
|
|
109
75
|
})
|
|
110
76
|
```
|
|
111
77
|
|
|
112
|
-
|
|
78
|
+
### With OpenAI (imperative loop)
|
|
113
79
|
|
|
114
80
|
```typescript
|
|
115
81
|
import { createOpenAIAdapter } from 'reactive-fsm/adapters/openai'
|
|
116
|
-
import OpenAI from 'openai'
|
|
117
82
|
|
|
118
|
-
const
|
|
119
|
-
const adapter = createOpenAIAdapter(fsm, registry, appCtx)
|
|
120
|
-
|
|
121
|
-
const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
|
|
122
|
-
{ role: 'user', content: 'Quiero agendar una cita' },
|
|
123
|
-
]
|
|
83
|
+
const adapter = createOpenAIAdapter(fsm, registry, context)
|
|
124
84
|
|
|
125
85
|
while (true) {
|
|
126
86
|
const { tools } = adapter.inject()
|
|
127
|
-
|
|
128
87
|
const response = await openai.chat.completions.create({
|
|
129
|
-
model: 'gpt-4o',
|
|
130
|
-
messages,
|
|
131
|
-
tools,
|
|
88
|
+
model: 'gpt-4o', messages, tools,
|
|
132
89
|
tool_choice: adapter.isLooping() ? 'none' : 'auto',
|
|
133
90
|
})
|
|
134
|
-
|
|
135
|
-
const msg = response.choices[0].message
|
|
136
|
-
messages.push(msg)
|
|
137
|
-
|
|
138
|
-
if (msg.tool_calls?.length) {
|
|
139
|
-
adapter.registerToolCall()
|
|
140
|
-
|
|
141
|
-
// Execute tools, push results to messages
|
|
142
|
-
for (const tc of msg.tool_calls) {
|
|
143
|
-
const result = await executeTool(tc.function.name, tc.function.arguments)
|
|
144
|
-
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result) })
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// If a tool mutated the DB state, refresh the gate
|
|
148
|
-
// adapter.refreshGate('BOOKING')
|
|
149
|
-
continue
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(msg.content) // final response
|
|
153
|
-
break
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### Anthropic SDK
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
import { createAnthropicAdapter } from 'reactive-fsm/adapters/anthropic'
|
|
161
|
-
import Anthropic from '@anthropic-ai/sdk'
|
|
162
|
-
|
|
163
|
-
const anthropic = new Anthropic()
|
|
164
|
-
const adapter = createAnthropicAdapter(fsm, registry, appCtx)
|
|
165
|
-
|
|
166
|
-
const messages = [{ role: 'user' as const, content: 'Quiero agendar una cita' }]
|
|
167
|
-
|
|
168
|
-
while (true) {
|
|
169
|
-
const { tools } = adapter.inject()
|
|
170
|
-
|
|
171
|
-
const response = await anthropic.messages.create({
|
|
172
|
-
model: 'claude-sonnet-4-20250514',
|
|
173
|
-
max_tokens: 1024,
|
|
174
|
-
messages,
|
|
175
|
-
tools,
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
const toolUses = response.content.filter(c => c.type === 'tool_use')
|
|
179
|
-
messages.push({ role: 'assistant', content: response.content })
|
|
180
|
-
|
|
181
|
-
if (toolUses.length > 0) {
|
|
182
|
-
adapter.registerToolCall()
|
|
183
|
-
if (adapter.isLooping()) break
|
|
184
|
-
|
|
185
|
-
const toolResults = toolUses.map(tc => ({
|
|
186
|
-
type: 'tool_result' as const,
|
|
187
|
-
tool_use_id: tc.id,
|
|
188
|
-
content: JSON.stringify(await executeTool(tc.name, tc.input)),
|
|
189
|
-
}))
|
|
190
|
-
messages.push({ role: 'user', content: toolResults })
|
|
191
|
-
continue
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return response.content.find(c => c.type === 'text')?.text
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
#### LangChain
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
import { wrapWithFSM } from 'reactive-fsm/adapters/langchain'
|
|
202
|
-
|
|
203
|
-
const wrapper = wrapWithFSM(fsm, registry, appCtx)
|
|
204
|
-
// wrapper.getTools() returns available tools for current gate
|
|
205
|
-
// wrapper.shouldStop() — check before each agent step
|
|
206
|
-
// wrapper.onToolCall() — call after each tool invocation
|
|
207
|
-
// wrapper.onStateChange('BOOKING') — refresh gate mid-flow
|
|
208
|
-
|
|
209
|
-
// Integrate with your existing LangChain executor:
|
|
210
|
-
const currentTools = Object.values(wrapper.getTools())
|
|
211
|
-
// …pass to your agent, re-fetch tools each iteration
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
#### Google Gemini
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
import { createGeminiAdapter } from 'reactive-fsm/adapters/gemini'
|
|
218
|
-
import { GoogleGenerativeAI } from '@google/generative-ai'
|
|
219
|
-
|
|
220
|
-
const genAI = new GoogleGenerativeAI(apiKey)
|
|
221
|
-
const adapter = createGeminiAdapter(fsm, registry, appCtx)
|
|
222
|
-
const model = genAI.getGenerativeModel({
|
|
223
|
-
model: 'gemini-2.0-flash',
|
|
224
|
-
tools: adapter.inject().tools,
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
const chat = model.startChat()
|
|
228
|
-
|
|
229
|
-
while (true) {
|
|
230
|
-
const { tools } = adapter.inject()
|
|
231
|
-
const result = await chat.sendMessage(userMessage)
|
|
232
|
-
const calls = result.response.functionCalls()
|
|
233
|
-
|
|
234
|
-
if (calls?.length) {
|
|
91
|
+
if (response.choices[0].message.tool_calls?.length) {
|
|
235
92
|
adapter.registerToolCall()
|
|
236
|
-
if (adapter.isLooping()) break
|
|
237
|
-
|
|
238
|
-
const parts = calls.map(call => ({
|
|
239
|
-
functionResponse: { name: call.name, response: executeTool(call.name, call.args) },
|
|
240
|
-
}))
|
|
241
|
-
await chat.sendMessage(parts)
|
|
242
93
|
continue
|
|
243
94
|
}
|
|
244
|
-
|
|
245
|
-
return result.response.text()
|
|
95
|
+
return response.choices[0].message.content
|
|
246
96
|
}
|
|
247
97
|
```
|
|
248
98
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
## Real-World Patterns
|
|
99
|
+
## Real-world use cases
|
|
252
100
|
|
|
253
|
-
### Booking
|
|
101
|
+
### Booking funnel
|
|
254
102
|
|
|
255
103
|
```typescript
|
|
256
|
-
const
|
|
104
|
+
const booking = createFSM({
|
|
257
105
|
initialState: 'GREETING',
|
|
258
106
|
states: ['GREETING', 'IDENTITY', 'SCHEDULING', 'CONFIRMED'],
|
|
259
107
|
tools: {
|
|
260
|
-
GREETING:
|
|
261
|
-
IDENTITY:
|
|
262
|
-
SCHEDULING:
|
|
263
|
-
CONFIRMED:
|
|
108
|
+
GREETING: ['greet', 'show_services'],
|
|
109
|
+
IDENTITY: ['collect_name', 'collect_contact'],
|
|
110
|
+
SCHEDULING: ['check_slots', 'reserve_appointment'],
|
|
111
|
+
CONFIRMED: ['send_confirmation', 'add_to_calendar'],
|
|
112
|
+
},
|
|
113
|
+
prompts: {
|
|
114
|
+
SCHEDULING: 'Offer available slots. Do not ask for identity again.',
|
|
264
115
|
},
|
|
265
116
|
loopShield: { enabled: true, maxConsecutiveTools: 3 },
|
|
266
117
|
})
|
|
267
|
-
|
|
268
|
-
// Flow: GREETING → user shows interest → IDENTITY → collects data → SCHEDULING → booked → CONFIRMED
|
|
269
118
|
```
|
|
270
119
|
|
|
271
|
-
|
|
120
|
+
In `SCHEDULING`, `collect_name` is gone. The LLM cannot loop back asking for data it already has.
|
|
121
|
+
|
|
122
|
+
### Payment gate
|
|
272
123
|
|
|
273
124
|
```typescript
|
|
274
|
-
const
|
|
275
|
-
initialState: '
|
|
276
|
-
states: ['
|
|
125
|
+
const checkout = createFSM({
|
|
126
|
+
initialState: 'BROWSING',
|
|
127
|
+
states: ['BROWSING', 'CART', 'PAYMENT', 'CONFIRMED'],
|
|
277
128
|
tools: {
|
|
278
|
-
|
|
279
|
-
|
|
129
|
+
BROWSING: ['search', 'add_to_cart'],
|
|
130
|
+
CART: ['view_cart', 'apply_coupon', 'checkout'],
|
|
131
|
+
PAYMENT: ['process_payment', 'cancel_order'],
|
|
280
132
|
CONFIRMED: ['send_receipt'],
|
|
281
133
|
},
|
|
134
|
+
guard: (from, to) => to === 'PAYMENT' && from !== 'CART' ? false : true,
|
|
282
135
|
})
|
|
283
|
-
|
|
284
|
-
// When entering PAYMENT, the adapter removes all shopping tools.
|
|
285
|
-
// The LLM can ONLY call process_payment or cancel_order.
|
|
286
136
|
```
|
|
287
137
|
|
|
288
|
-
|
|
138
|
+
In `PAYMENT`: 2 tools. The LLM cannot browse, cannot suggest alternatives, cannot negotiate. The guard blocks jumping from BROWSING straight to PAYMENT.
|
|
139
|
+
|
|
140
|
+
### Customer support with forced escalation
|
|
289
141
|
|
|
290
142
|
```typescript
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
143
|
+
const support = createFSM({
|
|
144
|
+
initialState: 'TRIAGE',
|
|
145
|
+
states: ['TRIAGE', 'DIAGNOSIS', 'SOLUTION', 'ESCALATION'],
|
|
146
|
+
tools: {
|
|
147
|
+
TRIAGE: ['classify_issue', 'verify_account'],
|
|
148
|
+
DIAGNOSIS: ['search_kb', 'check_status'],
|
|
149
|
+
SOLUTION: ['apply_fix', 'send_guide'],
|
|
150
|
+
ESCALATION: ['transfer_to_human'],
|
|
298
151
|
},
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// Regular user → only view_profile
|
|
302
|
-
// Admin → view_profile + delete_user
|
|
152
|
+
loopShield: { enabled: true, maxConsecutiveTools: 3 },
|
|
153
|
+
})
|
|
303
154
|
```
|
|
304
155
|
|
|
305
|
-
|
|
156
|
+
Loop shield triggers after 3 failed fix attempts. `ESCALATION` has exactly one tool. The bot cannot stay in the loop — it must escalate.
|
|
157
|
+
|
|
158
|
+
## Adapters
|
|
159
|
+
|
|
160
|
+
| Provider | Import | Loop shield |
|
|
161
|
+
|----------|--------|-------------|
|
|
162
|
+
| Vercel AI SDK | `reactive-fsm/adapters/vercel-ai` | Automatic via `prepareStep` |
|
|
163
|
+
| OpenAI | `reactive-fsm/adapters/openai` | Manual via `isLooping()` |
|
|
164
|
+
| Anthropic | `reactive-fsm/adapters/anthropic` | Manual via `isLooping()` |
|
|
165
|
+
| LangChain | `reactive-fsm/adapters/langchain` | Manual via `shouldStop()` |
|
|
166
|
+
| Google Gemini | `reactive-fsm/adapters/gemini` | Manual via `isLooping()` |
|
|
167
|
+
|
|
168
|
+
All adapters share the same pattern: build an FSM once, pass it to any adapter, call `inject()` to get provider-formatted tools.
|
|
306
169
|
|
|
307
|
-
##
|
|
170
|
+
## vs LangGraph
|
|
308
171
|
|
|
309
|
-
|
|
172
|
+
| | reactive-fsm | LangGraph |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| Dependencies | 0 (core) | LangChain + sub-dependencies |
|
|
175
|
+
| Bundle size | ~7 KB | ~5 MB |
|
|
176
|
+
| Define a 3-state FSM | 5 lines | 30+ lines |
|
|
177
|
+
| Tool gating per state | Declarative (`tools: { ... }`) | Manual (`route_tools(state)`) |
|
|
178
|
+
| Loop shield | Built-in | Not built-in |
|
|
179
|
+
| Gate refresh mid-turn | Automatic | Manual |
|
|
180
|
+
| Providers | Any (5 adapters) | LangChain ecosystem |
|
|
181
|
+
|
|
182
|
+
[Full migration guide →](#) _(coming soon)_
|
|
183
|
+
|
|
184
|
+
## API
|
|
310
185
|
|
|
311
186
|
```typescript
|
|
312
187
|
interface FSMConfig {
|
|
313
188
|
initialState: string
|
|
314
189
|
states: string[]
|
|
315
|
-
tools: Record<string, string[]>
|
|
316
|
-
loopShield?:
|
|
190
|
+
tools: Record<string, string[]>
|
|
191
|
+
loopShield?: { enabled: boolean; maxConsecutiveTools: number }
|
|
192
|
+
onTransition?: (from: string, to: string) => void
|
|
193
|
+
guard?: (from: string, to: string) => boolean // veto transitions
|
|
194
|
+
prompts?: Record<string, string> // state → system prompt
|
|
195
|
+
snapshot?: { state: string } // restore from saved state
|
|
317
196
|
}
|
|
318
197
|
|
|
319
|
-
interface
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
transitionTo(state
|
|
198
|
+
interface FSMPublic {
|
|
199
|
+
currentState: string // getter
|
|
200
|
+
allowedTools: string[] // getter
|
|
201
|
+
isLooping: boolean // getter
|
|
202
|
+
transitionTo(state): void
|
|
203
|
+
snapshot(): { state: string }
|
|
204
|
+
toMermaid(): string // Mermaid stateDiagram-v2
|
|
324
205
|
}
|
|
325
206
|
```
|
|
326
207
|
|
|
327
|
-
### `buildToolsForGate(gate, ctx, registry)`
|
|
328
|
-
|
|
329
208
|
```typescript
|
|
330
209
|
interface ToolEntry<TContext = any> {
|
|
331
210
|
name: string
|
|
332
211
|
gates: string[]
|
|
333
|
-
condition?: (ctx: TContext) => boolean
|
|
334
|
-
build: (ctx: TContext) => unknown
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
function buildToolsForGate<TContext>(
|
|
338
|
-
gate: string,
|
|
339
|
-
ctx: TContext,
|
|
340
|
-
registry: ToolEntry<TContext>[]
|
|
341
|
-
): Record<string, unknown>
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### `createVercelAdapter(fsm, registry, context)`
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
const adapter = createVercelAdapter(fsm, registry, context)
|
|
348
|
-
|
|
349
|
-
const { tools, prepareStep } = adapter.inject()
|
|
350
|
-
adapter.refreshGate('PAYMENT') // external gate change (e.g., after DB mutation)
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
`prepareStep` automatically handles loop shield detection by counting consecutive tool steps from the history. When `maxConsecutiveTools` is exceeded, it returns `{ toolChoice: 'none' }`.
|
|
354
|
-
|
|
355
|
-
### `createOpenAIAdapter(fsm, registry, context)`
|
|
356
|
-
|
|
357
|
-
```typescript
|
|
358
|
-
const adapter = createOpenAIAdapter(fsm, registry, context)
|
|
359
|
-
|
|
360
|
-
const { tools } = adapter.inject() // → { tools: OpenAI-formatted array }
|
|
361
|
-
adapter.isLooping() // → boolean (check before each API call)
|
|
362
|
-
adapter.registerToolCall() // → track tool call for loop shield
|
|
363
|
-
adapter.resetLoopShield() // → reset counter
|
|
364
|
-
adapter.refreshGate('PAYMENT') // → external gate change
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
Since the OpenAI SDK is stateless, the consumer controls the orchestration loop. Use `isLooping()` to decide `tool_choice` and `registerToolCall()` after each API call that invoked tools.
|
|
368
|
-
|
|
369
|
-
### `createLoopShield(config)`
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
interface LoopShieldConfig {
|
|
373
|
-
enabled: boolean
|
|
374
|
-
maxConsecutiveTools: number
|
|
212
|
+
condition?: (ctx: TContext) => boolean
|
|
213
|
+
build: (ctx: TContext) => unknown
|
|
214
|
+
schema?: unknown // Zod/Valibot/ArkType schema
|
|
375
215
|
}
|
|
376
216
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
shield.isLooping() // boolean
|
|
380
|
-
shield.reset()
|
|
217
|
+
buildToolsForGate(gate, ctx, registry): Record<string, unknown>
|
|
218
|
+
validateWith(schema, args): { ok, data } | { ok, error }
|
|
381
219
|
```
|
|
382
220
|
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Why reactive-fsm
|
|
386
|
-
|
|
387
|
-
| | reactive-fsm | LangGraph | Manual |
|
|
388
|
-
|---|---|---|---|
|
|
389
|
-
| Dependencies | **0** (core) | LangChain + several | None |
|
|
390
|
-
| Bundle size | **~7 KB** | ~5 MB | — |
|
|
391
|
-
| Setup | **3 lines** | 30+ lines | 50+ lines |
|
|
392
|
-
| Loop shield | **Built-in** | None | Manual |
|
|
393
|
-
| Tool gating | **Per-state** | Manual filter | Manual |
|
|
394
|
-
| Gate refresh | **Automatic** | None | None |
|
|
395
|
-
| Provider | **Any** | LangChain-only | Any |
|
|
396
|
-
| TypeScript | **100% typed** | Partial | Varies |
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
221
|
## Architecture
|
|
401
222
|
|
|
402
223
|
```
|
|
403
|
-
src/
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
│ └── loop-shield.ts # createLoopShield() — cognitive loop detection
|
|
408
|
-
├── adapters/
|
|
409
|
-
│ ├── vercel-ai.ts # createVercelAdapter() — Vercel AI SDK
|
|
410
|
-
│ └── openai.ts # createOpenAIAdapter() — OpenAI SDK
|
|
411
|
-
└── index.ts
|
|
412
|
-
```
|
|
224
|
+
src/core/ — zero npm dependencies
|
|
225
|
+
machine.ts createFSM()
|
|
226
|
+
tool-gating.ts buildToolsForGate(), validateWith()
|
|
227
|
+
loop-shield.ts createLoopShield()
|
|
413
228
|
|
|
414
|
-
|
|
415
|
-
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
The loop shield prevents LLMs from entering infinite tool-calling loops.
|
|
423
|
-
|
|
424
|
-
- **Vercel adapter**: `prepareStep` counts consecutive tool steps from history and forces `toolChoice: 'none'` when `maxConsecutiveTools` is exceeded.
|
|
425
|
-
- **OpenAI adapter**: the consumer checks `isLooping()` before each API call and sets `tool_choice: 'none'` when it returns `true`. Calls `registerToolCall()` after each tool-invoking response.
|
|
426
|
-
|
|
427
|
-
---
|
|
428
|
-
|
|
429
|
-
## License
|
|
229
|
+
src/adapters/ — optional peer dependencies
|
|
230
|
+
vercel-ai.ts createVercelAdapter()
|
|
231
|
+
openai.ts createOpenAIAdapter()
|
|
232
|
+
anthropic.ts createAnthropicAdapter()
|
|
233
|
+
langchain.ts wrapWithFSM()
|
|
234
|
+
gemini.ts createGeminiAdapter()
|
|
235
|
+
```
|
|
430
236
|
|
|
431
|
-
MIT
|
|
237
|
+
MIT · [GitHub](https://github.com/roddcode/reactive-fsm) · [Issues](https://github.com/roddcode/reactive-fsm/issues)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,GAAG;IAC3C,MAAM,IAAI,kBAAkB,CAAC;IAC7B,SAAS,IAAI,OAAO,CAAC;IACrB,gBAAgB,IAAI,IAAI,CAAC;IACzB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,GAAG,EAChD,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,aAAa,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,GAAG;IAC3C,MAAM,IAAI,kBAAkB,CAAC;IAC7B,SAAS,IAAI,OAAO,CAAC;IACrB,gBAAgB,IAAI,IAAI,CAAC;IACzB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,GAAG,EAChD,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,aAAa,CAAC,QAAQ,CAAC,CAyBzB"}
|
package/dist/adapters/openai.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAcxE,MAAM,UAAU,mBAAmB,CACjC,GAAc,EACd,QAA+B,EAC/B,OAAiB;IAEjB,MAAM,IAAI,GAAG,GAAkB,CAAC;IAChC,OAAO;QACL,MAAM;YACJ,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YACxE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,CAAC;QAED,SAAS;YACP,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QAED,gBAAgB;YACd,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;QAED,eAAe;YACb,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;QAED,WAAW,CAAC,KAAa;YACvB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAcxE,MAAM,UAAU,mBAAmB,CACjC,GAAc,EACd,QAA+B,EAC/B,OAAiB;IAEjB,MAAM,IAAI,GAAG,GAAkB,CAAC;IAChC,OAAO;QACL,MAAM;YACJ,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YACxE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,CAAC;QAED,SAAS;YACP,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QAED,gBAAgB;YACd,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;QAED,eAAe;YACb,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;QAED,WAAW,CAAC,KAAa;YACvB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACtB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxD,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;CAC7G;AAED,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,GAAG;IAC3C,MAAM,IAAI,kBAAkB,CAAC;IAC7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAiBD,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,GAAG,EAChD,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,aAAa,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxD,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;CAC7G;AAED,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,GAAG;IAC3C,MAAM,IAAI,kBAAkB,CAAC;IAC7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAiBD,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,GAAG,EAChD,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,aAAa,CAAC,QAAQ,CAAC,CA8CzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel-ai.js","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAsBxE,SAAS,SAAS,CAChB,MAA+B,EAC/B,IAAY,EACZ,GAAY,EACZ,QAAqB;IAErB,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,GAAc,EACd,QAA+B,EAC/B,OAAiB;IAEjB,MAAM,IAAI,GAAG,GAAkB,CAAC;IAChC,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,IAAI,WAAW,GAAkB,IAAI,CAAC;IAEtC,OAAO;QACL,MAAM;YACJ,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;YAChC,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YAEjD,OAAO;gBACL,KAAK;gBACL,WAAW,EAAE,KAAK,EAAE,EAAE,KAAK,EAA2B,EAAE,EAAE;oBACxD,IAAI,oBAAoB,GAAG,CAAC,CAAC;oBAC7B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC3C,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;4BAC1C,oBAAoB,EAAE,CAAC;wBACzB,CAAC;6BAAM,CAAC;4BACN,MAAM;wBACR,CAAC;oBACH,CAAC;oBAED,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,oBAAoB,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC9C,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC3B,CAAC;oBAED,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;wBACnB,OAAO,EAAE,UAAU,EAAE,MAAe,EAAE,CAAC;oBACzC,CAAC;oBAED,IAAI,IAAI,CAAC,YAAY,KAAK,WAAW,EAAE,CAAC;wBACtC,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;wBAChC,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;oBACnD,CAAC;oBAED,OAAO,EAAE,CAAC;gBACZ,CAAC;aACF,CAAC;QACJ,CAAC;QAED,WAAW,CAAC,KAAa;YACvB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"vercel-ai.js","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAsBxE,SAAS,SAAS,CAChB,MAA+B,EAC/B,IAAY,EACZ,GAAY,EACZ,QAAqB;IAErB,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,GAAc,EACd,QAA+B,EAC/B,OAAiB;IAEjB,MAAM,IAAI,GAAG,GAAkB,CAAC;IAChC,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,IAAI,WAAW,GAAkB,IAAI,CAAC;IAEtC,OAAO;QACL,MAAM;YACJ,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;YAChC,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YAEjD,OAAO;gBACL,KAAK;gBACL,WAAW,EAAE,KAAK,EAAE,EAAE,KAAK,EAA2B,EAAE,EAAE;oBACxD,IAAI,oBAAoB,GAAG,CAAC,CAAC;oBAC7B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC3C,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;4BAC1C,oBAAoB,EAAE,CAAC;wBACzB,CAAC;6BAAM,CAAC;4BACN,MAAM;wBACR,CAAC;oBACH,CAAC;oBAED,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,oBAAoB,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC9C,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC3B,CAAC;oBAED,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;wBACnB,OAAO,EAAE,UAAU,EAAE,MAAe,EAAE,CAAC;oBACzC,CAAC;oBAED,IAAI,IAAI,CAAC,YAAY,KAAK,WAAW,EAAE,CAAC;wBACtC,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;wBAChC,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;oBACnD,CAAC;oBAED,OAAO,EAAE,CAAC;gBACZ,CAAC;aACF,CAAC;QACJ,CAAC;QAED,WAAW,CAAC,KAAa;YACvB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACtB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { type LoopShieldConfig } from './loop-shield';
|
|
2
|
+
export interface FSMSnapshot {
|
|
3
|
+
state: string;
|
|
4
|
+
}
|
|
2
5
|
export interface FSMConfig {
|
|
3
6
|
initialState: string;
|
|
4
7
|
states: string[];
|
|
5
8
|
tools: Record<string, string[]>;
|
|
6
9
|
loopShield?: LoopShieldConfig;
|
|
10
|
+
onTransition?: (from: string, to: string) => void;
|
|
11
|
+
guard?: (from: string, to: string) => boolean;
|
|
12
|
+
prompts?: Record<string, string>;
|
|
13
|
+
snapshot?: FSMSnapshot;
|
|
14
|
+
context?: Record<string, unknown>;
|
|
7
15
|
}
|
|
8
16
|
export interface FSMPublic {
|
|
9
17
|
readonly currentState: string;
|
|
10
18
|
readonly allowedTools: string[];
|
|
11
19
|
readonly isLooping: boolean;
|
|
12
20
|
transitionTo(state: string): void;
|
|
21
|
+
snapshot(): FSMSnapshot;
|
|
22
|
+
toMermaid(): string;
|
|
23
|
+
context: Record<string, unknown>;
|
|
13
24
|
}
|
|
14
25
|
export interface FSMInstance extends FSMPublic {
|
|
15
26
|
_registerToolCall(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../src/core/machine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAExE,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../src/core/machine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,IAAI,WAAW,CAAC;IACxB,SAAS,IAAI,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,iBAAiB,IAAI,IAAI,CAAC;IAC1B,gBAAgB,IAAI,IAAI,CAAC;IACzB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CAuFxD"}
|
package/dist/core/machine.js
CHANGED
|
@@ -4,11 +4,23 @@ export function createFSM(config) {
|
|
|
4
4
|
if (!validStates.has(config.initialState)) {
|
|
5
5
|
throw new Error(`Initial state "${config.initialState}" is not in the states list`);
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
if (config.snapshot && !validStates.has(config.snapshot.state)) {
|
|
8
|
+
throw new Error(`Snapshot state "${config.snapshot.state}" is not in the states list`);
|
|
9
|
+
}
|
|
10
|
+
let _currentState = config.snapshot?.state ?? config.initialState;
|
|
11
|
+
const _context = config.context ? { ...config.context } : {};
|
|
8
12
|
const shield = createLoopShield(config.loopShield ?? { enabled: false, maxConsecutiveTools: 3 });
|
|
9
13
|
function getAllowedTools() {
|
|
10
14
|
return config.tools[_currentState] ?? [];
|
|
11
15
|
}
|
|
16
|
+
function doTransition(state) {
|
|
17
|
+
const from = _currentState;
|
|
18
|
+
if (config.guard && !config.guard(from, state)) {
|
|
19
|
+
throw new Error(`Guard blocked transition from "${from}" to "${state}"`);
|
|
20
|
+
}
|
|
21
|
+
_currentState = state;
|
|
22
|
+
config.onTransition?.(from, state);
|
|
23
|
+
}
|
|
12
24
|
return {
|
|
13
25
|
get currentState() {
|
|
14
26
|
return _currentState;
|
|
@@ -23,7 +35,7 @@ export function createFSM(config) {
|
|
|
23
35
|
if (!validStates.has(state)) {
|
|
24
36
|
throw new Error(`Invalid state "${state}". Valid states: ${config.states.join(', ')}`);
|
|
25
37
|
}
|
|
26
|
-
|
|
38
|
+
doTransition(state);
|
|
27
39
|
},
|
|
28
40
|
_registerToolCall() {
|
|
29
41
|
shield.registerToolCall();
|
|
@@ -35,8 +47,25 @@ export function createFSM(config) {
|
|
|
35
47
|
if (!validStates.has(state)) {
|
|
36
48
|
throw new Error(`Invalid state "${state}". Valid states: ${config.states.join(', ')}`);
|
|
37
49
|
}
|
|
38
|
-
|
|
50
|
+
doTransition(state);
|
|
51
|
+
},
|
|
52
|
+
snapshot() {
|
|
53
|
+
return { state: _currentState };
|
|
54
|
+
},
|
|
55
|
+
toMermaid() {
|
|
56
|
+
const lines = ['stateDiagram-v2'];
|
|
57
|
+
for (const state of config.states) {
|
|
58
|
+
const tools = (config.tools[state] ?? []).join('<br/>');
|
|
59
|
+
const label = tools ? `${state}<br/><i>${tools}</i>` : state;
|
|
60
|
+
lines.push(` ${state}: ${label}`);
|
|
61
|
+
}
|
|
62
|
+
for (const state of config.states) {
|
|
63
|
+
lines.push(` [*] --> ${state}`);
|
|
64
|
+
lines.push(` ${state} --> [*]`);
|
|
65
|
+
}
|
|
66
|
+
return lines.join('\n');
|
|
39
67
|
},
|
|
68
|
+
context: _context,
|
|
40
69
|
};
|
|
41
70
|
}
|
|
42
71
|
//# sourceMappingURL=machine.js.map
|
package/dist/core/machine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"machine.js","sourceRoot":"","sources":["../../src/core/machine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAyB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"machine.js","sourceRoot":"","sources":["../../src/core/machine.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,14 @@ export interface ToolEntry<TContext = any> {
|
|
|
3
3
|
gates: string[];
|
|
4
4
|
condition?: (ctx: TContext) => boolean;
|
|
5
5
|
build: (ctx: TContext) => unknown;
|
|
6
|
+
schema?: unknown;
|
|
6
7
|
}
|
|
8
|
+
export declare function validateWith(schema: unknown, args: unknown): {
|
|
9
|
+
ok: true;
|
|
10
|
+
data: unknown;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
error: string;
|
|
14
|
+
};
|
|
7
15
|
export declare function buildToolsForGate<TContext = any>(gate: string, ctx: TContext, registry: ToolEntry<TContext>[]): Record<string, unknown>;
|
|
8
16
|
//# sourceMappingURL=tool-gating.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-gating.d.ts","sourceRoot":"","sources":["../../src/core/tool-gating.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS,CAAC,QAAQ,GAAG,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-gating.d.ts","sourceRoot":"","sources":["../../src/core/tool-gating.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS,CAAC,QAAQ,GAAG,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,GACZ;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAa5D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,GAAG,GAAG,EAC9C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB"}
|
package/dist/core/tool-gating.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export function validateWith(schema, args) {
|
|
2
|
+
const s = schema;
|
|
3
|
+
if (!s || typeof s.safeParse !== 'function') {
|
|
4
|
+
return { ok: true, data: args };
|
|
5
|
+
}
|
|
6
|
+
const result = s.safeParse(args);
|
|
7
|
+
if (result.success) {
|
|
8
|
+
const data = 'data' in result ? result.data : 'output' in result ? result.output : args;
|
|
9
|
+
return { ok: true, data };
|
|
10
|
+
}
|
|
11
|
+
const issues = result.error?.issues ?? result.issues ?? [];
|
|
12
|
+
const error = issues.map((i) => i.message).join(', ') || 'Validation failed';
|
|
13
|
+
return { ok: false, error };
|
|
14
|
+
}
|
|
1
15
|
export function buildToolsForGate(gate, ctx, registry) {
|
|
2
16
|
return Object.fromEntries(registry
|
|
3
17
|
.filter((entry) => entry.gates.includes(gate))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-gating.js","sourceRoot":"","sources":["../../src/core/tool-gating.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-gating.js","sourceRoot":"","sources":["../../src/core/tool-gating.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,YAAY,CAC1B,MAAe,EACf,IAAa;IAEb,MAAM,CAAC,GAAG,MAAsC,CAAC;IACjD,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC5C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClC,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAE,MAAkC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QACrH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAsB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC;IAClG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,GAAa,EACb,QAA+B;IAE/B,OAAO,MAAM,CAAC,WAAW,CACvB,QAAQ;SACL,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;SAC7C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;SACxE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAClD,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createFSM as _createFSM } from './core/machine';
|
|
2
|
-
import type { FSMConfig, FSMPublic } from './core/machine';
|
|
2
|
+
import type { FSMConfig, FSMPublic, FSMSnapshot } from './core/machine';
|
|
3
3
|
export { _createFSM as createFSM };
|
|
4
|
-
export type { FSMConfig, FSMPublic };
|
|
5
|
-
export { buildToolsForGate } from './core/tool-gating';
|
|
4
|
+
export type { FSMConfig, FSMPublic, FSMSnapshot };
|
|
5
|
+
export { buildToolsForGate, validateWith } from './core/tool-gating';
|
|
6
6
|
export type { ToolEntry } from './core/tool-gating';
|
|
7
7
|
export { createLoopShield } from './core/loop-shield';
|
|
8
8
|
export type { LoopShieldConfig, LoopShieldInstance } from './core/loop-shield';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAExE,OAAO,EAAE,UAAU,IAAI,SAAS,EAAE,CAAC;AACnC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createFSM as _createFSM } from './core/machine';
|
|
2
2
|
export { _createFSM as createFSM };
|
|
3
|
-
export { buildToolsForGate } from './core/tool-gating';
|
|
3
|
+
export { buildToolsForGate, validateWith } from './core/tool-gating';
|
|
4
4
|
export { createLoopShield } from './core/loop-shield';
|
|
5
5
|
export { createVercelAdapter } from './adapters/vercel-ai';
|
|
6
6
|
export { createOpenAIAdapter } from './adapters/openai';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,UAAU,IAAI,SAAS,EAAE,CAAC;AAGnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,UAAU,IAAI,SAAS,EAAE,CAAC;AAGnC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAQ3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAMxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactive-fsm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Reactive Finite State Machine for AI agent orchestration — zero dependencies core, conditional tool gating, loop shield, adapters for Vercel AI SDK, OpenAI, Anthropic, LangChain, and Google Gemini",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|