claude-smart 0.2.42 → 0.2.43

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 (392) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +1 -1
  3. package/package.json +9 -3
  4. package/plugin/.claude-plugin/plugin.json +9 -3
  5. package/plugin/.codex-plugin/plugin.json +1 -1
  6. package/plugin/README.md +2 -2
  7. package/plugin/pyproject.toml +2 -2
  8. package/plugin/scripts/_lib.sh +91 -0
  9. package/plugin/scripts/backend-service.sh +1 -0
  10. package/plugin/scripts/cli.sh +1 -0
  11. package/plugin/scripts/codex-hook.js +72 -4
  12. package/plugin/scripts/dashboard-build.sh +1 -0
  13. package/plugin/scripts/dashboard-service.sh +1 -0
  14. package/plugin/scripts/hook_entry.sh +1 -0
  15. package/plugin/scripts/smart-install.sh +1 -0
  16. package/plugin/src/claude_smart/context_format.py +11 -12
  17. package/plugin/src/claude_smart/cs_cite.py +26 -12
  18. package/plugin/src/claude_smart/ids.py +13 -5
  19. package/plugin/uv.lock +1 -1
  20. package/plugin/vendor/reflexio/.env.example +53 -0
  21. package/plugin/vendor/reflexio/LICENSE +201 -0
  22. package/plugin/vendor/reflexio/README.md +338 -0
  23. package/plugin/vendor/reflexio/pyproject.toml +271 -0
  24. package/plugin/vendor/reflexio/reflexio/README.md +184 -0
  25. package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
  26. package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
  27. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
  28. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
  29. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
  30. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
  31. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
  32. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
  33. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
  34. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
  35. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
  36. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
  37. package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
  38. package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
  39. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
  40. package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
  41. package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
  42. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +266 -0
  43. package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
  44. package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
  45. package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
  46. package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
  47. package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
  48. package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
  49. package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
  50. package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
  51. package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
  52. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
  53. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
  54. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +961 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
  59. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
  60. package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
  61. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
  62. package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
  63. package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
  64. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
  65. package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
  66. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
  67. package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
  68. package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
  69. package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
  70. package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
  71. package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
  145. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
  146. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
  147. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
  148. package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
  149. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
  151. package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
  152. package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
  153. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
  154. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
  155. package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
  156. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
  157. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
  158. package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
  159. package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
  160. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
  161. package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
  162. package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
  164. package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
  165. package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
  166. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
  167. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1103 -0
  168. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
  169. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
  170. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
  171. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
  172. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +766 -0
  173. package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
  174. package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
  175. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
  176. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
  177. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
  178. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
  179. package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +280 -0
  180. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
  181. package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
  182. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
  183. package/plugin/vendor/reflexio/reflexio/server/README.md +616 -0
  184. package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
  185. package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
  186. package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
  187. package/plugin/vendor/reflexio/reflexio/server/api.py +2714 -0
  188. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
  189. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
  190. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
  191. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
  192. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +540 -0
  193. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
  194. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
  195. package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
  196. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
  197. package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
  198. package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
  199. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +110 -0
  200. package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
  201. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1595 -0
  202. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +112 -0
  203. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +469 -0
  204. package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
  205. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
  206. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
  207. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +210 -0
  208. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
  209. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +255 -0
  210. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
  211. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +177 -0
  212. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
  213. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +699 -0
  214. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
  215. package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
  216. package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
  217. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
  218. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
  219. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
  220. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
  221. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
  222. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
  223. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
  224. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
  225. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
  226. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
  227. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
  228. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
  229. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
  230. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
  231. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
  232. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
  233. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
  234. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
  235. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
  236. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
  237. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
  238. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
  239. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
  240. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
  241. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
  242. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +279 -0
  243. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
  244. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
  245. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
  246. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
  247. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
  248. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +127 -0
  249. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
  250. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
  251. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
  252. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
  253. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
  254. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
  255. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
  256. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
  257. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
  258. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
  259. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
  260. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
  261. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
  262. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
  263. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
  264. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
  265. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
  266. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
  267. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
  268. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
  269. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
  270. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
  271. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
  272. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
  273. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
  274. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
  275. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
  281. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
  282. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +340 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1626 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
  289. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
  290. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
  292. package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
  296. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
  297. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +351 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
  311. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +468 -0
  312. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
  313. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +777 -0
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
  315. package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +91 -0
  316. package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
  317. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +689 -0
  318. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
  319. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
  320. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
  321. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +960 -0
  322. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
  323. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
  324. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
  325. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
  326. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
  327. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
  328. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
  329. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
  330. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
  331. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
  332. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
  333. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
  334. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
  335. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
  336. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
  337. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
  338. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +741 -0
  339. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
  340. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +734 -0
  341. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
  342. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
  343. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
  344. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +800 -0
  345. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
  346. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
  347. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +70 -0
  348. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
  349. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +671 -0
  350. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
  351. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
  352. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
  353. package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
  354. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
  355. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
  356. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
  357. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
  358. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1253 -0
  359. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
  360. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
  361. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
  362. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
  363. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
  364. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
  365. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
  366. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
  367. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
  368. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
  369. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +372 -0
  370. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
  371. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
  372. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
  373. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
  374. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
  375. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
  376. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
  377. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
  378. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
  379. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +568 -0
  380. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
  381. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
  382. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
  383. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
  384. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
  385. package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
  386. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
  387. package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
  388. package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
  389. package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
  390. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
  391. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
  392. package/plugin/vendor/reflexio/reflexio/test_support/skip_decorators.py +43 -0
@@ -0,0 +1,671 @@
1
+ """
2
+ Utils for service layer
3
+ """
4
+
5
+ import ast
6
+ import json
7
+ import logging
8
+ import re
9
+ from dataclasses import dataclass
10
+ from datetime import UTC, datetime
11
+ from typing import Any
12
+
13
+ from reflexio.cli.log_format import LLM_IO_LOG_FILE, next_llm_entry_id
14
+ from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
15
+ from reflexio.models.api_schema.service_schemas import (
16
+ Interaction,
17
+ UserActionType,
18
+ )
19
+ from reflexio.server import LLM_PROMPT_LEVEL
20
+ from reflexio.server.prompt.prompt_manager import PromptManager
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Custom log level for model responses (between INFO=20 and WARNING=30)
25
+ # Already registered in server/__init__.py; import the numeric constant only.
26
+ MODEL_RESPONSE_LEVEL = 25
27
+
28
+
29
+ def _format_response_for_logging(response: Any) -> Any:
30
+ """Render ``ToolCallingChatResponse`` with pretty tool_calls; pass others through.
31
+
32
+ The dataclass's ``__repr__`` (which ``%s`` formatting falls back to)
33
+ prints each tool_call as an opaque object handle
34
+ (``<ChatCompletionMessageToolCall object at 0x…>``), erasing the
35
+ tool name + arguments the model emitted. This helper detects that
36
+ one case and renders a multi-line human-readable form using the
37
+ same ``_format_tool_calls`` helper the request-side formatter uses.
38
+
39
+ All other response types (strings, Pydantic ``BaseModel`` instances
40
+ from classic extractors / deduplicators / aggregators) fall through
41
+ unchanged so the existing log shape is preserved.
42
+
43
+ Lazy-imports ``ToolCallingChatResponse`` to avoid a circular
44
+ ``service_utils`` ↔ ``litellm_client`` dependency at module load.
45
+ """
46
+ try:
47
+ from reflexio.server.llm.litellm_client import ToolCallingChatResponse
48
+ except Exception: # noqa: BLE001 - fall back gracefully if the import fails
49
+ return response
50
+
51
+ if not isinstance(response, ToolCallingChatResponse):
52
+ return response
53
+
54
+ lines = [
55
+ f"ToolCallingChatResponse(finish_reason={response.finish_reason!r}):",
56
+ f" content: {response.content!r}",
57
+ ]
58
+ if response.tool_calls:
59
+ lines.extend(_format_tool_calls(response.tool_calls))
60
+ else:
61
+ lines.append(" tool_calls: []")
62
+ return "\n".join(lines)
63
+
64
+
65
+ def log_model_response(
66
+ target_logger: logging.Logger, label: str, response: Any
67
+ ) -> None:
68
+ """
69
+ Log an LLM model response. Full response goes to the log file at LLM_PROMPT level.
70
+ A one-line summary goes to the console at MODEL_RESPONSE level.
71
+
72
+ Args:
73
+ target_logger (logging.Logger): The logger instance to use
74
+ label (str): Descriptive label for the response (e.g. "Profile updates model response")
75
+ response (Any): The model response to log
76
+ """
77
+ entry_id = next_llm_entry_id()
78
+ # Special-case ToolCallingChatResponse so tool_calls render as
79
+ # id/name/arguments instead of opaque ``<… object at 0x…>`` handles.
80
+ formatted = _format_response_for_logging(response)
81
+ # Full response to llm_io.log only (level 15 < INFO 20, so console ignores it)
82
+ target_logger.log(
83
+ LLM_PROMPT_LEVEL,
84
+ "[#%d] %s: %s",
85
+ entry_id,
86
+ label,
87
+ formatted,
88
+ extra={"entry_id": entry_id, "label": label},
89
+ )
90
+ # One-line summary to console
91
+ response_type = type(response).__name__
92
+ target_logger.log(
93
+ MODEL_RESPONSE_LEVEL,
94
+ "[MODEL] %s: %s — %s [#%d]",
95
+ label,
96
+ response_type,
97
+ LLM_IO_LOG_FILE,
98
+ entry_id,
99
+ )
100
+
101
+
102
+ def log_llm_messages(
103
+ target_logger: logging.Logger,
104
+ label: str,
105
+ messages: list[dict[str, Any]],
106
+ ) -> None:
107
+ """
108
+ Log LLM prompt messages. Full content goes to the log file at LLM_PROMPT level.
109
+ A one-line summary goes to the console at INFO level.
110
+
111
+ Args:
112
+ target_logger (logging.Logger): The logger instance to use
113
+ label (str): Descriptive label (e.g. "Profile extraction")
114
+ messages (list[dict[str, Any]]): The LLM messages to log
115
+ """
116
+ entry_id = next_llm_entry_id()
117
+ # Full messages to llm_io.log only
118
+ formatted = format_messages_for_logging(messages)
119
+ target_logger.log(
120
+ LLM_PROMPT_LEVEL,
121
+ "%s messages:\n%s",
122
+ label,
123
+ formatted,
124
+ extra={"entry_id": entry_id, "label": label},
125
+ )
126
+ # Summary to console
127
+ total_chars = sum(len(str(msg.get("content", ""))) for msg in messages)
128
+ target_logger.info(
129
+ "[LLM] %s: %d msgs, ~%d chars — %s [#%d]",
130
+ label,
131
+ len(messages),
132
+ total_chars,
133
+ LLM_IO_LOG_FILE,
134
+ entry_id,
135
+ )
136
+
137
+
138
+ @dataclass
139
+ class PromptConfig:
140
+ """Configuration for a prompt to be rendered.
141
+
142
+ Attributes:
143
+ prompt_id: The ID of the prompt template to render
144
+ variables: Dictionary of variables to pass to the prompt template
145
+ """
146
+
147
+ prompt_id: str
148
+ variables: dict[str, any] # type: ignore[reportGeneralTypeIssues]
149
+
150
+
151
+ @dataclass
152
+ class MessageConstructionConfig:
153
+ """Configuration for constructing LLM messages from interactions.
154
+
155
+ Attributes:
156
+ prompt_manager: The prompt manager to use for rendering prompts
157
+ system_prompt_config: Configuration for the system message (before interactions)
158
+ user_prompt_config: Configuration for the user message (after interactions)
159
+ """
160
+
161
+ prompt_manager: PromptManager
162
+ system_prompt_config: PromptConfig | None = None
163
+ user_prompt_config: PromptConfig | None = None
164
+
165
+
166
+ def format_interactions_to_history_string(interactions: list[Interaction]) -> str:
167
+ """
168
+ Format a list of interactions into a single string representing the interaction history.
169
+
170
+ Each interaction is formatted as:
171
+ - Text content: "{role}: {content}"
172
+ - User actions: "{role}: {action} {action_description}"
173
+
174
+ Args:
175
+ interactions (list[Interaction]): List of interactions to format
176
+
177
+ Returns:
178
+ str: A formatted string representing the interaction history, with interactions separated by newlines.
179
+ Returns empty string if no interactions are provided.
180
+
181
+ Example:
182
+ >>> interactions = [
183
+ ... Interaction(role="user", content="I love sushi", user_action=UserActionType.NONE),
184
+ ... Interaction(role="user", content="", user_action=UserActionType.CLICK, user_action_description="menu item")
185
+ ... ]
186
+ >>> result = format_interactions_to_history_string(interactions)
187
+ >>> print(result)
188
+ user: I love sushi
189
+ user: click menu item
190
+ """
191
+ formatted_interactions = []
192
+ for interaction in interactions:
193
+ # Add text content with tools_used prefix if present
194
+ if interaction.content:
195
+ if interaction.tools_used:
196
+ tool_prefix = " ".join(
197
+ f"[used tool: {t.tool_name}({json.dumps(t.tool_data)})]"
198
+ for t in interaction.tools_used
199
+ )
200
+ formatted_interactions.append(
201
+ f"{interaction.role}: ```{tool_prefix} {interaction.content}```"
202
+ )
203
+ else:
204
+ formatted_interactions.append(
205
+ f"{interaction.role}: ```{interaction.content}```"
206
+ )
207
+
208
+ # Add user action
209
+ if interaction.user_action != UserActionType.NONE:
210
+ formatted_interactions.append(
211
+ f"{interaction.role}: ```{interaction.user_action.value} {interaction.user_action_description}```"
212
+ )
213
+
214
+ return "\n".join(formatted_interactions)
215
+
216
+
217
+ def format_sessions_to_history_string(
218
+ sessions: list[RequestInteractionDataModel],
219
+ ) -> str:
220
+ """
221
+ Format interactions grouped by session into a string.
222
+
223
+ All RequestInteractionDataModel objects with the same session_id are consolidated
224
+ under a single header. Within each consolidated group, interactions are ordered by
225
+ their id in ascending order (smaller to bigger).
226
+
227
+ Args:
228
+ sessions (list[RequestInteractionDataModel]): List of request interaction data models to format
229
+
230
+ Returns:
231
+ str: A formatted string with interactions grouped by session.
232
+ Returns empty string if no sessions are provided.
233
+
234
+ Example:
235
+ >>> # Given sessions with interactions (multiple requests in same session)
236
+ >>> result = format_sessions_to_history_string(sessions)
237
+ >>> print(result)
238
+ === Session: session_1 ===
239
+ user: Hello, I need help
240
+ assistant: How can I assist you?
241
+ user: Thanks for the help
242
+ assistant: You're welcome!
243
+
244
+ === Session: session_2 ===
245
+ user: I love sushi
246
+ assistant: That's great!
247
+ """
248
+ if not sessions:
249
+ return ""
250
+
251
+ # Group all RequestInteractionDataModel objects by their session_id
252
+ grouped_by_name: dict[str, list[RequestInteractionDataModel]] = {}
253
+ for request_interaction in sessions:
254
+ if request_interaction.session_id not in grouped_by_name:
255
+ grouped_by_name[request_interaction.session_id] = []
256
+ grouped_by_name[request_interaction.session_id].append(request_interaction)
257
+
258
+ # Sort each group's requests by created_at timestamp
259
+ for group_name in grouped_by_name:
260
+ grouped_by_name[group_name] = sorted(
261
+ grouped_by_name[group_name], key=lambda g: g.request.created_at
262
+ )
263
+
264
+ # Sort group names by the earliest request timestamp in each group
265
+ sorted_group_names = sorted(
266
+ grouped_by_name.keys(),
267
+ key=lambda name: grouped_by_name[name][0].request.created_at,
268
+ )
269
+
270
+ formatted_groups = []
271
+ for group_name in sorted_group_names:
272
+ # Format header with session name AND its earliest interaction date.
273
+ # Without the date, downstream extraction agents have no anchor for
274
+ # resolving relative-time references in the conversation
275
+ # ("X weeks ago", "yesterday", "two days before the wedding") —
276
+ # they fall back to real-world `now()` and encode every event as
277
+ # today's date, breaking temporal-reasoning queries.
278
+ #
279
+ # We use the earliest *interaction* timestamp, not request.created_at,
280
+ # because Request.created_at defaults to `now()` on construction —
281
+ # only interactions reliably carry the conversation's true wall-clock
282
+ # time when the publisher provides it.
283
+ all_ts: list[int] = [
284
+ i.created_at
285
+ for ri in grouped_by_name[group_name]
286
+ for i in ri.interactions
287
+ if i.created_at
288
+ ]
289
+ first_ts = min(all_ts) if all_ts else 0
290
+ if first_ts:
291
+ try:
292
+ session_date_iso = datetime.fromtimestamp(first_ts, tz=UTC).strftime(
293
+ "%Y-%m-%d"
294
+ )
295
+ group_header = (
296
+ f"=== Session: {group_name} (date: {session_date_iso}) ==="
297
+ )
298
+ except (OverflowError, OSError, ValueError):
299
+ group_header = f"=== Session: {group_name} ==="
300
+ else:
301
+ group_header = f"=== Session: {group_name} ==="
302
+
303
+ # Combine all interactions from all requests in this session
304
+ all_interactions = []
305
+ for request_interaction in grouped_by_name[group_name]:
306
+ all_interactions.extend(request_interaction.interactions)
307
+
308
+ # Sort interactions by id ascending (smaller to bigger)
309
+ all_interactions = sorted(all_interactions, key=lambda i: i.interaction_id)
310
+
311
+ # Format combined interactions
312
+ group_interactions = format_interactions_to_history_string(all_interactions)
313
+
314
+ # Combine header and interactions
315
+ formatted_groups.append(f"{group_header}\n{group_interactions}")
316
+
317
+ return "\n\n".join(formatted_groups)
318
+
319
+
320
+ def extract_interactions_from_request_interaction_data_models(
321
+ request_interaction_data_models: list[RequestInteractionDataModel],
322
+ ) -> list[Interaction]:
323
+ """
324
+ Extract a flat list of interactions from request interaction groups.
325
+
326
+ This is useful for backward compatibility with services that still expect
327
+ a flat list of interactions.
328
+
329
+ Args:
330
+ request_interaction_data_models (list[RequestInteractionDataModel]): List of request interaction data models
331
+
332
+ Returns:
333
+ list[Interaction]: Flat list of all interactions from all groups
334
+ """
335
+ interactions = []
336
+ for request_interaction_data_model in request_interaction_data_models:
337
+ interactions.extend(request_interaction_data_model.interactions)
338
+ return interactions
339
+
340
+
341
+ def construct_messages_from_interactions(
342
+ interactions: list[Interaction],
343
+ config: MessageConstructionConfig,
344
+ ) -> list[dict]:
345
+ """
346
+ Construct a list of LLM messages from interactions with custom prompts.
347
+
348
+ This function creates a structured message sequence:
349
+ 1. Optional system message (using system_prompt_config)
350
+ 2. Single user message containing:
351
+ - Formatted interactions (text content and user actions)
352
+ - User prompt (if configured)
353
+ - Last interaction's image (if present)
354
+
355
+ Args:
356
+ interactions: List of interactions to convert into messages
357
+ config: Configuration for message construction including prompt configs
358
+
359
+ Returns:
360
+ list[dict]: List of messages ready to be sent to the LLM API.
361
+ Each message is a dict with 'role' and 'content' keys.
362
+
363
+ Example:
364
+ >>> system_config = PromptConfig(
365
+ ... prompt_id="profile_update_instruction_start",
366
+ ... variables={"agent_context_prompt": "...", "context_prompt": "..."}
367
+ ... )
368
+ >>> user_config = PromptConfig(
369
+ ... prompt_id="profile_update_main",
370
+ ... variables={"extraction_definition_prompt": "...", "existing_profiles": "..."}
371
+ ... )
372
+ >>> config = MessageConstructionConfig(
373
+ ... prompt_manager=prompt_manager,
374
+ ... system_prompt_config=system_config,
375
+ ... user_prompt_config=user_config
376
+ ... )
377
+ >>> messages = construct_messages_from_interactions(interactions, config)
378
+ """
379
+ messages = []
380
+
381
+ # Add system message if configured
382
+ if config.system_prompt_config:
383
+ system_content = config.prompt_manager.render_prompt(
384
+ config.system_prompt_config.prompt_id,
385
+ config.system_prompt_config.variables,
386
+ )
387
+ messages.append({"role": "system", "content": system_content})
388
+
389
+ # Build combined user message content
390
+ combined_content = []
391
+
392
+ # Add user prompt if configured
393
+ if config.user_prompt_config:
394
+ user_prompt_content = config.prompt_manager.render_prompt(
395
+ config.user_prompt_config.prompt_id,
396
+ config.user_prompt_config.variables,
397
+ )
398
+ combined_content.append({"type": "text", "text": user_prompt_content})
399
+
400
+ # Add last interaction's image (if present)
401
+ if interactions:
402
+ last_interaction = interactions[-1]
403
+
404
+ # Add image URL (priority over base64)
405
+ if last_interaction.interacted_image_url:
406
+ combined_content.append(
407
+ {
408
+ "type": "image_url",
409
+ "image_url": {"url": last_interaction.interacted_image_url},
410
+ }
411
+ )
412
+ # Add base64-encoded image if no URL
413
+ elif last_interaction.image_encoding:
414
+ combined_content.append(
415
+ {
416
+ "type": "image_url",
417
+ "image_url": {
418
+ "url": f"data:image/jpeg;base64,{last_interaction.image_encoding}"
419
+ },
420
+ }
421
+ )
422
+
423
+ # Add combined user message if there's any content
424
+ if combined_content:
425
+ # Flatten to plain string when all blocks are text-only (no images).
426
+ # Content-block format combined with structured output (response_format)
427
+ # causes significantly slower OpenAI API responses and timeouts.
428
+ all_text = all(block.get("type") == "text" for block in combined_content)
429
+ if all_text:
430
+ content = "\n\n".join(block["text"] for block in combined_content)
431
+ else:
432
+ content = combined_content
433
+ messages.append({"role": "user", "content": content})
434
+
435
+ return messages
436
+
437
+
438
+ def extract_json_from_string(text: str) -> dict:
439
+ """
440
+ Extract JSON from a string, handling both JSON-style and Python-style booleans.
441
+
442
+ This function attempts to extract JSON from text in the following order:
443
+ 1. From code blocks (```json...```)
444
+ 2. From content between first { and last }
445
+
446
+ It also handles Python-style boolean values (True/False/None) by converting
447
+ them to JSON-style (true/false/null) before parsing, and falls back to
448
+ Python literal parsing for dict-like responses that use single quotes.
449
+
450
+ Args:
451
+ text (str): string to extract JSON from
452
+
453
+ Returns:
454
+ dict: JSON object, or empty dict if parsing fails
455
+ """
456
+
457
+ def normalize_json_string(json_str: str) -> str:
458
+ """Convert Python-style syntax to JSON-friendly syntax.
459
+
460
+ Handles:
461
+ - Python booleans (True/False/None) -> JSON (true/false/null)
462
+ """
463
+ # Replace Python boolean/null values with JSON equivalents
464
+ # Use word boundaries to avoid replacing parts of strings
465
+ json_str = re.sub(r"\bTrue\b", "true", json_str)
466
+ json_str = re.sub(r"\bFalse\b", "false", json_str)
467
+ json_str = re.sub(r"\bNone\b", "null", json_str)
468
+
469
+ return json_str # noqa: RET504
470
+
471
+ def fix_unescaped_inner_quotes(json_str: str) -> str:
472
+ """
473
+ Attempt to fix common cases where apostrophes are returned as double quotes.
474
+
475
+ Many LLMs occasionally substitute `customer's` with `customer"s`, leaving the JSON
476
+ invalid because the double quote isn't escaped. We treat double quotes that sit
477
+ between two word characters as apostrophes.
478
+ """
479
+ return re.sub(r"(?<=\w)\"(?=\w)", "'", json_str)
480
+
481
+ def parse_json_candidate(json_str: str) -> tuple[dict | None, str | None]:
482
+ """
483
+ Try to parse a JSON candidate string using multiple strategies.
484
+
485
+ Strategies:
486
+ 1. Direct json.loads
487
+ 2. json.loads after normalizing Python syntax
488
+ 3. ast.literal_eval as a fallback for Python-style dicts
489
+ """
490
+ candidates = [json_str]
491
+ last_error: str | None = None
492
+
493
+ normalized_json_str = normalize_json_string(json_str)
494
+ if normalized_json_str != json_str:
495
+ candidates.append(normalized_json_str)
496
+
497
+ repaired_json_str = fix_unescaped_inner_quotes(normalized_json_str)
498
+ if repaired_json_str not in candidates:
499
+ candidates.append(repaired_json_str)
500
+
501
+ for candidate in candidates:
502
+ try:
503
+ parsed = json.loads(candidate)
504
+ if isinstance(parsed, dict):
505
+ return parsed, None
506
+ except json.JSONDecodeError as err: # noqa: PERF203
507
+ last_error = str(err)
508
+ continue
509
+
510
+ for candidate in candidates:
511
+ try:
512
+ parsed = ast.literal_eval(candidate)
513
+ if isinstance(parsed, dict):
514
+ return parsed, None
515
+ except (ValueError, SyntaxError) as err: # noqa: PERF203
516
+ last_error = str(err)
517
+ continue
518
+
519
+ return None, last_error
520
+
521
+ # Pattern to match content between triple backticks and json keyword
522
+ # Allow optional whitespace around json keyword and content
523
+ pattern = r"```json\s*(.*?)\s*```"
524
+
525
+ # Find the match using regex
526
+ match = re.search(pattern, text, re.DOTALL)
527
+
528
+ if match:
529
+ json_str = match.group(1)
530
+ parsed_json, error_message = parse_json_candidate(json_str)
531
+ if parsed_json is not None:
532
+ return parsed_json
533
+ if error_message:
534
+ logger.warning("Failed to parse JSON from code block: %s", error_message)
535
+
536
+ # Try to find JSON content between first { and last }
537
+ start_idx = text.find("{")
538
+ end_idx = text.rfind("}")
539
+ if start_idx != -1 and end_idx != -1:
540
+ json_str = text[start_idx : end_idx + 1]
541
+ parsed_json, error_message = parse_json_candidate(json_str)
542
+ if parsed_json is not None:
543
+ return parsed_json
544
+ if error_message:
545
+ logger.warning("Failed to parse JSON from braces: %s", error_message)
546
+
547
+ return {}
548
+
549
+
550
+ def _format_tool_calls(tool_calls: list[Any]) -> list[str]:
551
+ """Render an assistant message's ``tool_calls`` list for the log.
552
+
553
+ Accepts either the OpenAI SDK object shape (with ``.function.name`` /
554
+ ``.function.arguments`` attrs) or the dict shape that pass-through
555
+ serialisation may produce. Returns one indented line per call with the
556
+ tool_call_id, the tool name, and the parsed arguments — so the log
557
+ reader can correlate each tool_call with its tool-role response.
558
+ """
559
+ lines: list[str] = [" tool_calls:"]
560
+ for tc in tool_calls:
561
+ # Extract id, name, arguments from either attribute or mapping shape.
562
+ tc_id = getattr(tc, "id", None) or (
563
+ tc.get("id") if isinstance(tc, dict) else None
564
+ )
565
+ fn = getattr(tc, "function", None)
566
+ if fn is not None:
567
+ name = getattr(fn, "name", None)
568
+ args_raw = getattr(fn, "arguments", None)
569
+ elif isinstance(tc, dict):
570
+ fn_dict = tc.get("function", {}) or {}
571
+ name = fn_dict.get("name") if isinstance(fn_dict, dict) else None
572
+ args_raw = fn_dict.get("arguments") if isinstance(fn_dict, dict) else None
573
+ else:
574
+ name = None
575
+ args_raw = None
576
+
577
+ # arguments comes through as a JSON string from the provider — parse
578
+ # for readability, fall back to raw text on malformed JSON.
579
+ parsed_args: Any
580
+ if isinstance(args_raw, str):
581
+ try:
582
+ parsed_args = json.loads(args_raw)
583
+ except json.JSONDecodeError:
584
+ parsed_args = args_raw
585
+ else:
586
+ parsed_args = args_raw
587
+
588
+ lines.append(f" - id: {tc_id}")
589
+ lines.append(f" name: {name}")
590
+ # Logging path must never raise — fall back to repr() on
591
+ # non-serializable argument objects (datetime, sets, custom
592
+ # types, etc.) so a logging call can't take down a request.
593
+ try:
594
+ rendered_args = json.dumps(parsed_args)
595
+ except (TypeError, ValueError):
596
+ rendered_args = repr(parsed_args)
597
+ lines.append(f" arguments: {rendered_args}")
598
+ return lines
599
+
600
+
601
+ def format_messages_for_logging(messages: list[dict[str, Any]]) -> str:
602
+ """
603
+ Format messages for logging with proper newlines in text content.
604
+
605
+ Args:
606
+ messages: List of message dictionaries with role and content
607
+
608
+ Returns:
609
+ str: Formatted string representation of messages with newlines preserved
610
+ """
611
+ formatted_parts = []
612
+ for i, msg in enumerate(messages):
613
+ formatted_parts.append(f"Message {i + 1}:")
614
+ formatted_parts.append(f" role: {msg.get('role', 'unknown')}")
615
+
616
+ # Tool-role messages carry a ``tool_call_id`` that correlates them
617
+ # back to the assistant's emitted call — render it so readers can
618
+ # reconstruct which response answered which call.
619
+ tool_call_id = msg.get("tool_call_id")
620
+ if tool_call_id is not None:
621
+ formatted_parts.append(f" tool_call_id: {tool_call_id}")
622
+
623
+ content = msg.get("content", "")
624
+
625
+ if isinstance(content, str):
626
+ # Simple string content - preserve newlines
627
+ formatted_parts.append(" content:")
628
+ # Indent each line of content
629
+ formatted_parts.extend(f" {line}" for line in content.split("\n"))
630
+ elif isinstance(content, list):
631
+ # Multimodal content (list of objects)
632
+ formatted_parts.append(" content:")
633
+ for item in content:
634
+ if isinstance(item, dict):
635
+ item_type = item.get("type", "unknown")
636
+ if item_type == "text":
637
+ text_content = item.get("text", "")
638
+ formatted_parts.append(f" type: {item_type}")
639
+ formatted_parts.append(" text:")
640
+ # Indent each line of text
641
+ formatted_parts.extend(
642
+ f" {line}" for line in text_content.split("\n")
643
+ )
644
+ else:
645
+ # For non-text content, use JSON representation
646
+ formatted_parts.append(f" {json.dumps(item, indent=4)}")
647
+ else:
648
+ formatted_parts.append(f" {json.dumps(item, indent=4)}")
649
+ else:
650
+ # Fallback to JSON for other types
651
+ formatted_parts.append(f" content: {json.dumps(content, indent=4)}")
652
+
653
+ # Assistant messages with tool_calls must render the call list —
654
+ # otherwise the log shows ``content: null`` with no visibility into
655
+ # which tools the model invoked. Classic extraction doesn't use
656
+ # tool-calling, but the agentic pipeline relies on it heavily.
657
+ tool_calls = msg.get("tool_calls")
658
+ if tool_calls:
659
+ formatted_parts.extend(_format_tool_calls(tool_calls))
660
+
661
+ formatted_parts.append("") # Empty line between messages
662
+
663
+ return "\n".join(formatted_parts)
664
+
665
+
666
+ # Example usage
667
+ if __name__ == "__main__":
668
+ test_string = """'Based on the existing profiles (which are currently empty) and the new interaction indicating a preference for sushi, the update would involve adding a new profile that reflects this preference. Here's the JSON format for the updates:\n\n```json\n{\n "add_profile": ["I like sushi"],\n "delete_profile": []\n}\n```'"""
669
+
670
+ result = extract_json_from_string(test_string)
671
+ print("Extracted JSON:", result)
@@ -0,0 +1 @@
1
+ """F1 per-turn shadow comparison service."""