signalwire-agents 0.1.13__py3-none-any.whl → 1.0.17.dev4__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.
Files changed (143) hide show
  1. signalwire_agents/__init__.py +99 -15
  2. signalwire_agents/agent_server.py +248 -60
  3. signalwire_agents/agents/bedrock.py +296 -0
  4. signalwire_agents/cli/__init__.py +9 -0
  5. signalwire_agents/cli/build_search.py +951 -41
  6. signalwire_agents/cli/config.py +80 -0
  7. signalwire_agents/cli/core/__init__.py +10 -0
  8. signalwire_agents/cli/core/agent_loader.py +470 -0
  9. signalwire_agents/cli/core/argparse_helpers.py +179 -0
  10. signalwire_agents/cli/core/dynamic_config.py +71 -0
  11. signalwire_agents/cli/core/service_loader.py +303 -0
  12. signalwire_agents/cli/dokku.py +2320 -0
  13. signalwire_agents/cli/execution/__init__.py +10 -0
  14. signalwire_agents/cli/execution/datamap_exec.py +446 -0
  15. signalwire_agents/cli/execution/webhook_exec.py +134 -0
  16. signalwire_agents/cli/init_project.py +2636 -0
  17. signalwire_agents/cli/output/__init__.py +10 -0
  18. signalwire_agents/cli/output/output_formatter.py +255 -0
  19. signalwire_agents/cli/output/swml_dump.py +186 -0
  20. signalwire_agents/cli/simulation/__init__.py +10 -0
  21. signalwire_agents/cli/simulation/data_generation.py +374 -0
  22. signalwire_agents/cli/simulation/data_overrides.py +200 -0
  23. signalwire_agents/cli/simulation/mock_env.py +282 -0
  24. signalwire_agents/cli/swaig_test_wrapper.py +52 -0
  25. signalwire_agents/cli/test_swaig.py +566 -2366
  26. signalwire_agents/cli/types.py +81 -0
  27. signalwire_agents/core/__init__.py +2 -2
  28. signalwire_agents/core/agent/__init__.py +12 -0
  29. signalwire_agents/core/agent/config/__init__.py +12 -0
  30. signalwire_agents/core/agent/deployment/__init__.py +9 -0
  31. signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
  32. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  33. signalwire_agents/core/agent/prompt/manager.py +306 -0
  34. signalwire_agents/core/agent/routing/__init__.py +9 -0
  35. signalwire_agents/core/agent/security/__init__.py +9 -0
  36. signalwire_agents/core/agent/swml/__init__.py +9 -0
  37. signalwire_agents/core/agent/tools/__init__.py +15 -0
  38. signalwire_agents/core/agent/tools/decorator.py +97 -0
  39. signalwire_agents/core/agent/tools/registry.py +210 -0
  40. signalwire_agents/core/agent_base.py +845 -2916
  41. signalwire_agents/core/auth_handler.py +233 -0
  42. signalwire_agents/core/config_loader.py +259 -0
  43. signalwire_agents/core/contexts.py +418 -0
  44. signalwire_agents/core/data_map.py +3 -15
  45. signalwire_agents/core/function_result.py +116 -44
  46. signalwire_agents/core/logging_config.py +162 -18
  47. signalwire_agents/core/mixins/__init__.py +28 -0
  48. signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
  49. signalwire_agents/core/mixins/auth_mixin.py +280 -0
  50. signalwire_agents/core/mixins/prompt_mixin.py +358 -0
  51. signalwire_agents/core/mixins/serverless_mixin.py +460 -0
  52. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  53. signalwire_agents/core/mixins/state_mixin.py +153 -0
  54. signalwire_agents/core/mixins/tool_mixin.py +230 -0
  55. signalwire_agents/core/mixins/web_mixin.py +1142 -0
  56. signalwire_agents/core/security_config.py +333 -0
  57. signalwire_agents/core/skill_base.py +84 -1
  58. signalwire_agents/core/skill_manager.py +62 -20
  59. signalwire_agents/core/swaig_function.py +18 -5
  60. signalwire_agents/core/swml_builder.py +207 -11
  61. signalwire_agents/core/swml_handler.py +27 -21
  62. signalwire_agents/core/swml_renderer.py +123 -312
  63. signalwire_agents/core/swml_service.py +171 -203
  64. signalwire_agents/mcp_gateway/__init__.py +29 -0
  65. signalwire_agents/mcp_gateway/gateway_service.py +564 -0
  66. signalwire_agents/mcp_gateway/mcp_manager.py +513 -0
  67. signalwire_agents/mcp_gateway/session_manager.py +218 -0
  68. signalwire_agents/prefabs/concierge.py +0 -3
  69. signalwire_agents/prefabs/faq_bot.py +0 -3
  70. signalwire_agents/prefabs/info_gatherer.py +0 -3
  71. signalwire_agents/prefabs/receptionist.py +0 -3
  72. signalwire_agents/prefabs/survey.py +0 -3
  73. signalwire_agents/schema.json +9218 -5489
  74. signalwire_agents/search/__init__.py +7 -1
  75. signalwire_agents/search/document_processor.py +490 -31
  76. signalwire_agents/search/index_builder.py +307 -37
  77. signalwire_agents/search/migration.py +418 -0
  78. signalwire_agents/search/models.py +30 -0
  79. signalwire_agents/search/pgvector_backend.py +748 -0
  80. signalwire_agents/search/query_processor.py +162 -31
  81. signalwire_agents/search/search_engine.py +916 -35
  82. signalwire_agents/search/search_service.py +376 -53
  83. signalwire_agents/skills/README.md +452 -0
  84. signalwire_agents/skills/__init__.py +14 -2
  85. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  86. signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
  87. signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
  88. signalwire_agents/skills/datasphere/README.md +210 -0
  89. signalwire_agents/skills/datasphere/skill.py +84 -3
  90. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  91. signalwire_agents/skills/datasphere_serverless/__init__.py +9 -0
  92. signalwire_agents/skills/datasphere_serverless/skill.py +82 -1
  93. signalwire_agents/skills/datetime/README.md +132 -0
  94. signalwire_agents/skills/datetime/__init__.py +9 -0
  95. signalwire_agents/skills/datetime/skill.py +20 -7
  96. signalwire_agents/skills/joke/README.md +149 -0
  97. signalwire_agents/skills/joke/__init__.py +9 -0
  98. signalwire_agents/skills/joke/skill.py +21 -0
  99. signalwire_agents/skills/math/README.md +161 -0
  100. signalwire_agents/skills/math/__init__.py +9 -0
  101. signalwire_agents/skills/math/skill.py +18 -4
  102. signalwire_agents/skills/mcp_gateway/README.md +230 -0
  103. signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
  104. signalwire_agents/skills/mcp_gateway/skill.py +421 -0
  105. signalwire_agents/skills/native_vector_search/README.md +210 -0
  106. signalwire_agents/skills/native_vector_search/__init__.py +9 -0
  107. signalwire_agents/skills/native_vector_search/skill.py +569 -101
  108. signalwire_agents/skills/play_background_file/README.md +218 -0
  109. signalwire_agents/skills/play_background_file/__init__.py +12 -0
  110. signalwire_agents/skills/play_background_file/skill.py +242 -0
  111. signalwire_agents/skills/registry.py +395 -40
  112. signalwire_agents/skills/spider/README.md +236 -0
  113. signalwire_agents/skills/spider/__init__.py +13 -0
  114. signalwire_agents/skills/spider/skill.py +598 -0
  115. signalwire_agents/skills/swml_transfer/README.md +395 -0
  116. signalwire_agents/skills/swml_transfer/__init__.py +10 -0
  117. signalwire_agents/skills/swml_transfer/skill.py +359 -0
  118. signalwire_agents/skills/weather_api/README.md +178 -0
  119. signalwire_agents/skills/weather_api/__init__.py +12 -0
  120. signalwire_agents/skills/weather_api/skill.py +191 -0
  121. signalwire_agents/skills/web_search/README.md +163 -0
  122. signalwire_agents/skills/web_search/__init__.py +9 -0
  123. signalwire_agents/skills/web_search/skill.py +586 -112
  124. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  125. signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
  126. signalwire_agents/skills/{wikipedia → wikipedia_search}/skill.py +33 -3
  127. signalwire_agents/web/__init__.py +17 -0
  128. signalwire_agents/web/web_service.py +559 -0
  129. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-agent-init.1 +400 -0
  130. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-search.1 +483 -0
  131. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/swaig-test.1 +308 -0
  132. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/METADATA +347 -215
  133. signalwire_agents-1.0.17.dev4.dist-info/RECORD +147 -0
  134. signalwire_agents-1.0.17.dev4.dist-info/entry_points.txt +6 -0
  135. signalwire_agents/core/state/file_state_manager.py +0 -219
  136. signalwire_agents/core/state/state_manager.py +0 -101
  137. signalwire_agents/skills/wikipedia/__init__.py +0 -9
  138. signalwire_agents-0.1.13.data/data/schema.json +0 -5611
  139. signalwire_agents-0.1.13.dist-info/RECORD +0 -67
  140. signalwire_agents-0.1.13.dist-info/entry_points.txt +0 -3
  141. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/WHEEL +0 -0
  142. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/licenses/LICENSE +0 -0
  143. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,12 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
1
10
  """
2
11
  Contexts and Steps System for SignalWire Agents
3
12
 
@@ -18,9 +27,16 @@ class Step:
18
27
  self._step_criteria: Optional[str] = None
19
28
  self._functions: Optional[Union[str, List[str]]] = None
20
29
  self._valid_steps: Optional[List[str]] = None
30
+ self._valid_contexts: Optional[List[str]] = None
21
31
 
22
32
  # POM-style sections for rich prompts
23
33
  self._sections: List[Dict[str, Any]] = []
34
+
35
+ # Reset object for context switching from steps
36
+ self._reset_system_prompt: Optional[str] = None
37
+ self._reset_user_prompt: Optional[str] = None
38
+ self._reset_consolidate: bool = False
39
+ self._reset_full_reset: bool = False
24
40
 
25
41
  def set_text(self, text: str) -> 'Step':
26
42
  """
@@ -108,6 +124,71 @@ class Step:
108
124
  self._valid_steps = steps
109
125
  return self
110
126
 
127
+ def set_valid_contexts(self, contexts: List[str]) -> 'Step':
128
+ """
129
+ Set which contexts can be navigated to from this step
130
+
131
+ Args:
132
+ contexts: List of valid context names
133
+
134
+ Returns:
135
+ Self for method chaining
136
+ """
137
+ self._valid_contexts = contexts
138
+ return self
139
+
140
+ def set_reset_system_prompt(self, system_prompt: str) -> 'Step':
141
+ """
142
+ Set system prompt for context switching when this step navigates to a context
143
+
144
+ Args:
145
+ system_prompt: New system prompt for context switching
146
+
147
+ Returns:
148
+ Self for method chaining
149
+ """
150
+ self._reset_system_prompt = system_prompt
151
+ return self
152
+
153
+ def set_reset_user_prompt(self, user_prompt: str) -> 'Step':
154
+ """
155
+ Set user prompt for context switching when this step navigates to a context
156
+
157
+ Args:
158
+ user_prompt: User message to inject for context switching
159
+
160
+ Returns:
161
+ Self for method chaining
162
+ """
163
+ self._reset_user_prompt = user_prompt
164
+ return self
165
+
166
+ def set_reset_consolidate(self, consolidate: bool) -> 'Step':
167
+ """
168
+ Set whether to consolidate conversation when this step switches contexts
169
+
170
+ Args:
171
+ consolidate: Whether to consolidate previous conversation
172
+
173
+ Returns:
174
+ Self for method chaining
175
+ """
176
+ self._reset_consolidate = consolidate
177
+ return self
178
+
179
+ def set_reset_full_reset(self, full_reset: bool) -> 'Step':
180
+ """
181
+ Set whether to do full reset when this step switches contexts
182
+
183
+ Args:
184
+ full_reset: Whether to completely rewrite system prompt vs inject
185
+
186
+ Returns:
187
+ Self for method chaining
188
+ """
189
+ self._reset_full_reset = full_reset
190
+ return self
191
+
111
192
  def _render_text(self) -> str:
112
193
  """Render the step's prompt text"""
113
194
  if self._text is not None:
@@ -133,6 +214,7 @@ class Step:
133
214
  def to_dict(self) -> Dict[str, Any]:
134
215
  """Convert step to dictionary for SWML generation"""
135
216
  step_dict = {
217
+ "name": self.name,
136
218
  "text": self._render_text()
137
219
  }
138
220
 
@@ -145,6 +227,23 @@ class Step:
145
227
  if self._valid_steps is not None:
146
228
  step_dict["valid_steps"] = self._valid_steps
147
229
 
230
+ if self._valid_contexts is not None:
231
+ step_dict["valid_contexts"] = self._valid_contexts
232
+
233
+ # Add reset object if any reset parameters are set
234
+ reset_obj = {}
235
+ if self._reset_system_prompt is not None:
236
+ reset_obj["system_prompt"] = self._reset_system_prompt
237
+ if self._reset_user_prompt is not None:
238
+ reset_obj["user_prompt"] = self._reset_user_prompt
239
+ if self._reset_consolidate:
240
+ reset_obj["consolidate"] = self._reset_consolidate
241
+ if self._reset_full_reset:
242
+ reset_obj["full_reset"] = self._reset_full_reset
243
+
244
+ if reset_obj:
245
+ step_dict["reset"] = reset_obj
246
+
148
247
  return step_dict
149
248
 
150
249
 
@@ -156,6 +255,23 @@ class Context:
156
255
  self._steps: Dict[str, Step] = {}
157
256
  self._step_order: List[str] = []
158
257
  self._valid_contexts: Optional[List[str]] = None
258
+
259
+ # Context entry parameters
260
+ self._post_prompt: Optional[str] = None
261
+ self._system_prompt: Optional[str] = None
262
+ self._system_prompt_sections: List[Dict[str, Any]] = [] # For POM-style system prompts
263
+ self._consolidate: bool = False
264
+ self._full_reset: bool = False
265
+ self._user_prompt: Optional[str] = None
266
+ self._isolated: bool = False
267
+
268
+ # Context prompt (separate from system_prompt)
269
+ self._prompt_text: Optional[str] = None
270
+ self._prompt_sections: List[Dict[str, Any]] = []
271
+
272
+ # Context fillers
273
+ self._enter_fillers: Optional[Dict[str, List[str]]] = None
274
+ self._exit_fillers: Optional[Dict[str, List[str]]] = None
159
275
 
160
276
  def add_step(self, name: str) -> Step:
161
277
  """
@@ -188,6 +304,273 @@ class Context:
188
304
  self._valid_contexts = contexts
189
305
  return self
190
306
 
307
+ def set_post_prompt(self, post_prompt: str) -> 'Context':
308
+ """
309
+ Set post prompt override for this context
310
+
311
+ Args:
312
+ post_prompt: Post prompt text to use when this context is active
313
+
314
+ Returns:
315
+ Self for method chaining
316
+ """
317
+ self._post_prompt = post_prompt
318
+ return self
319
+
320
+ def set_system_prompt(self, system_prompt: str) -> 'Context':
321
+ """
322
+ Set system prompt for context switching (triggers context reset)
323
+
324
+ Args:
325
+ system_prompt: New system prompt for when this context is entered
326
+
327
+ Returns:
328
+ Self for method chaining
329
+ """
330
+ if self._system_prompt_sections:
331
+ raise ValueError("Cannot use set_system_prompt() when POM sections have been added for system prompt. Use one approach or the other.")
332
+ self._system_prompt = system_prompt
333
+ return self
334
+
335
+ def set_consolidate(self, consolidate: bool) -> 'Context':
336
+ """
337
+ Set whether to consolidate conversation history when entering this context
338
+
339
+ Args:
340
+ consolidate: Whether to consolidate previous conversation
341
+
342
+ Returns:
343
+ Self for method chaining
344
+ """
345
+ self._consolidate = consolidate
346
+ return self
347
+
348
+ def set_full_reset(self, full_reset: bool) -> 'Context':
349
+ """
350
+ Set whether to do full reset when entering this context
351
+
352
+ Args:
353
+ full_reset: Whether to completely rewrite system prompt vs inject
354
+
355
+ Returns:
356
+ Self for method chaining
357
+ """
358
+ self._full_reset = full_reset
359
+ return self
360
+
361
+ def set_user_prompt(self, user_prompt: str) -> 'Context':
362
+ """
363
+ Set user prompt to inject when entering this context
364
+
365
+ Args:
366
+ user_prompt: User message to inject for context
367
+
368
+ Returns:
369
+ Self for method chaining
370
+ """
371
+ self._user_prompt = user_prompt
372
+ return self
373
+
374
+ def set_isolated(self, isolated: bool) -> 'Context':
375
+ """
376
+ Set whether to truncate conversation history when entering this context
377
+
378
+ Args:
379
+ isolated: Whether to truncate conversation on context switch
380
+
381
+ Returns:
382
+ Self for method chaining
383
+ """
384
+ self._isolated = isolated
385
+ return self
386
+
387
+ def add_system_section(self, title: str, body: str) -> 'Context':
388
+ """
389
+ Add a POM section to the system prompt
390
+
391
+ Args:
392
+ title: Section title
393
+ body: Section body text
394
+
395
+ Returns:
396
+ Self for method chaining
397
+ """
398
+ if self._system_prompt is not None:
399
+ raise ValueError("Cannot add POM sections for system prompt when set_system_prompt() has been used. Use one approach or the other.")
400
+ self._system_prompt_sections.append({"title": title, "body": body})
401
+ return self
402
+
403
+ def add_system_bullets(self, title: str, bullets: List[str]) -> 'Context':
404
+ """
405
+ Add a POM section with bullet points to the system prompt
406
+
407
+ Args:
408
+ title: Section title
409
+ bullets: List of bullet points
410
+
411
+ Returns:
412
+ Self for method chaining
413
+ """
414
+ if self._system_prompt is not None:
415
+ raise ValueError("Cannot add POM sections for system prompt when set_system_prompt() has been used. Use one approach or the other.")
416
+ self._system_prompt_sections.append({"title": title, "bullets": bullets})
417
+ return self
418
+
419
+ def set_prompt(self, prompt: str) -> 'Context':
420
+ """
421
+ Set the context's prompt text directly
422
+
423
+ Args:
424
+ prompt: The prompt text for this context
425
+
426
+ Returns:
427
+ Self for method chaining
428
+ """
429
+ if self._prompt_sections:
430
+ raise ValueError("Cannot use set_prompt() when POM sections have been added. Use one approach or the other.")
431
+ self._prompt_text = prompt
432
+ return self
433
+
434
+ def add_section(self, title: str, body: str) -> 'Context':
435
+ """
436
+ Add a POM section to the context prompt
437
+
438
+ Args:
439
+ title: Section title
440
+ body: Section body text
441
+
442
+ Returns:
443
+ Self for method chaining
444
+ """
445
+ if self._prompt_text is not None:
446
+ raise ValueError("Cannot add POM sections when set_prompt() has been used. Use one approach or the other.")
447
+ self._prompt_sections.append({"title": title, "body": body})
448
+ return self
449
+
450
+ def add_bullets(self, title: str, bullets: List[str]) -> 'Context':
451
+ """
452
+ Add a POM section with bullet points to the context prompt
453
+
454
+ Args:
455
+ title: Section title
456
+ bullets: List of bullet points
457
+
458
+ Returns:
459
+ Self for method chaining
460
+ """
461
+ if self._prompt_text is not None:
462
+ raise ValueError("Cannot add POM sections when set_prompt() has been used. Use one approach or the other.")
463
+ self._prompt_sections.append({"title": title, "bullets": bullets})
464
+ return self
465
+
466
+ def set_enter_fillers(self, enter_fillers: Dict[str, List[str]]) -> 'Context':
467
+ """
468
+ Set fillers that the AI says when entering this context
469
+
470
+ Args:
471
+ enter_fillers: Dictionary mapping language codes (or "default") to lists of filler phrases
472
+ Example: {"en-US": ["Welcome...", "Hello..."], "default": ["Entering..."]}
473
+
474
+ Returns:
475
+ Self for method chaining
476
+ """
477
+ if enter_fillers and isinstance(enter_fillers, dict):
478
+ self._enter_fillers = enter_fillers
479
+ return self
480
+
481
+ def set_exit_fillers(self, exit_fillers: Dict[str, List[str]]) -> 'Context':
482
+ """
483
+ Set fillers that the AI says when exiting this context
484
+
485
+ Args:
486
+ exit_fillers: Dictionary mapping language codes (or "default") to lists of filler phrases
487
+ Example: {"en-US": ["Goodbye...", "Thank you..."], "default": ["Exiting..."]}
488
+
489
+ Returns:
490
+ Self for method chaining
491
+ """
492
+ if exit_fillers and isinstance(exit_fillers, dict):
493
+ self._exit_fillers = exit_fillers
494
+ return self
495
+
496
+ def add_enter_filler(self, language_code: str, fillers: List[str]) -> 'Context':
497
+ """
498
+ Add enter fillers for a specific language
499
+
500
+ Args:
501
+ language_code: Language code (e.g., "en-US", "es") or "default" for catch-all
502
+ fillers: List of filler phrases for entering this context
503
+
504
+ Returns:
505
+ Self for method chaining
506
+ """
507
+ if language_code and fillers and isinstance(fillers, list):
508
+ if self._enter_fillers is None:
509
+ self._enter_fillers = {}
510
+ self._enter_fillers[language_code] = fillers
511
+ return self
512
+
513
+ def add_exit_filler(self, language_code: str, fillers: List[str]) -> 'Context':
514
+ """
515
+ Add exit fillers for a specific language
516
+
517
+ Args:
518
+ language_code: Language code (e.g., "en-US", "es") or "default" for catch-all
519
+ fillers: List of filler phrases for exiting this context
520
+
521
+ Returns:
522
+ Self for method chaining
523
+ """
524
+ if language_code and fillers and isinstance(fillers, list):
525
+ if self._exit_fillers is None:
526
+ self._exit_fillers = {}
527
+ self._exit_fillers[language_code] = fillers
528
+ return self
529
+
530
+ def _render_prompt(self) -> Optional[str]:
531
+ """Render the context's prompt text"""
532
+ if self._prompt_text is not None:
533
+ return self._prompt_text
534
+
535
+ if not self._prompt_sections:
536
+ return None
537
+
538
+ # Convert POM sections to markdown
539
+ markdown_parts = []
540
+ for section in self._prompt_sections:
541
+ if "bullets" in section:
542
+ markdown_parts.append(f"## {section['title']}")
543
+ for bullet in section["bullets"]:
544
+ markdown_parts.append(f"- {bullet}")
545
+ else:
546
+ markdown_parts.append(f"## {section['title']}")
547
+ markdown_parts.append(section["body"])
548
+ markdown_parts.append("") # Add spacing
549
+
550
+ return "\n".join(markdown_parts).strip()
551
+
552
+ def _render_system_prompt(self) -> Optional[str]:
553
+ """Render the system prompt text"""
554
+ if self._system_prompt is not None:
555
+ return self._system_prompt
556
+
557
+ if not self._system_prompt_sections:
558
+ return None
559
+
560
+ # Convert POM sections to markdown
561
+ markdown_parts = []
562
+ for section in self._system_prompt_sections:
563
+ if "bullets" in section:
564
+ markdown_parts.append(f"## {section['title']}")
565
+ for bullet in section["bullets"]:
566
+ markdown_parts.append(f"- {bullet}")
567
+ else:
568
+ markdown_parts.append(f"## {section['title']}")
569
+ markdown_parts.append(section["body"])
570
+ markdown_parts.append("") # Add spacing
571
+
572
+ return "\n".join(markdown_parts).strip()
573
+
191
574
  def to_dict(self) -> Dict[str, Any]:
192
575
  """Convert context to dictionary for SWML generation"""
193
576
  if not self._steps:
@@ -199,6 +582,41 @@ class Context:
199
582
 
200
583
  if self._valid_contexts is not None:
201
584
  context_dict["valid_contexts"] = self._valid_contexts
585
+
586
+ # Add context entry parameters
587
+ if self._post_prompt is not None:
588
+ context_dict["post_prompt"] = self._post_prompt
589
+
590
+ rendered_system_prompt = self._render_system_prompt()
591
+ if rendered_system_prompt is not None:
592
+ context_dict["system_prompt"] = rendered_system_prompt
593
+
594
+ if self._consolidate:
595
+ context_dict["consolidate"] = self._consolidate
596
+
597
+ if self._full_reset:
598
+ context_dict["full_reset"] = self._full_reset
599
+
600
+ if self._user_prompt is not None:
601
+ context_dict["user_prompt"] = self._user_prompt
602
+
603
+ if self._isolated:
604
+ context_dict["isolated"] = self._isolated
605
+
606
+ # Add context prompt - use POM structure if sections exist, otherwise use string
607
+ if self._prompt_sections:
608
+ # Use structured POM format
609
+ context_dict["pom"] = self._prompt_sections
610
+ elif self._prompt_text is not None:
611
+ # Use string format
612
+ context_dict["prompt"] = self._prompt_text
613
+
614
+ # Add enter and exit fillers if defined
615
+ if self._enter_fillers is not None:
616
+ context_dict["enter_fillers"] = self._enter_fillers
617
+
618
+ if self._exit_fillers is not None:
619
+ context_dict["exit_fillers"] = self._exit_fillers
202
620
 
203
621
  return context_dict
204
622
 
@@ -257,7 +257,6 @@ class DataMap:
257
257
 
258
258
  Args:
259
259
  foreach_config: Either:
260
- - String: JSON path to array in response (deprecated, kept for compatibility)
261
260
  - Dict: Foreach configuration with keys:
262
261
  - input_key: Key in API response containing the array
263
262
  - output_key: Name for the built string variable
@@ -278,20 +277,7 @@ class DataMap:
278
277
  if not self._webhooks:
279
278
  raise ValueError("Must add webhook before setting foreach")
280
279
 
281
- if isinstance(foreach_config, str):
282
- # Legacy support - convert string to basic foreach config
283
- # Extract the key from "${response.key}" format if present
284
- if foreach_config.startswith("${response.") and foreach_config.endswith("}"):
285
- key = foreach_config[12:-1] # Remove "${response." and "}"
286
- else:
287
- key = foreach_config
288
-
289
- foreach_data = {
290
- "input_key": key,
291
- "output_key": "foreach_output",
292
- "append": "${this}"
293
- }
294
- else:
280
+ if isinstance(foreach_config, dict):
295
281
  # New format - validate required keys
296
282
  required_keys = ["input_key", "output_key", "append"]
297
283
  missing_keys = [key for key in required_keys if key not in foreach_config]
@@ -299,6 +285,8 @@ class DataMap:
299
285
  raise ValueError(f"foreach config missing required keys: {missing_keys}")
300
286
 
301
287
  foreach_data = foreach_config
288
+ else:
289
+ raise ValueError("foreach_config must be a dictionary")
302
290
 
303
291
  self._webhooks[-1]["foreach"] = foreach_data
304
292
  return self