reactive-fsm 1.0.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 +133 -220
- package/dist/adapters/anthropic.d.ts +13 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +33 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/gemini.d.ts +15 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +33 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/langchain.d.ts +10 -0
- package/dist/adapters/langchain.d.ts.map +1 -0
- package/dist/adapters/langchain.js +20 -0
- package/dist/adapters/langchain.js.map +1 -0
- package/dist/adapters/openai.d.ts +2 -2
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +7 -5
- package/dist/adapters/openai.js.map +1 -1
- package/dist/adapters/vercel-ai.d.ts +2 -2
- package/dist/adapters/vercel-ai.d.ts.map +1 -1
- package/dist/adapters/vercel-ai.js +9 -7
- package/dist/adapters/vercel-ai.js.map +1 -1
- package/dist/core/machine.d.ts +14 -1
- 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 +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/package.json +30 -3
package/README.md
CHANGED
|
@@ -1,26 +1,44 @@
|
|
|
1
1
|
# reactive-fsm
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 --> [*]
|
|
8
25
|
```
|
|
9
26
|
|
|
10
|
-
|
|
27
|
+
> Generated by `fsm.toMermaid()`. Paste into any README, PR, or Notion.
|
|
11
28
|
|
|
12
|
-
|
|
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) |
|
|
29
|
+
## What it does
|
|
18
30
|
|
|
19
|
-
|
|
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.
|
|
20
36
|
|
|
21
|
-
##
|
|
37
|
+
## Get started
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add reactive-fsm
|
|
41
|
+
```
|
|
24
42
|
|
|
25
43
|
```typescript
|
|
26
44
|
import { createFSM } from 'reactive-fsm'
|
|
@@ -38,287 +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
|
-
|
|
118
|
-
const openai = new OpenAI()
|
|
119
|
-
const adapter = createOpenAIAdapter(fsm, registry, appCtx)
|
|
120
82
|
|
|
121
|
-
const
|
|
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) {
|
|
91
|
+
if (response.choices[0].message.tool_calls?.length) {
|
|
139
92
|
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
93
|
continue
|
|
150
94
|
}
|
|
151
|
-
|
|
152
|
-
console.log(msg.content) // final response
|
|
153
|
-
break
|
|
95
|
+
return response.choices[0].message.content
|
|
154
96
|
}
|
|
155
97
|
```
|
|
156
98
|
|
|
157
|
-
|
|
99
|
+
## Real-world use cases
|
|
158
100
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
### Booking Funnel (3 gates)
|
|
101
|
+
### Booking funnel
|
|
162
102
|
|
|
163
103
|
```typescript
|
|
164
|
-
const
|
|
104
|
+
const booking = createFSM({
|
|
165
105
|
initialState: 'GREETING',
|
|
166
106
|
states: ['GREETING', 'IDENTITY', 'SCHEDULING', 'CONFIRMED'],
|
|
167
107
|
tools: {
|
|
168
|
-
GREETING:
|
|
169
|
-
IDENTITY:
|
|
170
|
-
SCHEDULING:
|
|
171
|
-
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.',
|
|
172
115
|
},
|
|
173
116
|
loopShield: { enabled: true, maxConsecutiveTools: 3 },
|
|
174
117
|
})
|
|
175
|
-
|
|
176
|
-
// Flow: GREETING → user shows interest → IDENTITY → collects data → SCHEDULING → booked → CONFIRMED
|
|
177
118
|
```
|
|
178
119
|
|
|
179
|
-
|
|
120
|
+
In `SCHEDULING`, `collect_name` is gone. The LLM cannot loop back asking for data it already has.
|
|
121
|
+
|
|
122
|
+
### Payment gate
|
|
180
123
|
|
|
181
124
|
```typescript
|
|
182
|
-
const
|
|
183
|
-
initialState: '
|
|
184
|
-
states: ['
|
|
125
|
+
const checkout = createFSM({
|
|
126
|
+
initialState: 'BROWSING',
|
|
127
|
+
states: ['BROWSING', 'CART', 'PAYMENT', 'CONFIRMED'],
|
|
185
128
|
tools: {
|
|
186
|
-
|
|
187
|
-
|
|
129
|
+
BROWSING: ['search', 'add_to_cart'],
|
|
130
|
+
CART: ['view_cart', 'apply_coupon', 'checkout'],
|
|
131
|
+
PAYMENT: ['process_payment', 'cancel_order'],
|
|
188
132
|
CONFIRMED: ['send_receipt'],
|
|
189
133
|
},
|
|
134
|
+
guard: (from, to) => to === 'PAYMENT' && from !== 'CART' ? false : true,
|
|
190
135
|
})
|
|
191
|
-
|
|
192
|
-
// When entering PAYMENT, the adapter removes all shopping tools.
|
|
193
|
-
// The LLM can ONLY call process_payment or cancel_order.
|
|
194
136
|
```
|
|
195
137
|
|
|
196
|
-
|
|
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
|
|
197
141
|
|
|
198
142
|
```typescript
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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'],
|
|
206
151
|
},
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// Regular user → only view_profile
|
|
210
|
-
// Admin → view_profile + delete_user
|
|
152
|
+
loopShield: { enabled: true, maxConsecutiveTools: 3 },
|
|
153
|
+
})
|
|
211
154
|
```
|
|
212
155
|
|
|
213
|
-
|
|
156
|
+
Loop shield triggers after 3 failed fix attempts. `ESCALATION` has exactly one tool. The bot cannot stay in the loop — it must escalate.
|
|
214
157
|
|
|
215
|
-
##
|
|
158
|
+
## Adapters
|
|
216
159
|
|
|
217
|
-
|
|
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.
|
|
169
|
+
|
|
170
|
+
## vs LangGraph
|
|
171
|
+
|
|
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
|
|
218
185
|
|
|
219
186
|
```typescript
|
|
220
187
|
interface FSMConfig {
|
|
221
188
|
initialState: string
|
|
222
189
|
states: string[]
|
|
223
|
-
tools: Record<string, string[]>
|
|
224
|
-
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
|
|
225
196
|
}
|
|
226
197
|
|
|
227
|
-
interface
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
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
|
|
232
205
|
}
|
|
233
206
|
```
|
|
234
207
|
|
|
235
|
-
### `buildToolsForGate(gate, ctx, registry)`
|
|
236
|
-
|
|
237
208
|
```typescript
|
|
238
209
|
interface ToolEntry<TContext = any> {
|
|
239
210
|
name: string
|
|
240
211
|
gates: string[]
|
|
241
|
-
condition?: (ctx: TContext) => boolean
|
|
242
|
-
build: (ctx: TContext) => unknown
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
function buildToolsForGate<TContext>(
|
|
246
|
-
gate: string,
|
|
247
|
-
ctx: TContext,
|
|
248
|
-
registry: ToolEntry<TContext>[]
|
|
249
|
-
): Record<string, unknown>
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### `createVercelAdapter(fsm, registry, context)`
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
const adapter = createVercelAdapter(fsm, registry, context)
|
|
256
|
-
|
|
257
|
-
const { tools, prepareStep } = adapter.inject()
|
|
258
|
-
adapter.refreshGate('PAYMENT') // external gate change (e.g., after DB mutation)
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
`prepareStep` automatically handles loop shield detection by counting consecutive tool steps from the history. When `maxConsecutiveTools` is exceeded, it returns `{ toolChoice: 'none' }`.
|
|
262
|
-
|
|
263
|
-
### `createOpenAIAdapter(fsm, registry, context)`
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
const adapter = createOpenAIAdapter(fsm, registry, context)
|
|
267
|
-
|
|
268
|
-
const { tools } = adapter.inject() // → { tools: OpenAI-formatted array }
|
|
269
|
-
adapter.isLooping() // → boolean (check before each API call)
|
|
270
|
-
adapter.registerToolCall() // → track tool call for loop shield
|
|
271
|
-
adapter.resetLoopShield() // → reset counter
|
|
272
|
-
adapter.refreshGate('PAYMENT') // → external gate change
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
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.
|
|
276
|
-
|
|
277
|
-
### `createLoopShield(config)`
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
interface LoopShieldConfig {
|
|
281
|
-
enabled: boolean
|
|
282
|
-
maxConsecutiveTools: number
|
|
212
|
+
condition?: (ctx: TContext) => boolean
|
|
213
|
+
build: (ctx: TContext) => unknown
|
|
214
|
+
schema?: unknown // Zod/Valibot/ArkType schema
|
|
283
215
|
}
|
|
284
216
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
shield.isLooping() // boolean
|
|
288
|
-
shield.reset()
|
|
217
|
+
buildToolsForGate(gate, ctx, registry): Record<string, unknown>
|
|
218
|
+
validateWith(schema, args): { ok, data } | { ok, error }
|
|
289
219
|
```
|
|
290
220
|
|
|
291
|
-
---
|
|
292
|
-
|
|
293
221
|
## Architecture
|
|
294
222
|
|
|
295
223
|
```
|
|
296
|
-
src/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
224
|
+
src/core/ — zero npm dependencies
|
|
225
|
+
machine.ts createFSM()
|
|
226
|
+
tool-gating.ts buildToolsForGate(), validateWith()
|
|
227
|
+
loop-shield.ts createLoopShield()
|
|
228
|
+
|
|
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()
|
|
305
235
|
```
|
|
306
236
|
|
|
307
|
-
|
|
308
|
-
- **Adapters are optional.** `ai` and `openai` are optional peerDependencies.
|
|
309
|
-
- **`ToolEntry<TContext>`** is fully generic — you define your own context shape.
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
## Loop Shield
|
|
314
|
-
|
|
315
|
-
The loop shield prevents LLMs from entering infinite tool-calling loops.
|
|
316
|
-
|
|
317
|
-
- **Vercel adapter**: `prepareStep` counts consecutive tool steps from history and forces `toolChoice: 'none'` when `maxConsecutiveTools` is exceeded.
|
|
318
|
-
- **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.
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## License
|
|
323
|
-
|
|
324
|
-
MIT
|
|
237
|
+
MIT · [GitHub](https://github.com/roddcode/reactive-fsm) · [Issues](https://github.com/roddcode/reactive-fsm/issues)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FSMPublic } from '../core/machine';
|
|
2
|
+
import { type ToolEntry } from '../core/tool-gating';
|
|
3
|
+
export interface AnthropicAdapter<TContext = any> {
|
|
4
|
+
inject(): {
|
|
5
|
+
tools: unknown[];
|
|
6
|
+
};
|
|
7
|
+
isLooping(): boolean;
|
|
8
|
+
registerToolCall(): void;
|
|
9
|
+
resetLoopShield(): void;
|
|
10
|
+
refreshGate(state: string): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function createAnthropicAdapter<TContext = any>(fsm: FSMPublic, registry: ToolEntry<TContext>[], context: TContext): AnthropicAdapter<TContext>;
|
|
13
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,gBAAgB,CAAC,QAAQ,GAAG,GAAG;IAC9C,MAAM,IAAI;QAAE,KAAK,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAC/B,SAAS,IAAI,OAAO,CAAC;IACrB,gBAAgB,IAAI,IAAI,CAAC;IACzB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAQD,wBAAgB,sBAAsB,CAAC,QAAQ,GAAG,GAAG,EACnD,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,gBAAgB,CAAC,QAAQ,CAAC,CAkC5B"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { buildToolsForGate } from '../core/tool-gating';
|
|
2
|
+
export function createAnthropicAdapter(fsm, registry, context) {
|
|
3
|
+
const _fsm = fsm;
|
|
4
|
+
return {
|
|
5
|
+
inject() {
|
|
6
|
+
const toolMap = buildToolsForGate(_fsm.currentState, context, registry);
|
|
7
|
+
const toolsArray = [];
|
|
8
|
+
for (const t of Object.values(toolMap)) {
|
|
9
|
+
const tool = t;
|
|
10
|
+
toolsArray.push({
|
|
11
|
+
name: tool.name ?? '',
|
|
12
|
+
description: tool.description ?? '',
|
|
13
|
+
input_schema: tool.parameters ?? { type: 'object', properties: {} },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return { tools: toolsArray };
|
|
17
|
+
},
|
|
18
|
+
isLooping() {
|
|
19
|
+
return _fsm.isLooping;
|
|
20
|
+
},
|
|
21
|
+
registerToolCall() {
|
|
22
|
+
_fsm._registerToolCall();
|
|
23
|
+
},
|
|
24
|
+
resetLoopShield() {
|
|
25
|
+
_fsm._resetLoopShield();
|
|
26
|
+
},
|
|
27
|
+
refreshGate(state) {
|
|
28
|
+
_fsm._setState(state);
|
|
29
|
+
_fsm._resetLoopShield();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAgBxE,MAAM,UAAU,sBAAsB,CACpC,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,MAAM,UAAU,GAAoB,EAAE,CAAC;YACvC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvC,MAAM,IAAI,GAAG,CAA4B,CAAC;gBAC1C,UAAU,CAAC,IAAI,CAAC;oBACd,IAAI,EAAG,IAAI,CAAC,IAAe,IAAI,EAAE;oBACjC,WAAW,EAAG,IAAI,CAAC,WAAsB,IAAI,EAAE;oBAC/C,YAAY,EAAG,IAAI,CAAC,UAAsC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;iBACjG,CAAC,CAAC;YACL,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QAC/B,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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FSMPublic } from '../core/machine';
|
|
2
|
+
import { type ToolEntry } from '../core/tool-gating';
|
|
3
|
+
export interface GeminiAdapter<TContext = any> {
|
|
4
|
+
inject(): {
|
|
5
|
+
tools: Array<{
|
|
6
|
+
functionDeclarations: unknown[];
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
isLooping(): boolean;
|
|
10
|
+
registerToolCall(): void;
|
|
11
|
+
resetLoopShield(): void;
|
|
12
|
+
refreshGate(state: string): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function createGeminiAdapter<TContext = any>(fsm: FSMPublic, registry: ToolEntry<TContext>[], context: TContext): GeminiAdapter<TContext>;
|
|
15
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,GAAG;IAC3C,MAAM,IAAI;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,oBAAoB,EAAE,OAAO,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAChE,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,CAkCzB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { buildToolsForGate } from '../core/tool-gating';
|
|
2
|
+
export function createGeminiAdapter(fsm, registry, context) {
|
|
3
|
+
const _fsm = fsm;
|
|
4
|
+
return {
|
|
5
|
+
inject() {
|
|
6
|
+
const toolMap = buildToolsForGate(_fsm.currentState, context, registry);
|
|
7
|
+
const declarations = [];
|
|
8
|
+
for (const t of Object.values(toolMap)) {
|
|
9
|
+
const tool = t;
|
|
10
|
+
declarations.push({
|
|
11
|
+
name: tool.name ?? '',
|
|
12
|
+
description: tool.description ?? '',
|
|
13
|
+
parameters: tool.parameters ?? { type: 'object', properties: {} },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return { tools: [{ functionDeclarations: declarations }] };
|
|
17
|
+
},
|
|
18
|
+
isLooping() {
|
|
19
|
+
return _fsm.isLooping;
|
|
20
|
+
},
|
|
21
|
+
registerToolCall() {
|
|
22
|
+
_fsm._registerToolCall();
|
|
23
|
+
},
|
|
24
|
+
resetLoopShield() {
|
|
25
|
+
_fsm._resetLoopShield();
|
|
26
|
+
},
|
|
27
|
+
refreshGate(state) {
|
|
28
|
+
_fsm._setState(state);
|
|
29
|
+
_fsm._resetLoopShield();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAUxE,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,MAAM,YAAY,GAAc,EAAE,CAAC;YACnC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvC,MAAM,IAAI,GAAG,CAA4B,CAAC;gBAC1C,YAAY,CAAC,IAAI,CAAC;oBAChB,IAAI,EAAG,IAAI,CAAC,IAAe,IAAI,EAAE;oBACjC,WAAW,EAAG,IAAI,CAAC,WAAsB,IAAI,EAAE;oBAC/C,UAAU,EAAG,IAAI,CAAC,UAAsC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;iBAC/F,CAAC,CAAC;YACL,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;QAC7D,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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FSMPublic } from '../core/machine';
|
|
2
|
+
import { type ToolEntry } from '../core/tool-gating';
|
|
3
|
+
export interface LangChainFSMWrapper<TContext = any> {
|
|
4
|
+
getTools(): Record<string, unknown>;
|
|
5
|
+
shouldStop(): boolean;
|
|
6
|
+
onToolCall(): void;
|
|
7
|
+
onStateChange(gate: string): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function wrapWithFSM<TContext = any>(fsm: FSMPublic, registry: ToolEntry<TContext>[], context: TContext): LangChainFSMWrapper<TContext>;
|
|
10
|
+
//# sourceMappingURL=langchain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langchain.d.ts","sourceRoot":"","sources":["../../src/adapters/langchain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,mBAAmB,CAAC,QAAQ,GAAG,GAAG;IACjD,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,IAAI,OAAO,CAAC;IACtB,UAAU,IAAI,IAAI,CAAC;IACnB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,WAAW,CAAC,QAAQ,GAAG,GAAG,EACxC,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,mBAAmB,CAAC,QAAQ,CAAC,CAoB/B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { buildToolsForGate } from '../core/tool-gating';
|
|
2
|
+
export function wrapWithFSM(fsm, registry, context) {
|
|
3
|
+
const _fsm = fsm;
|
|
4
|
+
return {
|
|
5
|
+
getTools() {
|
|
6
|
+
return buildToolsForGate(_fsm.currentState, context, registry);
|
|
7
|
+
},
|
|
8
|
+
shouldStop() {
|
|
9
|
+
return _fsm.isLooping;
|
|
10
|
+
},
|
|
11
|
+
onToolCall() {
|
|
12
|
+
_fsm._registerToolCall();
|
|
13
|
+
},
|
|
14
|
+
onStateChange(gate) {
|
|
15
|
+
_fsm._setState(gate);
|
|
16
|
+
_fsm._resetLoopShield();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=langchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../src/adapters/langchain.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AASxE,MAAM,UAAU,WAAW,CACzB,GAAc,EACd,QAA+B,EAC/B,OAAiB;IAEjB,MAAM,IAAI,GAAG,GAAkB,CAAC;IAChC,OAAO;QACL,QAAQ;YACN,OAAO,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjE,CAAC;QAED,UAAU;YACR,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QAED,UAAU;YACR,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;QAED,aAAa,CAAC,IAAY;YACxB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACrB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FSMPublic } from '../core/machine';
|
|
2
2
|
import { type ToolEntry } from '../core/tool-gating';
|
|
3
3
|
export interface OpenAIInjectResult {
|
|
4
4
|
tools: unknown[];
|
|
@@ -10,5 +10,5 @@ export interface OpenAIAdapter<TContext = any> {
|
|
|
10
10
|
resetLoopShield(): void;
|
|
11
11
|
refreshGate(state: string): void;
|
|
12
12
|
}
|
|
13
|
-
export declare function createOpenAIAdapter<TContext = any>(fsm:
|
|
13
|
+
export declare function createOpenAIAdapter<TContext = any>(fsm: FSMPublic, registry: ToolEntry<TContext>[], context: TContext): OpenAIAdapter<TContext>;
|
|
14
14
|
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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,21 +1,23 @@
|
|
|
1
1
|
import { buildToolsForGate } from '../core/tool-gating';
|
|
2
2
|
export function createOpenAIAdapter(fsm, registry, context) {
|
|
3
|
+
const _fsm = fsm;
|
|
3
4
|
return {
|
|
4
5
|
inject() {
|
|
5
|
-
const toolMap = buildToolsForGate(
|
|
6
|
+
const toolMap = buildToolsForGate(_fsm.currentState, context, registry);
|
|
6
7
|
return { tools: Object.values(toolMap) };
|
|
7
8
|
},
|
|
8
9
|
isLooping() {
|
|
9
|
-
return
|
|
10
|
+
return _fsm.isLooping;
|
|
10
11
|
},
|
|
11
12
|
registerToolCall() {
|
|
12
|
-
|
|
13
|
+
_fsm._registerToolCall();
|
|
13
14
|
},
|
|
14
15
|
resetLoopShield() {
|
|
15
|
-
|
|
16
|
+
_fsm._resetLoopShield();
|
|
16
17
|
},
|
|
17
18
|
refreshGate(state) {
|
|
18
|
-
|
|
19
|
+
_fsm._setState(state);
|
|
20
|
+
_fsm._resetLoopShield();
|
|
19
21
|
},
|
|
20
22
|
};
|
|
21
23
|
}
|
|
@@ -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,
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FSMPublic } from '../core/machine';
|
|
2
2
|
import { type ToolEntry } from '../core/tool-gating';
|
|
3
3
|
export interface VercelStepInfo {
|
|
4
4
|
toolCalls?: Array<{
|
|
@@ -24,5 +24,5 @@ export interface VercelAdapter<TContext = any> {
|
|
|
24
24
|
inject(): VercelInjectResult;
|
|
25
25
|
refreshGate(state: string): void;
|
|
26
26
|
}
|
|
27
|
-
export declare function createVercelAdapter<TContext = any>(fsm:
|
|
27
|
+
export declare function createVercelAdapter<TContext = any>(fsm: FSMPublic, registry: ToolEntry<TContext>[], context: TContext): VercelAdapter<TContext>;
|
|
28
28
|
//# sourceMappingURL=vercel-ai.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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"}
|
|
@@ -9,11 +9,12 @@ function syncTools(target, gate, ctx, registry) {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
export function createVercelAdapter(fsm, registry, context) {
|
|
12
|
+
const _fsm = fsm;
|
|
12
13
|
const tools = {};
|
|
13
14
|
let currentGate = null;
|
|
14
15
|
return {
|
|
15
16
|
inject() {
|
|
16
|
-
currentGate =
|
|
17
|
+
currentGate = _fsm.currentState;
|
|
17
18
|
syncTools(tools, currentGate, context, registry);
|
|
18
19
|
return {
|
|
19
20
|
tools,
|
|
@@ -27,15 +28,15 @@ export function createVercelAdapter(fsm, registry, context) {
|
|
|
27
28
|
break;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
_fsm._resetLoopShield();
|
|
31
32
|
for (let i = 0; i < consecutiveToolSteps; i++) {
|
|
32
|
-
|
|
33
|
+
_fsm._registerToolCall();
|
|
33
34
|
}
|
|
34
|
-
if (
|
|
35
|
+
if (_fsm.isLooping) {
|
|
35
36
|
return { toolChoice: 'none' };
|
|
36
37
|
}
|
|
37
|
-
if (
|
|
38
|
-
currentGate =
|
|
38
|
+
if (_fsm.currentState !== currentGate) {
|
|
39
|
+
currentGate = _fsm.currentState;
|
|
39
40
|
syncTools(tools, currentGate, context, registry);
|
|
40
41
|
}
|
|
41
42
|
return {};
|
|
@@ -43,7 +44,8 @@ export function createVercelAdapter(fsm, registry, context) {
|
|
|
43
44
|
};
|
|
44
45
|
},
|
|
45
46
|
refreshGate(state) {
|
|
46
|
-
|
|
47
|
+
_fsm._setState(state);
|
|
48
|
+
_fsm._resetLoopShield();
|
|
47
49
|
},
|
|
48
50
|
};
|
|
49
51
|
}
|
|
@@ -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,
|
|
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,28 @@
|
|
|
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
|
-
export interface
|
|
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>;
|
|
24
|
+
}
|
|
25
|
+
export interface FSMInstance extends FSMPublic {
|
|
13
26
|
_registerToolCall(): void;
|
|
14
27
|
_resetLoopShield(): void;
|
|
15
28
|
_setState(state: string): 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,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {
|
|
1
|
+
import { createFSM as _createFSM } from './core/machine';
|
|
2
|
+
import type { FSMConfig, FSMPublic, FSMSnapshot } from './core/machine';
|
|
3
|
+
export { _createFSM as createFSM };
|
|
4
|
+
export type { FSMConfig, FSMPublic, FSMSnapshot };
|
|
5
|
+
export { buildToolsForGate, validateWith } from './core/tool-gating';
|
|
4
6
|
export type { ToolEntry } from './core/tool-gating';
|
|
5
7
|
export { createLoopShield } from './core/loop-shield';
|
|
6
8
|
export type { LoopShieldConfig, LoopShieldInstance } from './core/loop-shield';
|
|
@@ -8,4 +10,10 @@ export { createVercelAdapter } from './adapters/vercel-ai';
|
|
|
8
10
|
export type { VercelAdapter, VercelInjectResult, VercelStepInfo, VercelPrepareStepParams, } from './adapters/vercel-ai';
|
|
9
11
|
export { createOpenAIAdapter } from './adapters/openai';
|
|
10
12
|
export type { OpenAIAdapter, OpenAIInjectResult, } from './adapters/openai';
|
|
13
|
+
export { createAnthropicAdapter } from './adapters/anthropic';
|
|
14
|
+
export type { AnthropicAdapter } from './adapters/anthropic';
|
|
15
|
+
export { wrapWithFSM } from './adapters/langchain';
|
|
16
|
+
export type { LangChainFSMWrapper } from './adapters/langchain';
|
|
17
|
+
export { createGeminiAdapter } from './adapters/gemini';
|
|
18
|
+
export type { GeminiAdapter } from './adapters/gemini';
|
|
11
19
|
//# sourceMappingURL=index.d.ts.map
|
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,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,10 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
import { createFSM as _createFSM } from './core/machine';
|
|
2
|
+
export { _createFSM as createFSM };
|
|
3
|
+
export { buildToolsForGate, validateWith } from './core/tool-gating';
|
|
3
4
|
export { createLoopShield } from './core/loop-shield';
|
|
4
5
|
export { createVercelAdapter } from './adapters/vercel-ai';
|
|
5
6
|
export { createOpenAIAdapter } from './adapters/openai';
|
|
7
|
+
export { createAnthropicAdapter } from './adapters/anthropic';
|
|
8
|
+
export { wrapWithFSM } from './adapters/langchain';
|
|
9
|
+
export { createGeminiAdapter } from './adapters/gemini';
|
|
6
10
|
//# sourceMappingURL=index.js.map
|
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,EAAE,MAAM,gBAAgB,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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactive-fsm",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Reactive Finite State Machine for AI agent orchestration — zero dependencies core, conditional tool gating, loop shield, adapters for Vercel AI SDK and
|
|
3
|
+
"version": "1.2.0",
|
|
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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
"./adapters/openai": {
|
|
18
18
|
"types": "./dist/adapters/openai.d.ts",
|
|
19
19
|
"import": "./dist/adapters/openai.js"
|
|
20
|
+
},
|
|
21
|
+
"./adapters/anthropic": {
|
|
22
|
+
"types": "./dist/adapters/anthropic.d.ts",
|
|
23
|
+
"import": "./dist/adapters/anthropic.js"
|
|
24
|
+
},
|
|
25
|
+
"./adapters/langchain": {
|
|
26
|
+
"types": "./dist/adapters/langchain.d.ts",
|
|
27
|
+
"import": "./dist/adapters/langchain.js"
|
|
28
|
+
},
|
|
29
|
+
"./adapters/gemini": {
|
|
30
|
+
"types": "./dist/adapters/gemini.d.ts",
|
|
31
|
+
"import": "./dist/adapters/gemini.js"
|
|
20
32
|
}
|
|
21
33
|
},
|
|
22
34
|
"files": [
|
|
@@ -30,6 +42,9 @@
|
|
|
30
42
|
"ai-agents",
|
|
31
43
|
"vercel-ai-sdk",
|
|
32
44
|
"openai",
|
|
45
|
+
"anthropic",
|
|
46
|
+
"langchain",
|
|
47
|
+
"gemini",
|
|
33
48
|
"tool-gating"
|
|
34
49
|
],
|
|
35
50
|
"author": "Alejandro Alvarado <roddcode.dev@gmail.com>",
|
|
@@ -52,7 +67,10 @@
|
|
|
52
67
|
},
|
|
53
68
|
"peerDependencies": {
|
|
54
69
|
"ai": "^4.0.0",
|
|
55
|
-
"openai": "^4.0.0"
|
|
70
|
+
"openai": "^4.0.0",
|
|
71
|
+
"@anthropic-ai/sdk": "^0.30.0",
|
|
72
|
+
"langchain": "^0.3.0",
|
|
73
|
+
"@google/generative-ai": "^0.21.0"
|
|
56
74
|
},
|
|
57
75
|
"peerDependenciesMeta": {
|
|
58
76
|
"ai": {
|
|
@@ -60,6 +78,15 @@
|
|
|
60
78
|
},
|
|
61
79
|
"openai": {
|
|
62
80
|
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"@anthropic-ai/sdk": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"langchain": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@google/generative-ai": {
|
|
89
|
+
"optional": true
|
|
63
90
|
}
|
|
64
91
|
},
|
|
65
92
|
"scripts": {
|