fastworkflow 2.16.0__py3-none-any.whl → 2.17.0__py3-none-any.whl

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.

Potentially problematic release.


This version of fastworkflow might be problematic. Click here for more details.

@@ -0,0 +1,103 @@
1
+ # pyright: reportUnusedFunction=false
2
+
3
+ from fastapi_mcp import FastApiMCP
4
+
5
+ def setup_mcp(
6
+ app,
7
+ session_manager,
8
+ ):
9
+ """Mount MCP to automatically convert FastAPI endpoints to MCP tools.
10
+
11
+ FastAPI endpoints are automatically exposed as MCP tools, except those in the exclude list.
12
+
13
+ Key exposed tools:
14
+ - invoke_agent: Streaming agent invocation with NDJSON/SSE support (from /invoke_agent_stream)
15
+ - invoke_assistant: Assistant mode (deterministic execution)
16
+ - new_conversation, get_all_conversations, post_feedback, activate_conversation
17
+
18
+ MCP Client Setup:
19
+ - MCP clients use pre-configured long-lived access tokens (generated via /admin/generate_mcp_token)
20
+ - Tokens are added to the MCP client configuration, not obtained via tool calls
21
+ - No need for initialize or refresh_token tools in MCP context
22
+
23
+ Note: Prompt registration (format-command, clarify-params) is commented out
24
+ as fastapi-mcp 0.4.0 does not support custom prompts.
25
+ """
26
+
27
+ # =========================================================================
28
+ # Mount MCP (FastApiMCP will scan and find all FastAPI endpoints)
29
+ # =========================================================================
30
+
31
+ # Exclude endpoints that should not be exposed as MCP tools:
32
+ # - root: HTML homepage endpoint
33
+ # - dump_all_conversations: Admin-only endpoint for dumping all user conversations
34
+ # - generate_mcp_token: Admin-only endpoint for generating long-lived MCP tokens
35
+ # - rest_initialize: Regular initialization (MCP clients use pre-configured tokens, don't need to initialize)
36
+ # - perform_action: Low-level action execution (use invoke_agent/invoke_assistant instead)
37
+ # - rest_invoke_agent: Non-streaming version (use "invoke_agent" streaming endpoint instead)
38
+ # - refresh_token: JWT token refresh (not needed for MCP since MCP uses long-lived tokens)
39
+ #
40
+ # Exposed MCP tools:
41
+ # - invoke_agent (operation_id) → /invoke_agent_stream endpoint (streaming with NDJSON/SSE support)
42
+ # - invoke_assistant: Assistant mode (deterministic execution)
43
+ # - new_conversation, get_all_conversations, post_feedback, activate_conversation
44
+ #
45
+ # Note: MCP clients are configured with long-lived access tokens generated via /admin/generate_mcp_token
46
+ mcp = FastApiMCP(
47
+ app,
48
+ exclude_operations=[
49
+ "root",
50
+ "dump_all_conversations",
51
+ "generate_mcp_token",
52
+ "rest_initialize",
53
+ "perform_action",
54
+ "rest_invoke_agent",
55
+ "refresh_token"
56
+ ]
57
+ )
58
+ mcp.mount_http()
59
+
60
+ # Note: Prompt registration is not supported in fastapi-mcp 0.4.0
61
+ # The library automatically converts FastAPI endpoints to MCP tools,
62
+ # but does not provide a way to register custom prompts.
63
+ # Prompts may be added in a future version or via manual MCP server implementation.
64
+
65
+ # TODO: Re-enable when fastapi-mcp supports prompts or implement custom prompt handler
66
+ # # Prompts
67
+ # mcp.add_prompt(
68
+ # name="format-command",
69
+ # description="Given command metadata and a user intent, format a single executable command with XML-tagged parameters.",
70
+ # arguments=[{"name": "intent", "required": True}, {"name": "metadata", "required": True}],
71
+ # handler=lambda intent, metadata: [
72
+ # {
73
+ # "role": "user",
74
+ # "content": {
75
+ # "type": "text",
76
+ # "text": (
77
+ # f"Intent: {intent}\n\nMetadata:\n{metadata}\n\n"
78
+ # "Format a single command: command_name <param>value</param> ..."
79
+ # ),
80
+ # },
81
+ # }
82
+ # ],
83
+ # )
84
+ #
85
+ # mcp.add_prompt(
86
+ # name="clarify-params",
87
+ # description="Compose a concise clarification question for missing parameters using the provided metadata.",
88
+ # arguments=[{"name": "error_message", "required": True}, {"name": "metadata", "required": True}],
89
+ # handler=lambda error_message, metadata: [
90
+ # {
91
+ # "role": "user",
92
+ # "content": {
93
+ # "type": "text",
94
+ # "text": (
95
+ # f"{error_message}\n\nMetadata:\n{metadata}\n\n"
96
+ # "Ask one short question to request the missing parameters."
97
+ # ),
98
+ # },
99
+ # }
100
+ # ],
101
+ # )
102
+
103
+
@@ -0,0 +1,40 @@
1
+ """
2
+ Script to export the ReDoc documentation page into a standalone HTML file.
3
+ Created by https://github.com/pawamoy on https://github.com/Redocly/redoc/issues/726#issuecomment-645414239
4
+ """
5
+
6
+
7
+ import json
8
+
9
+ from services.run_fastapi.main import app
10
+
11
+ HTML_TEMPLATE = """<!DOCTYPE html>
12
+ <html>
13
+ <head>
14
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
15
+ <title>My Project - ReDoc</title>
16
+ <meta charset="utf-8">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
19
+ <style>
20
+ body {
21
+ margin: 0;
22
+ padding: 0;
23
+ }
24
+ </style>
25
+ <style data-styled="" data-styled-version="4.4.1"></style>
26
+ </head>
27
+ <body>
28
+ <div id="redoc-container"></div>
29
+ <script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
30
+ <script>
31
+ var spec = %s;
32
+ Redoc.init(spec, {}, document.getElementById("redoc-container"));
33
+ </script>
34
+ </body>
35
+ </html>
36
+ """
37
+
38
+ if __name__ == "__main__":
39
+ with open("redoc.html", "w", encoding="utf-8") as fd:
40
+ print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd)
@@ -0,0 +1,427 @@
1
+ import asyncio
2
+ import os
3
+ import queue
4
+ import time
5
+ from dataclasses import dataclass
6
+ from typing import Any, Optional
7
+
8
+ from fastapi import HTTPException, status, Depends
9
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
10
+ from jose import JWTError
11
+ from pydantic import BaseModel, field_validator
12
+
13
+ import fastworkflow
14
+ from fastworkflow.utils.logging import logger
15
+
16
+ from .conversation_store import ConversationStore, restore_history_from_turns
17
+ from .jwt_manager import verify_token
18
+
19
+
20
+ # ============================================================================
21
+ # Data Models (aligned with FastWorkflow canonical types)
22
+ # ============================================================================
23
+
24
+ class InitializationRequest(BaseModel):
25
+ """Request to initialize a FastWorkflow session for a user"""
26
+ user_id: str
27
+ stream_format: Optional[str] = None # "ndjson" | "sse" (default ndjson)
28
+
29
+
30
+ class TokenResponse(BaseModel):
31
+ """JWT token pair returned from initialization or token refresh"""
32
+ access_token: str
33
+ refresh_token: str
34
+ token_type: str = "bearer"
35
+ expires_in: int # Access token expiration in seconds
36
+
37
+
38
+ class SessionData(BaseModel):
39
+ """Validated session data extracted from JWT token"""
40
+ user_id: str
41
+ token_type: str # "access" or "refresh"
42
+ issued_at: int # Unix timestamp
43
+ expires_at: int # Unix timestamp
44
+ jti: str # JWT ID (unique token identifier)
45
+
46
+
47
+ class InvokeRequest(BaseModel):
48
+ """
49
+ Request to invoke agent or assistant.
50
+ Requires user_id to be passed in the Authorization header (via JWT token).
51
+ """
52
+ user_query: str
53
+ timeout_seconds: int = 60
54
+
55
+
56
+ class PerformActionRequest(BaseModel):
57
+ """
58
+ Request to perform a specific action.
59
+ Requires user_id to be passed in the Authorization header (via JWT token).
60
+ """
61
+ action: dict[str, Any] # Will be converted to fastworkflow.Action
62
+ timeout_seconds: int = 60
63
+
64
+
65
+ class PostFeedbackRequest(BaseModel):
66
+ """
67
+ Request to post feedback on the latest turn.
68
+ Requires user_id to be passed in the Authorization header (via JWT token).
69
+
70
+ Note: binary_or_numeric_score accepts numeric values (float).
71
+ Boolean values (True/False) are automatically converted to 1.0/0.0.
72
+ """
73
+ binary_or_numeric_score: Optional[float] = None
74
+ nl_feedback: Optional[str] = None
75
+
76
+ @field_validator('nl_feedback')
77
+ @classmethod
78
+ def validate_feedback_presence(cls, v, info):
79
+ """Ensure at least one feedback field is provided"""
80
+ if v is None and info.data.get('binary_or_numeric_score') is None:
81
+ raise ValueError("At least one of binary_or_numeric_score or nl_feedback must be provided")
82
+ return v
83
+
84
+
85
+ class ActivateConversationRequest(BaseModel):
86
+ """
87
+ Request to activate a conversation by ID.
88
+ Requires user_id to be passed in the Authorization header (via JWT token).
89
+ """
90
+ conversation_id: int
91
+
92
+
93
+ class DumpConversationsRequest(BaseModel):
94
+ """Admin request to dump all conversations"""
95
+ output_folder: str
96
+
97
+
98
+ class GenerateMCPTokenRequest(BaseModel):
99
+ """Request to generate a long-lived MCP token"""
100
+ user_id: str
101
+ expires_days: int = 365
102
+
103
+
104
+ # class CommandOutputWithTraces(BaseModel):
105
+ # """CommandOutput extended with optional traces for HTTP responses"""
106
+ # command_responses: list[dict[str, Any]]
107
+ # workflow_name: str = ""
108
+ # context: str = ""
109
+ # command_name: str = ""
110
+ # command_parameters: str = ""
111
+ # success: bool = True
112
+ # traces: Optional[list[dict[str, Any]]] = None
113
+
114
+
115
+ # ============================================================================
116
+ # Helper Functions
117
+ # ============================================================================
118
+
119
+ # Create HTTPBearer security scheme instance
120
+ # This integrates with FastAPI's OpenAPI/Swagger UI to provide the "Authorize" button
121
+ http_bearer = HTTPBearer(
122
+ scheme_name="BearerAuth",
123
+ description="JWT Bearer token obtained from /initialize or /refresh_token endpoint",
124
+ auto_error=True
125
+ )
126
+
127
+ def get_session_from_jwt(
128
+ credentials: HTTPAuthorizationCredentials = Depends(http_bearer)
129
+ ) -> SessionData:
130
+ """
131
+ FastAPI dependency to extract and validate session data from JWT Bearer token.
132
+
133
+ This dependency integrates with FastAPI's security system and Swagger UI:
134
+ - Shows the "Authorize" button in Swagger UI
135
+ - Automatically handles "Bearer " prefix (no need to type it manually)
136
+ - Validates token format and presence
137
+
138
+ Args:
139
+ credentials: HTTPAuthorizationCredentials from the Authorization header.
140
+ FastAPI automatically extracts and validates the Bearer token format.
141
+
142
+ Returns:
143
+ SessionData: Validated session data extracted from the JWT token
144
+
145
+ Raises:
146
+ HTTPException: If the Authorization header is missing, malformed, or contains an invalid/expired token
147
+
148
+ Example:
149
+ Use as a dependency in FastAPI endpoints:
150
+ ```python
151
+ @app.post("/endpoint")
152
+ async def endpoint(session: SessionData = Depends(get_session_from_jwt)):
153
+ # Use session.user_id, session.token_type, etc.
154
+ pass
155
+ ```
156
+
157
+ HTTP Request Example:
158
+ ```bash
159
+ curl -X POST "http://localhost:8000/endpoint" \\
160
+ -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \\
161
+ -H "Content-Type: application/json" \\
162
+ -d '{"data": "value"}'
163
+ ```
164
+
165
+ Swagger UI Usage:
166
+ 1. Click the "Authorize" button (lock icon)
167
+ 2. Enter ONLY your JWT token (without "Bearer " prefix)
168
+ 3. Swagger UI automatically adds the "Bearer " prefix
169
+ """
170
+ # Extract token from credentials (already validated by HTTPBearer)
171
+ token = credentials.credentials
172
+
173
+ # Verify and decode token
174
+ try:
175
+ payload = verify_token(token, expected_type="access")
176
+
177
+ # Extract session data from payload
178
+ return SessionData(
179
+ user_id=payload["sub"],
180
+ token_type=payload["type"],
181
+ issued_at=payload["iat"],
182
+ expires_at=payload["exp"],
183
+ jti=payload["jti"]
184
+ )
185
+
186
+ except JWTError as e:
187
+ raise HTTPException(
188
+ status_code=status.HTTP_401_UNAUTHORIZED,
189
+ detail=f"Invalid or expired token: {str(e)}",
190
+ headers={"WWW-Authenticate": "Bearer"}
191
+ )
192
+ except KeyError as e:
193
+ raise HTTPException(
194
+ status_code=status.HTTP_401_UNAUTHORIZED,
195
+ detail=f"Token missing required claim: {str(e)}",
196
+ headers={"WWW-Authenticate": "Bearer"}
197
+ )
198
+
199
+
200
+ async def ensure_user_runtime_exists(
201
+ user_id: str,
202
+ session_manager: 'UserSessionManager',
203
+ workflow_path: str,
204
+ context: Optional[dict] = None,
205
+ startup_command: Optional[str] = None,
206
+ startup_action: Optional['fastworkflow.Action'] = None,
207
+ stream_format: str = "ndjson"
208
+ ) -> None:
209
+ """
210
+ Ensure a user runtime exists in the session manager. If not, create it.
211
+
212
+ This function encapsulates the session creation logic from the initialize endpoint,
213
+ allowing it to be reused across different parts of the application without duplicating code.
214
+
215
+ Args:
216
+ user_id: The user identifier
217
+ session_manager: The UserSessionManager instance
218
+ workflow_path: Path to the workflow directory (validated at server startup)
219
+ context: Optional workflow context dictionary
220
+ startup_command: Optional startup command
221
+ startup_action: Optional startup action
222
+ stream_format: Stream format preference ("ndjson" or "sse", default "ndjson")
223
+
224
+ Raises:
225
+ HTTPException: If session creation fails
226
+ """
227
+ # Check if user already has an active session
228
+ existing_runtime = await session_manager.get_session(user_id)
229
+ if existing_runtime:
230
+ logger.debug(f"Session for user_id {user_id} already exists, skipping creation")
231
+ return
232
+
233
+ logger.info(f"Creating new session for user_id: {user_id}")
234
+
235
+ # Resolve conversation store base folder from SPEEDDICT_FOLDERNAME/user_conversations
236
+ conv_base_folder = get_userconversations_dir()
237
+
238
+ # Create conversation store for this user
239
+ conversation_store = ConversationStore(user_id, conv_base_folder)
240
+
241
+ # Create ChatSession in agent mode (forced)
242
+ chat_session = fastworkflow.ChatSession(run_as_agent=True)
243
+
244
+ # Restore last conversation if it exists; else start new
245
+ conv_id_to_restore = None
246
+ if conv_id_to_restore := conversation_store.get_last_conversation_id():
247
+ conversation = conversation_store.get_conversation(conv_id_to_restore)
248
+ if not conversation:
249
+ # this means a new conversation was started but not saved
250
+ conv_id_to_restore = conv_id_to_restore-1
251
+ conversation = conversation_store.get_conversation(conv_id_to_restore)
252
+
253
+ if conversation:
254
+ # Restore the conversation history from saved turns
255
+ restored_history = restore_history_from_turns(conversation["turns"])
256
+ chat_session._conversation_history = restored_history
257
+ logger.info(f"Restored conversation {conv_id_to_restore} for user {user_id}")
258
+ else:
259
+ logger.info(f"No conversations available for user {user_id}, starting new")
260
+ conv_id_to_restore = None
261
+
262
+ # Start the workflow
263
+ chat_session.start_workflow(
264
+ workflow_folderpath=workflow_path,
265
+ workflow_context=context,
266
+ startup_command=startup_command,
267
+ startup_action=startup_action,
268
+ keep_alive=True
269
+ )
270
+
271
+ # Create and store user runtime
272
+ await session_manager.create_session(
273
+ user_id=user_id,
274
+ chat_session=chat_session,
275
+ conversation_store=conversation_store,
276
+ active_conversation_id=conv_id_to_restore,
277
+ stream_format=stream_format
278
+ )
279
+
280
+ logger.info(f"Successfully created session for user_id: {user_id}")
281
+
282
+ # Wait for workflow to be ready (background thread sets status to RUNNING)
283
+ import asyncio
284
+ import time
285
+ max_wait = 5 # seconds
286
+ wait_start = time.time()
287
+ from fastworkflow.chat_session import SessionStatus
288
+ while chat_session._status != SessionStatus.RUNNING and time.time() - wait_start < max_wait:
289
+ await asyncio.sleep(0.1)
290
+
291
+ if chat_session._status != SessionStatus.RUNNING:
292
+ logger.warning(f"Workflow not fully started after {max_wait}s, status={chat_session._status}")
293
+
294
+
295
+ def get_userconversations_dir() -> str:
296
+ """
297
+ Return SPEEDDICT_FOLDERNAME/user_conversations, creating the directory if missing.
298
+ fastworkflow is injected to avoid circular imports and to access get_env_var.
299
+ """
300
+ speedict_foldername = fastworkflow.get_env_var("SPEEDDICT_FOLDERNAME")
301
+ user_conversations_dir = os.path.join(speedict_foldername, "user_conversations")
302
+ os.makedirs(user_conversations_dir, exist_ok=True)
303
+ return user_conversations_dir
304
+
305
+
306
+ async def wait_for_command_output(
307
+ runtime: 'UserRuntime',
308
+ timeout_seconds: int
309
+ ) -> 'fastworkflow.CommandOutput':
310
+ """Wait for command output from the queue with timeout"""
311
+ start_time = time.time()
312
+
313
+ while time.time() - start_time < timeout_seconds:
314
+ try:
315
+ return runtime.chat_session.command_output_queue.get(timeout=0.5)
316
+ except queue.Empty:
317
+ await asyncio.sleep(0.1)
318
+ continue
319
+
320
+ raise HTTPException(
321
+ status_code=status.HTTP_504_GATEWAY_TIMEOUT,
322
+ detail=f"Command execution timed out after {timeout_seconds} seconds"
323
+ )
324
+
325
+
326
+ def collect_trace_events(runtime: 'UserRuntime') -> list[dict[str, Any]]:
327
+ """Drain and collect all trace events from the queue"""
328
+ traces = []
329
+
330
+ while True:
331
+ try:
332
+ evt = runtime.chat_session.command_trace_queue.get_nowait()
333
+ traces.append({
334
+ "direction": evt.direction.value if hasattr(evt.direction, 'value') else str(evt.direction),
335
+ "raw_command": evt.raw_command,
336
+ "command_name": evt.command_name,
337
+ "parameters": evt.parameters,
338
+ "response_text": evt.response_text,
339
+ "success": evt.success,
340
+ "timestamp_ms": evt.timestamp_ms
341
+ })
342
+ except queue.Empty:
343
+ break
344
+
345
+ return traces
346
+
347
+
348
+ # ============================================================================
349
+ # Session Management
350
+ # ============================================================================
351
+
352
+ @dataclass
353
+ class UserRuntime:
354
+ """Per-user runtime state"""
355
+ user_id: str
356
+ active_conversation_id: int
357
+ chat_session: 'fastworkflow.ChatSession'
358
+ lock: asyncio.Lock
359
+ conversation_store: 'ConversationStore'
360
+ stream_format: str = "ndjson" # "ndjson" | "sse"
361
+
362
+
363
+ class UserSessionManager:
364
+ """Process-wide manager for user sessions"""
365
+
366
+ def __init__(self):
367
+ self._sessions: dict[str, UserRuntime] = {}
368
+ self._lock = asyncio.Lock()
369
+
370
+ async def get_session(self, user_id: str) -> Optional[UserRuntime]:
371
+ """Get a session by user_id"""
372
+ async with self._lock:
373
+ return self._sessions.get(user_id)
374
+
375
+ async def create_session(
376
+ self,
377
+ user_id: str,
378
+ chat_session: 'fastworkflow.ChatSession',
379
+ conversation_store: 'ConversationStore',
380
+ active_conversation_id: Optional[int] = None,
381
+ stream_format: str = "ndjson"
382
+ ) -> UserRuntime:
383
+ """Create or update a session"""
384
+ async with self._lock:
385
+ runtime = UserRuntime(
386
+ user_id=user_id,
387
+ active_conversation_id=active_conversation_id or 0,
388
+ chat_session=chat_session,
389
+ lock=asyncio.Lock(),
390
+ conversation_store=conversation_store,
391
+ stream_format=stream_format
392
+ )
393
+ self._sessions[user_id] = runtime
394
+ return runtime
395
+
396
+ async def remove_session(self, user_id: str) -> None:
397
+ """Remove a session"""
398
+ async with self._lock:
399
+ if user_id in self._sessions:
400
+ del self._sessions[user_id]
401
+
402
+
403
+ # ============================================================================
404
+ # Helper Functions
405
+ # ============================================================================
406
+
407
+ def save_conversation_incremental(runtime: UserRuntime, extract_turns_func, logger) -> None:
408
+ """
409
+ Save conversation turns incrementally after each turn (without generating topic/summary).
410
+ This provides crash protection - all turns except the last will be preserved.
411
+ """
412
+ # Extract turns from conversation history
413
+ if turns := extract_turns_func(runtime.chat_session.conversation_history):
414
+ # Initialize conversation ID for first conversation if needed
415
+ if runtime.active_conversation_id == 0:
416
+ # This is the first conversation for this session
417
+ # Reserve ID 1 and use it
418
+ runtime.active_conversation_id = runtime.conversation_store.reserve_next_conversation_id()
419
+ logger.debug(f"Initialized first conversation with ID {runtime.active_conversation_id} for user {runtime.user_id}")
420
+
421
+ # Save turns using the active conversation ID
422
+ runtime.conversation_store.save_conversation_turns(
423
+ runtime.active_conversation_id, turns
424
+ )
425
+ logger.debug(f"Incrementally saved {len(turns)} turn(s) to conversation {runtime.active_conversation_id}")
426
+
427
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastworkflow
3
- Version: 2.16.0
3
+ Version: 2.17.0
4
4
  Summary: A framework for rapidly building large-scale, deterministic, interactive workflows with a fault-tolerant, conversational UX
5
5
  License: Apache-2.0
6
6
  Keywords: fastworkflow,ai,workflow,llm,openai
@@ -7,7 +7,7 @@ fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/ab
7
7
  fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py,sha256=VHfhwlqc1ceG9P_wL8Fl7dpJA2UlcSrcXhz7zZU9NpA,2517
8
8
  fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py,sha256=K526OAf5ks95SwqVdRNVxLM_AWDfA1qXbkNYq0dANwg,1889
9
9
  fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py,sha256=xvInu6uDw0YRUHVXNyTZphSr75f8QiQgFwDtv7SlE9o,1346
10
- fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py,sha256=9I9Y1VRvmiqft7vkz_8gGgCYTesMzBvP58wef3sjLZM,6593
10
+ fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py,sha256=KEYZ8qJG9j_93ycy2CbRdvoqJZ5FawmwjDyRlXWMNeM,6887
11
11
  fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_is_current_context.py,sha256=S5RQLr62Q2MnKU85nw4IW_ueAK_FXvhcY9gXajFxujg,1464
12
12
  fastworkflow/_workflows/command_metadata_extraction/_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py,sha256=Sqpc2hwM-DgmsqiHu3OoOuqo3XnHLkFlmyYCJA8nj_8,7843
@@ -35,13 +35,13 @@ fastworkflow/build/navigator_stub_generator.py,sha256=_DSvHC6r1xWQiFHtUgPhI51nQf
35
35
  fastworkflow/build/pydantic_model_generator.py,sha256=oNyoANyUWBpHG-fE3tGL911RNvDzQXjxAm0ssvuXUH4,1854
36
36
  fastworkflow/build/utterance_generator.py,sha256=UrtkF0wyAZ1hiFitHX0g8w7Wh-D0leLCrP1aUACSfHo,299
37
37
  fastworkflow/cache_matching.py,sha256=OoB--1tO6-O4BKCuCrUbB0CkUr76J62K4VAf6MShi-w,7984
38
- fastworkflow/chat_session.py,sha256=p8n2rQx3mhZ_DoRb14sRbp0PGKidMS6H2wwC1TNPxnM,31592
38
+ fastworkflow/chat_session.py,sha256=mduwtY27MV5YcabUbaFDXyRalVVNLUpp7ZFNiOV9ewc,31627
39
39
  fastworkflow/cli.py,sha256=li9OFT05sxqz4BZJc9byKAeTmomjLfsWMVuy0OiRGSs,18953
40
- fastworkflow/command_context_model.py,sha256=nWxLP3TR7WJr3yWCedqcdFOxo_kwae_mS3VRN2cOmK8,13437
40
+ fastworkflow/command_context_model.py,sha256=bQadDB_IH2lc0br46IT07Iej_j2KrAMderiVKqU7gno,15914
41
41
  fastworkflow/command_directory.py,sha256=aJ6UQCwevfF11KbcQB2Qz6mQ7Kj91pZtvHmQY6JFnao,29030
42
42
  fastworkflow/command_executor.py,sha256=WTSrukv6UDQfWUDSNleIQ1TxwDnAQIKIimh4sQVwnig,8457
43
43
  fastworkflow/command_interfaces.py,sha256=PWIKlcp0G8nmYl0vkrg1o6QzJL0pxXkfrn1joqTa0eU,460
44
- fastworkflow/command_metadata_api.py,sha256=KtidE3PM9HYfY-nmEXZ8Y4nnaw2qn23p_gvwFVT3F8Y,39770
44
+ fastworkflow/command_metadata_api.py,sha256=J5ltiwOhFRf1fggm8yWziKPrZA3trKt_rkyBtSS0H3I,42233
45
45
  fastworkflow/command_routing.py,sha256=R7194pcY0d2VHzmCu9ALacm1UvNuIRIvTn8mLp-EZIM,17219
46
46
  fastworkflow/docs/context_modules_prd.txt,sha256=9wvs3LgNoIVXAczo1sXBIV4YmFqVhzC2ja1T3K7FG04,2199
47
47
  fastworkflow/examples/extended_workflow_example/README.md,sha256=2O0O4Bg--fwF98YDScnkNCUL3PcH8KpX2p6I1cwNWeg,2864
@@ -144,7 +144,14 @@ fastworkflow/mcp_server.py,sha256=NxbLSKf2MA4lAHVcm6ZfiVuOjVO6IeV5Iw17wImFbxQ,88
144
144
  fastworkflow/model_pipeline_training.py,sha256=P_9wrYSfJVSYCTu8VEPkgXJ16eH58LLCK4rCRbRFAVg,46740
145
145
  fastworkflow/refine/__main__.py,sha256=bDLpPNMcdp8U4EFnMdjxx1sPDQCZuEJoBURr2KebTng,3398
146
146
  fastworkflow/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
- fastworkflow/run/__main__.py,sha256=e3gTjcQwYuEz12LZIdL5w48aMRJ-42MdlyapINj7XWk,12430
147
+ fastworkflow/run/__main__.py,sha256=XmybAsr1MnCx8tzvhWxtBT7xu2Om3PVZFtABXavPccU,12075
148
+ fastworkflow/run_fastapi_mcp/README.md,sha256=H3XcMNxDcOdGnHNODGe8U7b9s7loiHo-rEZqs-NXzn8,8024
149
+ fastworkflow/run_fastapi_mcp/conversation_store.py,sha256=OuS6Yq5noXrNn5jPwld_KueVi53Jlpa0qvF_vLUIbgg,13516
150
+ fastworkflow/run_fastapi_mcp/jwt_manager.py,sha256=fV5cKgYKluBk3l3ZepTSY3MgN0OopyH6oh8G_ncFQ4k,8166
151
+ fastworkflow/run_fastapi_mcp/main.py,sha256=JmgfViu2jpHBYvjx0DJ4QEU3D56CZJXnD1yo_8hzAq4,48148
152
+ fastworkflow/run_fastapi_mcp/mcp_specific.py,sha256=RdOPcPn68KlxNSM9Vb2yeYEDNGoNTcKZq-AC0cd86cw,4506
153
+ fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py,sha256=oYWn30O-xKX6pVjunCeLupyOM2DbeZ3QgFj-F2LalOE,1191
154
+ fastworkflow/run_fastapi_mcp/utils.py,sha256=GGwX3DEghY4TWntSwvrGvOwaiE_vxh_tCdXT6pA2gP4,15525
148
155
  fastworkflow/train/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
149
156
  fastworkflow/train/__main__.py,sha256=AeGja42d0QhslQkxvDVigIluxxL7DYLdQPXYFOKQ7QA,8536
150
157
  fastworkflow/train/generate_synthetic.py,sha256=sTDk-E5ewkS4o-0LJeofiEv4uXGpqdGcFRYKY_Yf36Y,5322
@@ -168,8 +175,8 @@ fastworkflow/utils/startup_progress.py,sha256=9icSdnpFAxzIq0sUliGpNaH0Efvrt5lDtG
168
175
  fastworkflow/workflow.py,sha256=37gn7e3ct-gdGw43zS6Ab_ADoJJBO4eJW2PywfUpjEg,18825
169
176
  fastworkflow/workflow_agent.py,sha256=-RXoHXH-vrEh6AWC6iYAwwR9CvaRynYuu-KrzOPCJbg,16348
170
177
  fastworkflow/workflow_inheritance_model.py,sha256=Pp-qSrQISgPfPjJVUfW84pc7HLmL2evuq0UVIYR51K0,7974
171
- fastworkflow-2.16.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
172
- fastworkflow-2.16.0.dist-info/METADATA,sha256=Mno8E51bhYTFQ4P7YRzmd71rjOBm_niCOKnFsTGmJNs,30336
173
- fastworkflow-2.16.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
174
- fastworkflow-2.16.0.dist-info/entry_points.txt,sha256=m8HqoPzCyaZLAx-V5X8MJgw3Lx3GiPDlxNEZ7K-Gb-U,54
175
- fastworkflow-2.16.0.dist-info/RECORD,,
178
+ fastworkflow-2.17.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
179
+ fastworkflow-2.17.0.dist-info/METADATA,sha256=oMz7RTyTMeFvweI6yza3AVnVU7VfGVxglUhBCFiQL8c,30336
180
+ fastworkflow-2.17.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
181
+ fastworkflow-2.17.0.dist-info/entry_points.txt,sha256=m8HqoPzCyaZLAx-V5X8MJgw3Lx3GiPDlxNEZ7K-Gb-U,54
182
+ fastworkflow-2.17.0.dist-info/RECORD,,