soprano-sdk 0.2.5__py3-none-any.whl → 0.2.7__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.
@@ -10,6 +10,7 @@ class MFAChallenge(TypedDict):
10
10
  class MFAState(TypedDict):
11
11
  challengeType: Literal['OTP', 'dob']
12
12
  post_payload: dict[str, str]
13
+ post_headers: NotRequired[dict[str, str]]
13
14
  otpValue: NotRequired[str]
14
15
  status: Literal['IN_PROGRESS', 'COMPLETED', 'ERRORED', 'FAILED'] | None
15
16
  message: str
@@ -35,6 +36,10 @@ def enforce_mfa_if_required(state: dict, mfa_config: Optional[MFAConfig] = None)
35
36
  _mfa : MFAState = state['_mfa']
36
37
  if _mfa['status'] == 'COMPLETED':
37
38
  return True
39
+
40
+ # Use custom headers if provided, otherwise empty dict
41
+ headers = _mfa.get('post_headers', {})
42
+
38
43
  generate_token_response = requests.post(
39
44
  build_path(
40
45
  base_url=mfa_config.generate_token_base_url,
@@ -42,7 +47,7 @@ def enforce_mfa_if_required(state: dict, mfa_config: Optional[MFAConfig] = None)
42
47
  ),
43
48
  json=_mfa['post_payload'],
44
49
  timeout=mfa_config.api_timeout,
45
- headers={"Authorization": f"Bearer {state['bearer_token']}"}
50
+ headers=headers
46
51
  )
47
52
  _, error = get_response(generate_token_response)
48
53
 
@@ -65,6 +70,9 @@ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dic
65
70
  if not state[input_field_name]:
66
71
  return False
67
72
 
73
+ # Use custom headers if provided, otherwise empty dict
74
+ headers = _mfa.get('post_headers', {})
75
+
68
76
  post_payload = _mfa['post_payload']
69
77
  challenge_field_name = f"{_mfa['challengeType'].lower()}Challenge"
70
78
  post_payload.update({challenge_field_name: {"value": state[input_field_name]}})
@@ -75,7 +83,7 @@ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dic
75
83
  ),
76
84
  json=post_payload,
77
85
  timeout=mfa_config.api_timeout,
78
- headers={"Authorization": f"Bearer {state['bearer_token']}"}
86
+ headers=headers
79
87
  )
80
88
  _mfa['retry_count'] += 1
81
89
  response, error = get_response(validate_token_response)
@@ -95,7 +103,7 @@ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dic
95
103
  ),
96
104
  json=post_payload,
97
105
  timeout=mfa_config.api_timeout,
98
- headers={"Authorization": f"Bearer {state['bearer_token']}"}
106
+ headers=headers
99
107
  )
100
108
  if authorize.status_code == 204:
101
109
  _mfa['status'] = 'COMPLETED'
@@ -146,25 +154,47 @@ class MFANodeConfig:
146
154
  model=model_name,
147
155
  initial_message="{{_mfa.message}}",
148
156
  instructions="""
149
- 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.
150
158
 
151
159
  **Task:**
152
- - Read the user's message
153
- - Extract ONLY the OTP code value
154
- - Output in the exact format shown below
155
-
156
- Examples:
157
- * User says: "1234" → `MFA_CAPTURED:1223`
158
- * 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
159
184
 
160
185
  **Output Format:**
161
- MFA_CAPTURED:<input_field_name>
186
+ - For OTP/MFA codes: MFA_CAPTURED:<otp_value>
187
+ - For cancellation: MFA_CANCELLED:
162
188
 
163
189
  """),
164
190
  transitions=[
165
191
  dict(
166
192
  pattern="MFA_CAPTURED:",
167
193
  next=next_node
194
+ ),
195
+ dict(
196
+ pattern="MFA_CANCELLED:",
197
+ next="mfa_cancelled"
168
198
  )
169
199
  ]
170
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
  """
@@ -39,11 +39,17 @@ class CallFunctionStrategy(ActionStrategy):
39
39
  if 'mfa' in self.step_config:
40
40
  state['_mfa'] = state.get('_mfa', {})
41
41
  state['_mfa']['post_payload'] = dict(transactionId=str(uuid.uuid4()))
42
+ state['_mfa']['post_headers'] = {}
42
43
  state['_mfa_config'] = self.engine_context.mfa_config
43
44
  template_loader = self.engine_context.get_config_value("template_loader", Environment())
44
45
  for k, v in self.step_config['mfa']['payload'].items():
45
46
  state['_mfa']['post_payload'][k] = compile_values(template_loader, state, v)
46
47
 
48
+ # Process headers if provided
49
+ if 'headers' in self.step_config['mfa']:
50
+ for k, v in self.step_config['mfa']['headers'].items():
51
+ state['_mfa']['post_headers'][k] = compile_values(template_loader, state, v)
52
+
47
53
  def execute(self, state: Dict[str, Any]) -> Dict[str, Any]:
48
54
  from ..utils.tracing import trace_node_execution
49
55
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.2.5
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
@@ -6,16 +6,16 @@ soprano_sdk/agents/adaptor.py,sha256=Cm02YKFclrESu-Qq4CTknCgU7KaA7Z_2FspnQDkEVfU
6
6
  soprano_sdk/agents/factory.py,sha256=Aucfz4rZVKCXMAQtbGAqp1JR8aYwa66mokRmKkKGhYA,6699
7
7
  soprano_sdk/agents/structured_output.py,sha256=7DSVzfMPsZAqBwI3v6XL15qG5Gh4jJ-qddcVPaa3gdc,3326
8
8
  soprano_sdk/authenticators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- soprano_sdk/authenticators/mfa.py,sha256=7aTueLu2jLxl213Q5_BkNQ534RgEwf6lodQa_PaJFWs,5980
9
+ soprano_sdk/authenticators/mfa.py,sha256=Vew9Nb8pIRTw9hKbEZTH3YScY-fZ_TLq4ZuCzc-wbr8,7387
10
10
  soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- soprano_sdk/core/constants.py,sha256=21ful8skHgDeDJJMdB_oJDw7Xq3lkbO6KWOhHuuLIa0,3330
12
- soprano_sdk/core/engine.py,sha256=UTFJimrgUzfdDZgc4rW5nvkOKevAEGW8C5Zr1D0tgcs,11270
11
+ soprano_sdk/core/constants.py,sha256=UPXlRbF7gsOUNOV0Lm0jvgFfgZX7JrsV6n9I5csMfns,3508
12
+ soprano_sdk/core/engine.py,sha256=vM2-nAvDc6Oam-q_BegabBT4uKVMV8DVV10SyJJPgVw,11762
13
13
  soprano_sdk/core/rollback_strategies.py,sha256=NjDTtBCZlqyDql5PSwI9SMDLK7_BNlTxbW_cq_5gV0g,7783
14
14
  soprano_sdk/core/state.py,sha256=k8ojLfWgjES3p9XWMeGU5s4UK-Xa5T8mS4VtZzTrcDw,2961
15
15
  soprano_sdk/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  soprano_sdk/nodes/async_function.py,sha256=v6WujLKm8NXX2iAkJ7Gz_QIVCtWFrpC6nnPyyfuCxXs,9354
17
17
  soprano_sdk/nodes/base.py,sha256=idFyOGGPnjsASYnrOF_NIh7eFcSuJqw61EoVN_WCTaU,2360
18
- soprano_sdk/nodes/call_function.py,sha256=PdmSSVXkMT5ptxeUjmKqND-wvqbaXp1i61QzIzFOfgQ,5535
18
+ soprano_sdk/nodes/call_function.py,sha256=afYBmj5Aditbkvb_7gD3CsXBEEUohcsC1_cdHfcOunE,5847
19
19
  soprano_sdk/nodes/collect_input.py,sha256=lEltZOU5ALvc57q8I_4SjzzEapVejy9mS0E73Jf7-sk,23759
20
20
  soprano_sdk/nodes/factory.py,sha256=IbBzT4FKBnYw5PuSo7uDONV3HSFtoyqjBQQtXtUY2IY,1756
21
21
  soprano_sdk/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -29,7 +29,7 @@ soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,
29
29
  soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
30
30
  soprano_sdk/validation/schema.py,sha256=SlC4sq-ueEg0p_8Uox_cgPj9S-0AEEiOOlA1Vsu0DsE,15443
31
31
  soprano_sdk/validation/validator.py,sha256=GaCvHvjwVe88Z8yatQsueiPnqtq1oo5uN75gogzpQT0,8940
32
- soprano_sdk-0.2.5.dist-info/METADATA,sha256=KYdstjCuIfKrBuMAGOTVZDDYLESuWEcBFQY7oEhNdNg,11297
33
- soprano_sdk-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
- soprano_sdk-0.2.5.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
35
- soprano_sdk-0.2.5.dist-info/RECORD,,
32
+ soprano_sdk-0.2.7.dist-info/METADATA,sha256=L9wKEDqgskIRsFIIhtYJq8gfUZ2TJuwXUqrtb-4qNEI,11297
33
+ soprano_sdk-0.2.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
+ soprano_sdk-0.2.7.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
35
+ soprano_sdk-0.2.7.dist-info/RECORD,,