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 CHANGED
@@ -1,26 +1,44 @@
1
1
  # reactive-fsm
2
2
 
3
- Reactive Finite State Machine for AI agent orchestration. Zero-dependency core with conditional tool gating, loop shield, and adapters for Vercel AI SDK and OpenAI SDK.
4
-
5
- ```bash
6
- pnpm add reactive-fsm
7
- # o npm install reactive-fsm
3
+ [![npm](https://img.shields.io/npm/v/reactive-fsm)](https://www.npmjs.com/package/reactive-fsm)
4
+ [![license](https://img.shields.io/npm/l/reactive-fsm)](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
- ## Concepts
27
+ > Generated by `fsm.toMermaid()`. Paste into any README, PR, or Notion.
11
28
 
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) |
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
- ## Quick Start
37
+ ## Get started
22
38
 
23
- ### 1. Define your FSM
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
- ### 2. Build a tool registry
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, appCtx)
69
+ const adapter = createVercelAdapter(fsm, registry, context)
104
70
 
105
71
  const response = await generateText({
106
72
  model: openai('gpt-4o'),
107
- messages: [{ role: 'user', content: 'Quiero agendar una cita' }],
108
- ...adapter.inject(), // { tools, prepareStep }
73
+ messages,
74
+ ...adapter.inject(), // { tools, prepareStep }
109
75
  })
110
76
  ```
111
77
 
112
- #### OpenAI SDK
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 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) {
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
- ## Real-World Patterns
160
-
161
- ### Booking Funnel (3 gates)
101
+ ### Booking funnel
162
102
 
163
103
  ```typescript
164
- const bookingFSM = createFSM({
104
+ const booking = createFSM({
165
105
  initialState: 'GREETING',
166
106
  states: ['GREETING', 'IDENTITY', 'SCHEDULING', 'CONFIRMED'],
167
107
  tools: {
168
- GREETING: ['greet', 'show_catalog'],
169
- IDENTITY: ['collect_name', 'collect_dni', 'show_catalog'],
170
- SCHEDULING: ['check_slots', 'book_appointment'],
171
- CONFIRMED: ['send_confirmation'],
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
- ### Payment Gate (block everything except payment)
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 paymentFSM = createFSM({
183
- initialState: 'SHOPPING',
184
- states: ['SHOPPING', 'PAYMENT', 'CONFIRMED'],
125
+ const checkout = createFSM({
126
+ initialState: 'BROWSING',
127
+ states: ['BROWSING', 'CART', 'PAYMENT', 'CONFIRMED'],
185
128
  tools: {
186
- SHOPPING: ['browse', 'add_to_cart', 'checkout'],
187
- PAYMENT: ['process_payment', 'cancel_order'], // only these two
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
- ### Conditional Tool: Admin vs User
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 registry: ToolEntry<{ role: 'user' | 'admin' }>[] = [
200
- { name: 'view_profile', gates: ['DASHBOARD'], build: () => ({ /* ... */ }) },
201
- {
202
- name: 'delete_user',
203
- gates: ['DASHBOARD'],
204
- condition: (ctx) => ctx.role === 'admin',
205
- build: () => ({ /* ... */ }),
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
- ## API Reference
158
+ ## Adapters
216
159
 
217
- ### `createFSM(config)`
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[]> // state → tool names
224
- loopShield?: LoopShieldConfig
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 FSMInstance {
228
- readonly currentState: string
229
- readonly allowedTools: string[]
230
- readonly isLooping: boolean
231
- transitionTo(state: string): void
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 // optional runtime filter
242
- build: (ctx: TContext) => unknown // returns the tool definition
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
- const shield = createLoopShield({ enabled: true, maxConsecutiveTools: 3 })
286
- shield.registerToolCall()
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
- ├── core/
298
- │ ├── machine.ts # createFSM() — state management
299
- │ ├── tool-gating.ts # buildToolsForGate() — conditional tool injection
300
- │ └── loop-shield.ts # createLoopShield() — cognitive loop detection
301
- ├── adapters/
302
- │ ├── vercel-ai.ts # createVercelAdapter() — Vercel AI SDK
303
- │ └── openai.ts # createOpenAIAdapter() — OpenAI SDK
304
- └── index.ts
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
- - **Core has zero npm dependencies.** Only TypeScript types.
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 { FSMInstance } from '../core/machine';
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: FSMInstance, registry: ToolEntry<TContext>[], context: TContext): OpenAIAdapter<TContext>;
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,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,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,WAAW,EAChB,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,aAAa,CAAC,QAAQ,CAAC,CAuBzB"}
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"}
@@ -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(fsm.currentState, context, registry);
6
+ const toolMap = buildToolsForGate(_fsm.currentState, context, registry);
6
7
  return { tools: Object.values(toolMap) };
7
8
  },
8
9
  isLooping() {
9
- return fsm.isLooping;
10
+ return _fsm.isLooping;
10
11
  },
11
12
  registerToolCall() {
12
- fsm._registerToolCall();
13
+ _fsm._registerToolCall();
13
14
  },
14
15
  resetLoopShield() {
15
- fsm._resetLoopShield();
16
+ _fsm._resetLoopShield();
16
17
  },
17
18
  refreshGate(state) {
18
- fsm._setState(state);
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,GAAgB,EAChB,QAA+B,EAC/B,OAAiB;IAEjB,OAAO;QACL,MAAM;YACJ,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YACvE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,CAAC;QAED,SAAS;YACP,OAAO,GAAG,CAAC,SAAS,CAAC;QACvB,CAAC;QAED,gBAAgB;YACd,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC1B,CAAC;QAED,eAAe;YACb,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACzB,CAAC;QAED,WAAW,CAAC,KAAa;YACvB,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,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,4 +1,4 @@
1
- import type { FSMInstance } from '../core/machine';
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: FSMInstance, registry: ToolEntry<TContext>[], context: TContext): VercelAdapter<TContext>;
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,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,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,WAAW,EAChB,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,EAC/B,OAAO,EAAE,QAAQ,GAChB,aAAa,CAAC,QAAQ,CAAC,CA4CzB"}
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 = fsm.currentState;
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
- fsm._resetLoopShield();
31
+ _fsm._resetLoopShield();
31
32
  for (let i = 0; i < consecutiveToolSteps; i++) {
32
- fsm._registerToolCall();
33
+ _fsm._registerToolCall();
33
34
  }
34
- if (fsm.isLooping) {
35
+ if (_fsm.isLooping) {
35
36
  return { toolChoice: 'none' };
36
37
  }
37
- if (fsm.currentState !== currentGate) {
38
- currentGate = fsm.currentState;
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
- fsm._setState(state);
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,GAAgB,EAChB,QAA+B,EAC/B,OAAiB;IAEjB,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,IAAI,WAAW,GAAkB,IAAI,CAAC;IAEtC,OAAO;QACL,MAAM;YACJ,WAAW,GAAG,GAAG,CAAC,YAAY,CAAC;YAC/B,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,GAAG,CAAC,gBAAgB,EAAE,CAAC;oBACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,oBAAoB,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC9C,GAAG,CAAC,iBAAiB,EAAE,CAAC;oBAC1B,CAAC;oBAED,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;wBAClB,OAAO,EAAE,UAAU,EAAE,MAAe,EAAE,CAAC;oBACzC,CAAC;oBAED,IAAI,GAAG,CAAC,YAAY,KAAK,WAAW,EAAE,CAAC;wBACrC,WAAW,GAAG,GAAG,CAAC,YAAY,CAAC;wBAC/B,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,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,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"}
@@ -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 FSMInstance {
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;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,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,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,CAoDxD"}
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"}
@@ -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
- let _currentState = config.initialState;
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
- _currentState = state;
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
- _currentState = state;
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
@@ -1 +1 @@
1
- {"version":3,"file":"machine.js","sourceRoot":"","sources":["../../src/core/machine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAyB,MAAM,eAAe,CAAC;AAmBxE,MAAM,UAAU,SAAS,CAAC,MAAiB;IACzC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAE3C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,CAAC,YAAY,6BAA6B,CAAC,CAAC;IACtF,CAAC;IAED,IAAI,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;IAExC,MAAM,MAAM,GAAG,gBAAgB,CAC7B,MAAM,CAAC,UAAU,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,EAAE,CAChE,CAAC;IAEF,SAAS,eAAe;QACtB,OAAO,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,OAAO;QACL,IAAI,YAAY;YACd,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,IAAI,YAAY;YACd,OAAO,eAAe,EAAE,CAAC;QAC3B,CAAC;QAED,IAAI,SAAS;YACX,OAAO,MAAM,CAAC,SAAS,EAAE,CAAC;QAC5B,CAAC;QAED,YAAY,CAAC,KAAa;YACxB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,oBAAoB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzF,CAAC;YACD,aAAa,GAAG,KAAK,CAAC;QACxB,CAAC;QAED,iBAAiB;YACf,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC5B,CAAC;QAED,gBAAgB;YACd,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QAED,SAAS,CAAC,KAAa;YACrB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,oBAAoB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzF,CAAC;YACD,aAAa,GAAG,KAAK,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,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;CACnC;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"}
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"}
@@ -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":"AAOA,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"}
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
- export { createFSM } from './core/machine';
2
- export type { FSMConfig, FSMInstance } from './core/machine';
3
- export { buildToolsForGate } from './core/tool-gating';
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,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"}
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
- export { createFSM } from './core/machine';
2
- export { buildToolsForGate } from './core/tool-gating';
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;AAG3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAQ3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,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.0.0",
4
- "description": "Reactive Finite State Machine for AI agent orchestration — zero dependencies core, conditional tool gating, loop shield, adapters for Vercel AI SDK and OpenAI SDK",
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": {