soprano-sdk 0.2.6__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/PKG-INFO +1 -1
  2. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/pyproject.toml +1 -1
  3. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/authenticators/mfa.py +31 -9
  4. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/core/constants.py +4 -0
  5. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/core/engine.py +15 -2
  6. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_mfa_scenarios.py +5 -8
  7. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/.github/workflows/test_build_and_publish.yaml +0 -0
  8. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/.gitignore +0 -0
  9. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/.python-version +0 -0
  10. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/CLAUDE.md +0 -0
  11. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/LICENSE +0 -0
  12. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/README.md +0 -0
  13. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/concert_booking/__init__.py +0 -0
  14. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/concert_booking/booking_helpers.py +0 -0
  15. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/concert_booking/concert_ticket_booking.yaml +0 -0
  16. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/framework_example.yaml +0 -0
  17. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/greeting_functions.py +0 -0
  18. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/greeting_workflow.yaml +0 -0
  19. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/main.py +0 -0
  20. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/persistence/README.md +0 -0
  21. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/persistence/conversation_based.py +0 -0
  22. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/persistence/entity_based.py +0 -0
  23. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/persistence/mongodb_demo.py +0 -0
  24. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/return_functions.py +0 -0
  25. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/return_workflow.yaml +0 -0
  26. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/structured_output_example.yaml +0 -0
  27. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/README.md +0 -0
  28. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/crewai_supervisor_ui.py +0 -0
  29. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/langgraph_supervisor_ui.py +0 -0
  30. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/tools/__init__.py +0 -0
  31. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/tools/crewai_tools.py +0 -0
  32. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/tools/langgraph_tools.py +0 -0
  33. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/supervisors/workflow_tools.py +0 -0
  34. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/tools/__init__.py +0 -0
  35. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/tools/address.py +0 -0
  36. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/examples/validator.py +0 -0
  37. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/langgraph_demo.py +0 -0
  38. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/langgraph_selfloop_demo.py +0 -0
  39. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/langgraph_v.py +0 -0
  40. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/main.py +0 -0
  41. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/return_fsm.excalidraw +0 -0
  42. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/return_state_machine.png +0 -0
  43. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/legacy/ui.py +0 -0
  44. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/scripts/visualize_workflow.py +0 -0
  45. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/scripts/workflow_demo.py +0 -0
  46. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/scripts/workflow_demo_ui.py +0 -0
  47. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/__init__.py +0 -0
  48. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/agents/__init__.py +0 -0
  49. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/agents/adaptor.py +0 -0
  50. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/agents/factory.py +0 -0
  51. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/agents/structured_output.py +0 -0
  52. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/authenticators/__init__.py +0 -0
  53. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/core/__init__.py +0 -0
  54. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/core/rollback_strategies.py +0 -0
  55. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/core/state.py +0 -0
  56. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/engine.py +0 -0
  57. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/nodes/__init__.py +0 -0
  58. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/nodes/async_function.py +0 -0
  59. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/nodes/base.py +0 -0
  60. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/nodes/call_function.py +0 -0
  61. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/nodes/collect_input.py +0 -0
  62. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/nodes/factory.py +0 -0
  63. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/routing/__init__.py +0 -0
  64. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/routing/router.py +0 -0
  65. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/tools.py +0 -0
  66. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/utils/__init__.py +0 -0
  67. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/utils/function.py +0 -0
  68. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/utils/logger.py +0 -0
  69. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/utils/template.py +0 -0
  70. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/utils/tool.py +0 -0
  71. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/utils/tracing.py +0 -0
  72. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/validation/__init__.py +0 -0
  73. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/validation/schema.py +0 -0
  74. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/soprano_sdk/validation/validator.py +0 -0
  75. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/debug_jinja2.py +0 -0
  76. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_agent_factory.py +0 -0
  77. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_async_function.py +0 -0
  78. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_collect_input_refactor.py +0 -0
  79. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_external_values.py +0 -0
  80. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_inputs_validation.py +0 -0
  81. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_jinja2_path.py +0 -0
  82. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_jinja2_standalone.py +0 -0
  83. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_persistence.py +0 -0
  84. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_structured_output.py +0 -0
  85. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/tests/test_transition_routing.py +0 -0
  86. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/todo.md +0 -0
  87. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/uv.lock +0 -0
  88. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/.eslintrc.cjs +0 -0
  89. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/.gitignore +0 -0
  90. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/README.md +0 -0
  91. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/index.html +0 -0
  92. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/package-lock.json +0 -0
  93. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/package.json +0 -0
  94. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/App.jsx +0 -0
  95. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/CustomNode.jsx +0 -0
  96. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/StepDetailsModal.jsx +0 -0
  97. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/WorkflowGraph.jsx +0 -0
  98. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/WorkflowInfoPanel.jsx +0 -0
  99. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/assets/react.svg +0 -0
  100. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/src/main.jsx +0 -0
  101. {soprano_sdk-0.2.6 → soprano_sdk-0.2.7}/workflow-visualizer/vite.config.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: YAML-driven workflow engine with AI agent integration for building conversational SOPs
5
5
  Author: Arvind Thangamani
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "soprano-sdk"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "YAML-driven workflow engine with AI agent integration for building conversational SOPs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -154,25 +154,47 @@ class MFANodeConfig:
154
154
  model=model_name,
155
155
  initial_message="{{_mfa.message}}",
156
156
  instructions="""
157
- You are an authentication value extractor. Your job is to identify and extract MFA codes from user input.
157
+ You are an authentication value extractor. Your job is to identify and extract MFA codes from user input, or detect if the user wants to cancel the authentication flow.
158
158
 
159
159
  **Task:**
160
- - Read the user's message
161
- - Extract ONLY the OTP code value
162
- - Output in the exact format shown below
163
-
164
- Examples:
165
- * User says: "1234" → `MFA_CAPTURED:1223`
166
- * User says: "2345e" `MFA_CAPTURED:1223e
160
+ - Read the user's message carefully
161
+ - First, check if the user wants to cancel, stop, or exit the authentication process
162
+ - If they want to cancel, output: MFA_CANCELLED:
163
+ - Otherwise, extract ONLY the OTP/MFA code value and output in the format shown below
164
+
165
+ **Cancellation Detection:**
166
+ If the user expresses any intent to cancel, stop, exit, abort, or quit the authentication process, respond with: MFA_CANCELLED
167
+
168
+ Examples of cancellation phrases:
169
+ * "cancel" → MFA_CANCELLED:
170
+ * "I want to stop" → MFA_CANCELLED:
171
+ * "exit" → MFA_CANCELLED:
172
+ * "nevermind" → MFA_CANCELLED:
173
+ * "I don't want to continue" → MFA_CANCELLED:
174
+ * "stop this" → MFA_CANCELLED:
175
+ * "forget it" → MFA_CANCELLED:
176
+ * "abort" → MFA_CANCELLED:
177
+ * "quit" → MFA_CANCELLED:
178
+
179
+ **OTP Capture Examples:**
180
+ * "1234" → MFA_CAPTURED:1234
181
+ * "2345e" → MFA_CAPTURED:2345e
182
+ * "the code is 567890" → MFA_CAPTURED:567890
183
+ * "my otp is 123456" → MFA_CAPTURED:123456
167
184
 
168
185
  **Output Format:**
169
- MFA_CAPTURED:<input_field_name>
186
+ - For OTP/MFA codes: MFA_CAPTURED:<otp_value>
187
+ - For cancellation: MFA_CANCELLED:
170
188
 
171
189
  """),
172
190
  transitions=[
173
191
  dict(
174
192
  pattern="MFA_CAPTURED:",
175
193
  next=next_node
194
+ ),
195
+ dict(
196
+ pattern="MFA_CANCELLED:",
197
+ next="mfa_cancelled"
176
198
  )
177
199
  ]
178
200
  )
@@ -114,6 +114,10 @@ class MFAConfig(BaseSettings):
114
114
  default=30,
115
115
  description="API request timeout in seconds"
116
116
  )
117
+ mfa_cancelled_message: str = Field(
118
+ default="Authentication has been cancelled.",
119
+ description="Message to display when user cancels MFA authentication"
120
+ )
117
121
 
118
122
  model_config = SettingsConfigDict(
119
123
  case_sensitive=False,
@@ -39,8 +39,7 @@ class WorkflowEngine:
39
39
  self.step_map = {step['id']: step for step in self.steps}
40
40
  self.mfa_config = (mfa_config or MFAConfig()) if self.mfa_validator_steps else None
41
41
  self.data_fields = self.load_data()
42
-
43
- self.outcomes = self.config['outcomes']
42
+ self.outcomes = self.load_outcomes()
44
43
  self.metadata = self.config.get('metadata', {})
45
44
 
46
45
  self.StateType = create_state_model(self.data_fields)
@@ -260,6 +259,20 @@ class WorkflowEngine:
260
259
  )
261
260
  return data
262
261
 
262
+ def load_outcomes(self):
263
+ outcomes: list = self.config['outcomes']
264
+
265
+ if self.mfa_config:
266
+ mfa_cancelled_outcome = {
267
+ 'id': 'mfa_cancelled',
268
+ 'type': 'failure',
269
+ 'message': self.mfa_config.mfa_cancelled_message
270
+ }
271
+ outcomes.append(mfa_cancelled_outcome)
272
+ logger.info(f"Auto-generated 'mfa_cancelled' outcome with message: {self.mfa_config.mfa_cancelled_message}")
273
+
274
+ return outcomes
275
+
263
276
 
264
277
  def load_workflow(yaml_path: str, checkpointer=None, config=None, mfa_config: Optional[MFAConfig] = None) -> Tuple[CompiledStateGraph, WorkflowEngine]:
265
278
  """
@@ -447,7 +447,7 @@ def test_mfa_default_max_attempts():
447
447
 
448
448
  assert max_attempts == 3, \
449
449
  f"Expected default max_attempts=3, got {max_attempts}"
450
- print(f" ✅ Correctly uses default max_attempts=3")
450
+ print(" ✅ Correctly uses default max_attempts=3")
451
451
 
452
452
  print("\n✅ TEST PASSED: Default max_attempts value verified")
453
453
  print("=" * 80)
@@ -466,7 +466,6 @@ def test_mfa_custom_max_attempts():
466
466
 
467
467
  # Create a temporary YAML with custom max_attempts
468
468
  import tempfile
469
- import shutil
470
469
 
471
470
  yaml_path = os.path.join(get_examples_dir(), "concert_ticket_booking.yaml")
472
471
 
@@ -511,7 +510,7 @@ def test_mfa_custom_max_attempts():
511
510
 
512
511
  assert max_attempts == 5, \
513
512
  f"Expected custom max_attempts=5, got {max_attempts}"
514
- print(f" ✅ Correctly uses custom max_attempts=5")
513
+ print(" ✅ Correctly uses custom max_attempts=5")
515
514
 
516
515
  print("\n✅ TEST PASSED: Custom max_attempts value applied correctly")
517
516
  print("=" * 80)
@@ -579,7 +578,7 @@ def test_mfa_custom_error_message():
579
578
 
580
579
  assert error_message == custom_error, \
581
580
  f"Expected custom error message, got {error_message}"
582
- print(f" ✅ Correctly uses custom error message")
581
+ print(" ✅ Correctly uses custom error message")
583
582
 
584
583
  print("\n✅ TEST PASSED: Custom error message applied correctly")
585
584
  print("=" * 80)
@@ -656,7 +655,7 @@ def test_mfa_custom_max_attempts_and_error():
656
655
 
657
656
  assert error_message == custom_error, \
658
657
  f"Expected custom error message, got {error_message}"
659
- print(f" ✅ Correctly uses custom error message")
658
+ print(" ✅ Correctly uses custom error message")
660
659
 
661
660
  print("\n✅ TEST PASSED: Both custom values applied correctly")
662
661
  print("=" * 80)
@@ -742,8 +741,6 @@ def test_mfa_custom_headers_with_jinja():
742
741
  assert process_payment_step in engine.list_steps(), \
743
742
  f"Expected {process_payment_step} step to exist"
744
743
 
745
- step_info = engine.get_step_info(process_payment_step)
746
-
747
744
  # Check the MFA start node - this is where the MFA config is stored
748
745
  process_payment_mfa_start = 'process_payment_mfa_start'
749
746
  assert process_payment_mfa_start in engine.list_steps(), \
@@ -756,7 +753,7 @@ def test_mfa_custom_headers_with_jinja():
756
753
  assert 'headers' in mfa_start_info['mfa'], "MFA config should have headers"
757
754
 
758
755
  headers_config = mfa_start_info['mfa']['headers']
759
- print(f"\nHeaders defined in MFA config:")
756
+ print("\nHeaders defined in MFA config:")
760
757
  for key, value in headers_config.items():
761
758
  print(f" {key}: {value}")
762
759
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes