devsper 2.1.6__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 (375) hide show
  1. devsper/__init__.py +14 -0
  2. devsper/agents/a2a/__init__.py +27 -0
  3. devsper/agents/a2a/client.py +126 -0
  4. devsper/agents/a2a/discovery.py +24 -0
  5. devsper/agents/a2a/server.py +128 -0
  6. devsper/agents/a2a/tool_adapter.py +68 -0
  7. devsper/agents/a2a/types.py +49 -0
  8. devsper/agents/agent.py +602 -0
  9. devsper/agents/critic.py +80 -0
  10. devsper/agents/message_bus.py +124 -0
  11. devsper/agents/roles.py +181 -0
  12. devsper/agents/run_agent.py +78 -0
  13. devsper/analytics/__init__.py +5 -0
  14. devsper/analytics/tool_analytics.py +78 -0
  15. devsper/audit/__init__.py +5 -0
  16. devsper/audit/logger.py +214 -0
  17. devsper/bus/__init__.py +29 -0
  18. devsper/bus/backends/__init__.py +5 -0
  19. devsper/bus/backends/base.py +38 -0
  20. devsper/bus/backends/memory.py +55 -0
  21. devsper/bus/backends/redis.py +146 -0
  22. devsper/bus/message.py +56 -0
  23. devsper/bus/schema_version.py +3 -0
  24. devsper/bus/topics.py +19 -0
  25. devsper/cache/__init__.py +6 -0
  26. devsper/cache/embedding_index.py +98 -0
  27. devsper/cache/hashing.py +24 -0
  28. devsper/cache/store.py +153 -0
  29. devsper/cache/task_cache.py +191 -0
  30. devsper/cli/__init__.py +6 -0
  31. devsper/cli/commands/reg.py +733 -0
  32. devsper/cli/github_oauth.py +157 -0
  33. devsper/cli/init.py +637 -0
  34. devsper/cli/main.py +2956 -0
  35. devsper/cli/run_progress.py +103 -0
  36. devsper/cli/ui/__init__.py +65 -0
  37. devsper/cli/ui/components.py +94 -0
  38. devsper/cli/ui/errors.py +104 -0
  39. devsper/cli/ui/logging.py +120 -0
  40. devsper/cli/ui/onboarding.py +102 -0
  41. devsper/cli/ui/progress.py +43 -0
  42. devsper/cli/ui/run_view.py +308 -0
  43. devsper/cli/ui/theme.py +40 -0
  44. devsper/cluster/__init__.py +29 -0
  45. devsper/cluster/election.py +84 -0
  46. devsper/cluster/local.py +97 -0
  47. devsper/cluster/node_info.py +77 -0
  48. devsper/cluster/registry.py +71 -0
  49. devsper/cluster/router.py +117 -0
  50. devsper/cluster/state_backend.py +105 -0
  51. devsper/compliance/__init__.py +5 -0
  52. devsper/compliance/pii.py +147 -0
  53. devsper/config/__init__.py +52 -0
  54. devsper/config/config_loader.py +121 -0
  55. devsper/config/defaults.py +77 -0
  56. devsper/config/resolver.py +342 -0
  57. devsper/config/schema.py +237 -0
  58. devsper/credentials/__init__.py +19 -0
  59. devsper/credentials/cli.py +197 -0
  60. devsper/credentials/migration.py +124 -0
  61. devsper/credentials/store.py +142 -0
  62. devsper/dashboard/__init__.py +9 -0
  63. devsper/dashboard/dashboard.py +87 -0
  64. devsper/dev/__init__.py +25 -0
  65. devsper/dev/builder.py +195 -0
  66. devsper/dev/debugger.py +95 -0
  67. devsper/dev/repo_index.py +138 -0
  68. devsper/dev/sandbox.py +203 -0
  69. devsper/dev/scaffold.py +122 -0
  70. devsper/embeddings/__init__.py +5 -0
  71. devsper/embeddings/service.py +36 -0
  72. devsper/explainability/__init__.py +14 -0
  73. devsper/explainability/decision_tree.py +104 -0
  74. devsper/explainability/rationale.py +38 -0
  75. devsper/explainability/simulation.py +56 -0
  76. devsper/hitl/__init__.py +13 -0
  77. devsper/hitl/approval.py +160 -0
  78. devsper/hitl/escalation.py +95 -0
  79. devsper/intelligence/__init__.py +9 -0
  80. devsper/intelligence/adaptation.py +88 -0
  81. devsper/intelligence/analysis/__init__.py +19 -0
  82. devsper/intelligence/analysis/analyzer.py +71 -0
  83. devsper/intelligence/analysis/cost_estimator.py +66 -0
  84. devsper/intelligence/analysis/formatter.py +103 -0
  85. devsper/intelligence/analysis/run_report.py +402 -0
  86. devsper/intelligence/learning_engine.py +92 -0
  87. devsper/intelligence/strategies/__init__.py +23 -0
  88. devsper/intelligence/strategies/base.py +14 -0
  89. devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
  90. devsper/intelligence/strategies/data_science_strategy.py +33 -0
  91. devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
  92. devsper/intelligence/strategies/experiment_strategy.py +33 -0
  93. devsper/intelligence/strategies/research_strategy.py +34 -0
  94. devsper/intelligence/strategy_selector.py +84 -0
  95. devsper/intelligence/synthesis.py +132 -0
  96. devsper/intelligence/task_optimizer.py +92 -0
  97. devsper/knowledge/__init__.py +5 -0
  98. devsper/knowledge/extractor.py +204 -0
  99. devsper/knowledge/knowledge_graph.py +184 -0
  100. devsper/knowledge/query.py +285 -0
  101. devsper/memory/__init__.py +35 -0
  102. devsper/memory/consolidation.py +138 -0
  103. devsper/memory/embeddings.py +60 -0
  104. devsper/memory/memory_index.py +97 -0
  105. devsper/memory/memory_router.py +62 -0
  106. devsper/memory/memory_store.py +221 -0
  107. devsper/memory/memory_types.py +54 -0
  108. devsper/memory/namespaces.py +45 -0
  109. devsper/memory/scoring.py +77 -0
  110. devsper/memory/summarizer.py +52 -0
  111. devsper/nodes/__init__.py +5 -0
  112. devsper/nodes/controller.py +449 -0
  113. devsper/nodes/rpc.py +127 -0
  114. devsper/nodes/single.py +161 -0
  115. devsper/nodes/worker.py +506 -0
  116. devsper/orchestration/__init__.py +19 -0
  117. devsper/orchestration/meta_planner.py +239 -0
  118. devsper/orchestration/priority_queue.py +61 -0
  119. devsper/plugins/__init__.py +19 -0
  120. devsper/plugins/marketplace/__init__.py +0 -0
  121. devsper/plugins/plugin_loader.py +70 -0
  122. devsper/plugins/plugin_registry.py +34 -0
  123. devsper/plugins/registry.py +83 -0
  124. devsper/protocols/__init__.py +6 -0
  125. devsper/providers/__init__.py +17 -0
  126. devsper/providers/anthropic.py +84 -0
  127. devsper/providers/base.py +75 -0
  128. devsper/providers/complexity_router.py +94 -0
  129. devsper/providers/gemini.py +36 -0
  130. devsper/providers/github.py +180 -0
  131. devsper/providers/model_router.py +40 -0
  132. devsper/providers/openai.py +105 -0
  133. devsper/providers/router/__init__.py +21 -0
  134. devsper/providers/router/backends/__init__.py +19 -0
  135. devsper/providers/router/backends/anthropic_backend.py +111 -0
  136. devsper/providers/router/backends/custom_backend.py +138 -0
  137. devsper/providers/router/backends/gemini_backend.py +89 -0
  138. devsper/providers/router/backends/github_backend.py +165 -0
  139. devsper/providers/router/backends/ollama_backend.py +104 -0
  140. devsper/providers/router/backends/openai_backend.py +142 -0
  141. devsper/providers/router/backends/vllm_backend.py +35 -0
  142. devsper/providers/router/base.py +60 -0
  143. devsper/providers/router/factory.py +92 -0
  144. devsper/providers/router/legacy.py +101 -0
  145. devsper/providers/router/router.py +135 -0
  146. devsper/reasoning/__init__.py +12 -0
  147. devsper/reasoning/graph.py +59 -0
  148. devsper/reasoning/nodes.py +20 -0
  149. devsper/reasoning/store.py +67 -0
  150. devsper/runtime/__init__.py +12 -0
  151. devsper/runtime/health.py +88 -0
  152. devsper/runtime/replay.py +53 -0
  153. devsper/runtime/replay_engine.py +142 -0
  154. devsper/runtime/run_history.py +204 -0
  155. devsper/runtime/telemetry.py +116 -0
  156. devsper/runtime/visualize.py +58 -0
  157. devsper/sandbox/__init__.py +13 -0
  158. devsper/sandbox/sandbox.py +161 -0
  159. devsper/swarm/checkpointer.py +65 -0
  160. devsper/swarm/executor.py +558 -0
  161. devsper/swarm/map_reduce.py +44 -0
  162. devsper/swarm/planner.py +197 -0
  163. devsper/swarm/prefetcher.py +91 -0
  164. devsper/swarm/scheduler.py +153 -0
  165. devsper/swarm/speculation.py +47 -0
  166. devsper/swarm/swarm.py +562 -0
  167. devsper/tools/__init__.py +33 -0
  168. devsper/tools/base.py +29 -0
  169. devsper/tools/code_intelligence/__init__.py +13 -0
  170. devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
  171. devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
  172. devsper/tools/code_intelligence/codebase_indexer.py +71 -0
  173. devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
  174. devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
  175. devsper/tools/code_intelligence/large_function_detector.py +68 -0
  176. devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
  177. devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
  178. devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
  179. devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
  180. devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
  181. devsper/tools/coding/__init__.py +12 -0
  182. devsper/tools/coding/analyze_code_complexity.py +48 -0
  183. devsper/tools/coding/dependency_analyzer.py +42 -0
  184. devsper/tools/coding/extract_functions.py +38 -0
  185. devsper/tools/coding/format_python.py +50 -0
  186. devsper/tools/coding/generate_docstrings.py +40 -0
  187. devsper/tools/coding/generate_unit_tests.py +42 -0
  188. devsper/tools/coding/lint_python.py +51 -0
  189. devsper/tools/coding/refactor_function.py +41 -0
  190. devsper/tools/coding/repo_structure_map.py +54 -0
  191. devsper/tools/coding/run_python.py +53 -0
  192. devsper/tools/data/__init__.py +12 -0
  193. devsper/tools/data/column_type_detection.py +64 -0
  194. devsper/tools/data/csv_summary.py +52 -0
  195. devsper/tools/data/dataframe_filter.py +51 -0
  196. devsper/tools/data/dataframe_groupby.py +47 -0
  197. devsper/tools/data/dataframe_stats.py +38 -0
  198. devsper/tools/data/dataset_sampling.py +55 -0
  199. devsper/tools/data/dataset_schema.py +45 -0
  200. devsper/tools/data/json_pretty_print.py +37 -0
  201. devsper/tools/data/json_query.py +46 -0
  202. devsper/tools/data/missing_value_report.py +47 -0
  203. devsper/tools/data_science/__init__.py +13 -0
  204. devsper/tools/data_science/correlation_heatmap.py +72 -0
  205. devsper/tools/data_science/dataset_bias_detector.py +49 -0
  206. devsper/tools/data_science/dataset_distribution_report.py +64 -0
  207. devsper/tools/data_science/dataset_drift_detector.py +64 -0
  208. devsper/tools/data_science/dataset_outlier_detector.py +65 -0
  209. devsper/tools/data_science/dataset_profile.py +76 -0
  210. devsper/tools/data_science/distributed_dataset_processor.py +54 -0
  211. devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
  212. devsper/tools/data_science/feature_importance_estimator.py +82 -0
  213. devsper/tools/data_science/model_input_validator.py +59 -0
  214. devsper/tools/data_science/time_series_analyzer.py +57 -0
  215. devsper/tools/documents/__init__.py +11 -0
  216. devsper/tools/documents/_docproc.py +56 -0
  217. devsper/tools/documents/document_to_markdown.py +29 -0
  218. devsper/tools/documents/extract_document_images.py +39 -0
  219. devsper/tools/documents/extract_document_text.py +29 -0
  220. devsper/tools/documents/extract_equations.py +36 -0
  221. devsper/tools/documents/extract_tables.py +47 -0
  222. devsper/tools/documents/summarize_document.py +42 -0
  223. devsper/tools/documents/write_latex_document.py +133 -0
  224. devsper/tools/documents/write_markdown_document.py +89 -0
  225. devsper/tools/documents/write_word_document.py +149 -0
  226. devsper/tools/experiments/__init__.py +13 -0
  227. devsper/tools/experiments/bootstrap_estimator.py +54 -0
  228. devsper/tools/experiments/experiment_report_generator.py +50 -0
  229. devsper/tools/experiments/experiment_tracker.py +36 -0
  230. devsper/tools/experiments/grid_search_runner.py +50 -0
  231. devsper/tools/experiments/model_benchmark_runner.py +45 -0
  232. devsper/tools/experiments/monte_carlo_experiment.py +38 -0
  233. devsper/tools/experiments/parameter_sweep_runner.py +51 -0
  234. devsper/tools/experiments/result_comparator.py +58 -0
  235. devsper/tools/experiments/simulation_runner.py +43 -0
  236. devsper/tools/experiments/statistical_significance_test.py +56 -0
  237. devsper/tools/experiments/swarm_map_reduce.py +42 -0
  238. devsper/tools/filesystem/__init__.py +12 -0
  239. devsper/tools/filesystem/append_file.py +42 -0
  240. devsper/tools/filesystem/file_hash.py +40 -0
  241. devsper/tools/filesystem/file_line_count.py +36 -0
  242. devsper/tools/filesystem/file_metadata.py +38 -0
  243. devsper/tools/filesystem/file_preview.py +55 -0
  244. devsper/tools/filesystem/find_large_files.py +50 -0
  245. devsper/tools/filesystem/list_directory.py +39 -0
  246. devsper/tools/filesystem/read_file.py +35 -0
  247. devsper/tools/filesystem/search_files.py +60 -0
  248. devsper/tools/filesystem/write_file.py +41 -0
  249. devsper/tools/flagship/__init__.py +15 -0
  250. devsper/tools/flagship/distributed_document_analysis.py +77 -0
  251. devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
  252. devsper/tools/flagship/repository_semantic_map.py +99 -0
  253. devsper/tools/flagship/research_graph_builder.py +111 -0
  254. devsper/tools/flagship/swarm_experiment_runner.py +86 -0
  255. devsper/tools/knowledge/__init__.py +10 -0
  256. devsper/tools/knowledge/citation_graph_builder.py +69 -0
  257. devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
  258. devsper/tools/knowledge/corpus_builder.py +66 -0
  259. devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
  260. devsper/tools/knowledge/document_corpus_summary.py +68 -0
  261. devsper/tools/knowledge/document_topic_extractor.py +58 -0
  262. devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
  263. devsper/tools/knowledge/timeline_extractor.py +59 -0
  264. devsper/tools/math/__init__.py +12 -0
  265. devsper/tools/math/calculate_expression.py +52 -0
  266. devsper/tools/math/correlation.py +44 -0
  267. devsper/tools/math/distribution_summary.py +39 -0
  268. devsper/tools/math/histogram.py +53 -0
  269. devsper/tools/math/linear_regression.py +47 -0
  270. devsper/tools/math/matrix_multiply.py +38 -0
  271. devsper/tools/math/mean_std.py +35 -0
  272. devsper/tools/math/monte_carlo_simulation.py +43 -0
  273. devsper/tools/math/polynomial_fit.py +40 -0
  274. devsper/tools/math/random_sample.py +36 -0
  275. devsper/tools/mcp/__init__.py +23 -0
  276. devsper/tools/mcp/adapter.py +53 -0
  277. devsper/tools/mcp/client.py +235 -0
  278. devsper/tools/mcp/discovery.py +53 -0
  279. devsper/tools/memory/__init__.py +16 -0
  280. devsper/tools/memory/delete_memory.py +25 -0
  281. devsper/tools/memory/list_memory.py +34 -0
  282. devsper/tools/memory/search_memory.py +36 -0
  283. devsper/tools/memory/store_memory.py +47 -0
  284. devsper/tools/memory/summarize_memory.py +41 -0
  285. devsper/tools/memory/tag_memory.py +47 -0
  286. devsper/tools/pipelines.py +92 -0
  287. devsper/tools/registry.py +39 -0
  288. devsper/tools/research/__init__.py +12 -0
  289. devsper/tools/research/arxiv_download.py +55 -0
  290. devsper/tools/research/arxiv_search.py +58 -0
  291. devsper/tools/research/citation_extractor.py +35 -0
  292. devsper/tools/research/duckduckgo_search.py +42 -0
  293. devsper/tools/research/paper_metadata_extractor.py +45 -0
  294. devsper/tools/research/paper_summarizer.py +41 -0
  295. devsper/tools/research/research_question_generator.py +39 -0
  296. devsper/tools/research/topic_cluster.py +46 -0
  297. devsper/tools/research/web_search.py +47 -0
  298. devsper/tools/research/wikipedia_lookup.py +50 -0
  299. devsper/tools/research_advanced/__init__.py +14 -0
  300. devsper/tools/research_advanced/citation_context_extractor.py +60 -0
  301. devsper/tools/research_advanced/literature_review_generator.py +79 -0
  302. devsper/tools/research_advanced/methodology_extractor.py +58 -0
  303. devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
  304. devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
  305. devsper/tools/research_advanced/paper_method_comparator.py +62 -0
  306. devsper/tools/research_advanced/paper_similarity_search.py +69 -0
  307. devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
  308. devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
  309. devsper/tools/research_advanced/research_gap_finder.py +71 -0
  310. devsper/tools/research_advanced/research_topic_mapper.py +69 -0
  311. devsper/tools/research_advanced/swarm_literature_review.py +58 -0
  312. devsper/tools/scoring/__init__.py +52 -0
  313. devsper/tools/scoring/report.py +44 -0
  314. devsper/tools/scoring/scorer.py +39 -0
  315. devsper/tools/scoring/selector.py +61 -0
  316. devsper/tools/scoring/store.py +267 -0
  317. devsper/tools/selector.py +130 -0
  318. devsper/tools/system/__init__.py +12 -0
  319. devsper/tools/system/cpu_usage.py +22 -0
  320. devsper/tools/system/disk_usage.py +35 -0
  321. devsper/tools/system/environment_variables.py +29 -0
  322. devsper/tools/system/memory_usage.py +23 -0
  323. devsper/tools/system/pip_install.py +44 -0
  324. devsper/tools/system/pip_search.py +29 -0
  325. devsper/tools/system/process_list.py +34 -0
  326. devsper/tools/system/python_package_list.py +40 -0
  327. devsper/tools/system/run_shell_command.py +51 -0
  328. devsper/tools/system/system_info.py +26 -0
  329. devsper/tools/tool_runner.py +122 -0
  330. devsper/tui/__init__.py +5 -0
  331. devsper/tui/activity_feed_view.py +73 -0
  332. devsper/tui/adaptive_tasks_view.py +75 -0
  333. devsper/tui/agent_role_view.py +35 -0
  334. devsper/tui/app.py +395 -0
  335. devsper/tui/dashboard_screen.py +290 -0
  336. devsper/tui/dev_view.py +99 -0
  337. devsper/tui/inject_screen.py +73 -0
  338. devsper/tui/knowledge_graph_view.py +46 -0
  339. devsper/tui/layout.py +43 -0
  340. devsper/tui/logs_view.py +83 -0
  341. devsper/tui/memory_view.py +58 -0
  342. devsper/tui/performance_view.py +33 -0
  343. devsper/tui/reasoning_graph_view.py +39 -0
  344. devsper/tui/results_view.py +139 -0
  345. devsper/tui/swarm_view.py +37 -0
  346. devsper/tui/task_detail_screen.py +55 -0
  347. devsper/tui/task_view.py +103 -0
  348. devsper/types/event.py +97 -0
  349. devsper/types/exceptions.py +21 -0
  350. devsper/types/swarm.py +41 -0
  351. devsper/types/task.py +80 -0
  352. devsper/upgrade/__init__.py +21 -0
  353. devsper/upgrade/changelog.py +124 -0
  354. devsper/upgrade/cli.py +145 -0
  355. devsper/upgrade/installer.py +103 -0
  356. devsper/upgrade/notifier.py +52 -0
  357. devsper/upgrade/version_check.py +121 -0
  358. devsper/utils/event_logger.py +88 -0
  359. devsper/utils/http.py +43 -0
  360. devsper/utils/models.py +54 -0
  361. devsper/visualization/__init__.py +5 -0
  362. devsper/visualization/dag_export.py +67 -0
  363. devsper/workflow/__init__.py +18 -0
  364. devsper/workflow/conditions.py +157 -0
  365. devsper/workflow/context.py +108 -0
  366. devsper/workflow/loader.py +156 -0
  367. devsper/workflow/resolver.py +109 -0
  368. devsper/workflow/runner.py +562 -0
  369. devsper/workflow/schema.py +63 -0
  370. devsper/workflow/validator.py +128 -0
  371. devsper-2.1.6.dist-info/METADATA +346 -0
  372. devsper-2.1.6.dist-info/RECORD +375 -0
  373. devsper-2.1.6.dist-info/WHEEL +4 -0
  374. devsper-2.1.6.dist-info/entry_points.txt +3 -0
  375. devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
@@ -0,0 +1,111 @@
1
+ """Anthropic Claude LLM backend."""
2
+
3
+ import os
4
+
5
+ from devsper.providers.router.base import LLMBackend, LLMRequest, LLMResponse
6
+
7
+
8
+ def _messages_to_anthropic(messages: list[dict]) -> list:
9
+ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
10
+ out = []
11
+ for m in messages:
12
+ role = (m.get("role") or "user").lower()
13
+ content = m.get("content") or ""
14
+ if role == "user":
15
+ out.append(HumanMessage(content=content))
16
+ elif role == "assistant":
17
+ out.append(AIMessage(content=content))
18
+ elif role == "system":
19
+ out.append(SystemMessage(content=content))
20
+ else:
21
+ out.append(HumanMessage(content=content))
22
+ return out
23
+
24
+
25
+ class AnthropicBackend(LLMBackend):
26
+ """Anthropic API (and Azure Foundry when env is set)."""
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: str | None = None,
31
+ *,
32
+ azure: bool = False,
33
+ azure_endpoint: str | None = None,
34
+ azure_api_key: str | None = None,
35
+ azure_deployment: str | None = None,
36
+ ) -> None:
37
+ self.azure = azure or bool(
38
+ os.environ.get("AZURE_ANTHROPIC_ENDPOINT") or os.environ.get("AZURE_ANTHROPIC_API_KEY")
39
+ )
40
+ self.azure_endpoint = (azure_endpoint or os.environ.get("AZURE_ANTHROPIC_ENDPOINT", "")).rstrip("/")
41
+ if self.azure_endpoint.endswith("/messages"):
42
+ self.azure_endpoint = self.azure_endpoint[: -len("/messages")]
43
+ self.api_key = azure_api_key or os.environ.get("AZURE_ANTHROPIC_API_KEY") if self.azure else (api_key or os.environ.get("ANTHROPIC_API_KEY"))
44
+ self.azure_deployment = (azure_deployment or os.environ.get("AZURE_ANTHROPIC_DEPLOYMENT_NAME") or "").strip()
45
+ self._llm = None
46
+
47
+ @property
48
+ def name(self) -> str:
49
+ return "anthropic"
50
+
51
+ def _get_llm(self, model: str):
52
+ if self._llm is not None:
53
+ return self._llm
54
+ from langchain_anthropic import ChatAnthropic
55
+ if self.azure:
56
+ self._llm = ChatAnthropic(
57
+ model=model or self.azure_deployment or "claude-3-5-sonnet-20241022",
58
+ anthropic_api_url=self.azure_endpoint,
59
+ anthropic_api_key=self.api_key,
60
+ temperature=0,
61
+ )
62
+ else:
63
+ self._llm = ChatAnthropic(
64
+ model=model or "claude-3-5-sonnet-20241022",
65
+ api_key=self.api_key,
66
+ temperature=0,
67
+ )
68
+ return self._llm
69
+
70
+ def supports_model(self, model_name: str) -> bool:
71
+ return (model_name or "").strip().lower().startswith("claude")
72
+
73
+ async def complete(self, request: LLMRequest) -> LLMResponse:
74
+ from langchain_core.messages import HumanMessage
75
+ model = request.model or "claude-3-5-sonnet-20241022"
76
+ llm = self._get_llm(model)
77
+ lc_messages = _messages_to_anthropic(request.messages)
78
+ msg = await llm.ainvoke(lc_messages)
79
+ content = msg.content if isinstance(msg.content, str) else str(msg.content or "")
80
+ usage = {}
81
+ if hasattr(msg, "response_metadata") and msg.response_metadata:
82
+ meta = msg.response_metadata
83
+ usage = {
84
+ "prompt_tokens": meta.get("input_tokens", 0),
85
+ "completion_tokens": meta.get("output_tokens", 0),
86
+ "total_tokens": meta.get("input_tokens", 0) + meta.get("output_tokens", 0),
87
+ }
88
+ return LLMResponse(
89
+ content=content,
90
+ model=model,
91
+ usage=usage,
92
+ finish_reason="stop",
93
+ backend=self.name,
94
+ )
95
+
96
+ async def stream(self, request: LLMRequest):
97
+ model = request.model or "claude-3-5-sonnet-20241022"
98
+ llm = self._get_llm(model)
99
+ lc_messages = _messages_to_anthropic(request.messages)
100
+ async for chunk in llm.astream(lc_messages):
101
+ if hasattr(chunk, "content") and chunk.content:
102
+ yield chunk.content
103
+
104
+ async def health(self) -> bool:
105
+ try:
106
+ await self.complete(
107
+ LLMRequest(model="claude-3-haiku-20240307", messages=[{"role": "user", "content": "Hi"}], max_tokens=2)
108
+ )
109
+ return True
110
+ except Exception:
111
+ return False
@@ -0,0 +1,138 @@
1
+ """Custom OpenAI-compatible endpoint backend."""
2
+
3
+ import httpx
4
+
5
+ from devsper.providers.router.base import LLMBackend, LLMRequest, LLMResponse
6
+
7
+
8
+ def _messages_to_openai(messages: list[dict]) -> list[dict]:
9
+ out = []
10
+ for m in messages:
11
+ role = (m.get("role") or "user").lower()
12
+ content = m.get("content") or ""
13
+ out.append({"role": role, "content": content})
14
+ return out
15
+
16
+
17
+ class CustomBackend(LLMBackend):
18
+ """Any OpenAI-compatible endpoint (base_url, optional api_key, optional model_prefix_strip)."""
19
+
20
+ def __init__(
21
+ self,
22
+ base_url: str,
23
+ api_key: str | None = None,
24
+ model_prefix_strip: str | None = None,
25
+ ) -> None:
26
+ self.base_url = base_url.rstrip("/")
27
+ self.api_key = api_key
28
+ self.model_prefix_strip = (model_prefix_strip or "").strip() or None
29
+
30
+ @property
31
+ def name(self) -> str:
32
+ return "custom"
33
+
34
+ def _model_for_request(self, model: str) -> str:
35
+ if self.model_prefix_strip and model.startswith(self.model_prefix_strip):
36
+ return model[len(self.model_prefix_strip):].lstrip("-")
37
+ return model
38
+
39
+ def supports_model(self, model_name: str) -> bool:
40
+ return True
41
+
42
+ def _headers(self) -> dict[str, str]:
43
+ h = {"Content-Type": "application/json"}
44
+ if self.api_key:
45
+ h["Authorization"] = f"Bearer {self.api_key}"
46
+ return h
47
+
48
+ async def complete(self, request: LLMRequest) -> LLMResponse:
49
+ model = self._model_for_request(request.model or "gpt-4o")
50
+ messages = _messages_to_openai(request.messages)
51
+ payload = {
52
+ "model": model,
53
+ "messages": messages,
54
+ "max_tokens": request.max_tokens,
55
+ "temperature": request.temperature,
56
+ }
57
+ if request.tools:
58
+ payload["tools"] = request.tools
59
+ url = f"{self.base_url}/v1/chat/completions"
60
+ from devsper.utils.http import ssl_verify, format_retry_after
61
+ async with httpx.AsyncClient(timeout=120.0, verify=ssl_verify()) as client:
62
+ r = await client.post(url, headers=self._headers(), json=payload)
63
+ try:
64
+ r.raise_for_status()
65
+ except httpx.HTTPStatusError as e:
66
+ hint = format_retry_after(e.response)
67
+ if hint:
68
+ raise httpx.HTTPStatusError(
69
+ str(e) + hint, request=e.request, response=e.response
70
+ ) from e
71
+ raise
72
+ data = r.json()
73
+ choices = data.get("choices") or []
74
+ content = ""
75
+ finish_reason = "stop"
76
+ if choices:
77
+ msg = choices[0].get("message") or {}
78
+ content = msg.get("content") or ""
79
+ finish_reason = choices[0].get("finish_reason") or "stop"
80
+ usage = data.get("usage") or {}
81
+ return LLMResponse(
82
+ content=content,
83
+ model=model,
84
+ usage={
85
+ "prompt_tokens": usage.get("prompt_tokens", 0),
86
+ "completion_tokens": usage.get("completion_tokens", 0),
87
+ "total_tokens": usage.get("total_tokens", 0),
88
+ },
89
+ finish_reason=finish_reason,
90
+ backend=self.name,
91
+ )
92
+
93
+ async def stream(self, request: LLMRequest):
94
+ model = self._model_for_request(request.model or "gpt-4o")
95
+ messages = _messages_to_openai(request.messages)
96
+ payload = {
97
+ "model": model,
98
+ "messages": messages,
99
+ "max_tokens": request.max_tokens,
100
+ "temperature": request.temperature,
101
+ "stream": True,
102
+ }
103
+ url = f"{self.base_url}/v1/chat/completions"
104
+ from devsper.utils.http import ssl_verify, format_retry_after
105
+ async with httpx.AsyncClient(timeout=120.0, verify=ssl_verify()) as client:
106
+ async with client.stream("POST", url, headers=self._headers(), json=payload) as resp:
107
+ try:
108
+ resp.raise_for_status()
109
+ except httpx.HTTPStatusError as e:
110
+ hint = format_retry_after(e.response)
111
+ if hint:
112
+ raise httpx.HTTPStatusError(
113
+ str(e) + hint, request=e.request, response=e.response
114
+ ) from e
115
+ raise
116
+ async for line in resp.aiter_lines():
117
+ if not line.strip():
118
+ continue
119
+ if line.strip() == "data: [DONE]":
120
+ return
121
+ if line.startswith("data: "):
122
+ import json
123
+ try:
124
+ chunk = json.loads(line[6:])
125
+ choices = chunk.get("choices") or []
126
+ if choices and choices[0].get("delta", {}).get("content"):
127
+ yield choices[0]["delta"]["content"]
128
+ except Exception:
129
+ pass
130
+
131
+ async def health(self) -> bool:
132
+ try:
133
+ await self.complete(
134
+ LLMRequest(model="gpt-4o-mini", messages=[{"role": "user", "content": "Hi"}], max_tokens=2)
135
+ )
136
+ return True
137
+ except Exception:
138
+ return False
@@ -0,0 +1,89 @@
1
+ """Google Gemini LLM backend."""
2
+
3
+ import os
4
+
5
+ from devsper.providers.router.base import LLMBackend, LLMRequest, LLMResponse
6
+
7
+
8
+ def _messages_to_lc(messages: list[dict]) -> list:
9
+ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
10
+ out = []
11
+ for m in messages:
12
+ role = (m.get("role") or "user").lower()
13
+ content = m.get("content") or ""
14
+ if role == "user":
15
+ out.append(HumanMessage(content=content))
16
+ elif role == "assistant":
17
+ out.append(AIMessage(content=content))
18
+ elif role == "system":
19
+ out.append(SystemMessage(content=content))
20
+ else:
21
+ out.append(HumanMessage(content=content))
22
+ return out
23
+
24
+
25
+ class GeminiBackend(LLMBackend):
26
+ """Google Gemini API."""
27
+
28
+ def __init__(self, api_key: str | None = None) -> None:
29
+ self.api_key = api_key or os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
30
+ if not self.api_key:
31
+ raise ValueError("Gemini requires api_key or GOOGLE_API_KEY or GEMINI_API_KEY")
32
+ self._llm = None
33
+
34
+ @property
35
+ def name(self) -> str:
36
+ return "gemini"
37
+
38
+ def _get_llm(self, model: str):
39
+ if self._llm is not None:
40
+ return self._llm
41
+ from langchain_google_genai import ChatGoogleGenerativeAI
42
+ self._llm = ChatGoogleGenerativeAI(
43
+ model=model or "gemini-1.5-flash",
44
+ google_api_key=self.api_key,
45
+ temperature=0,
46
+ )
47
+ return self._llm
48
+
49
+ def supports_model(self, model_name: str) -> bool:
50
+ return (model_name or "").strip().lower().startswith("gemini")
51
+
52
+ async def complete(self, request: LLMRequest) -> LLMResponse:
53
+ model = request.model or "gemini-1.5-flash"
54
+ llm = self._get_llm(model)
55
+ lc_messages = _messages_to_lc(request.messages)
56
+ msg = await llm.ainvoke(lc_messages)
57
+ content = msg.content if isinstance(msg.content, str) else str(msg.content or "")
58
+ usage = {}
59
+ if hasattr(msg, "response_metadata") and msg.response_metadata:
60
+ meta = msg.response_metadata
61
+ usage = {
62
+ "prompt_tokens": meta.get("input_tokens", 0),
63
+ "completion_tokens": meta.get("output_tokens", 0),
64
+ "total_tokens": meta.get("input_tokens", 0) + meta.get("output_tokens", 0),
65
+ }
66
+ return LLMResponse(
67
+ content=content,
68
+ model=model,
69
+ usage=usage,
70
+ finish_reason="stop",
71
+ backend=self.name,
72
+ )
73
+
74
+ async def stream(self, request: LLMRequest):
75
+ model = request.model or "gemini-1.5-flash"
76
+ llm = self._get_llm(model)
77
+ lc_messages = _messages_to_lc(request.messages)
78
+ async for chunk in llm.astream(lc_messages):
79
+ if hasattr(chunk, "content") and chunk.content:
80
+ yield chunk.content
81
+
82
+ async def health(self) -> bool:
83
+ try:
84
+ await self.complete(
85
+ LLMRequest(model="gemini-1.5-flash", messages=[{"role": "user", "content": "Hi"}], max_tokens=2)
86
+ )
87
+ return True
88
+ except Exception:
89
+ return False
@@ -0,0 +1,165 @@
1
+ """GitHub Models LLM backend (models.github.ai)."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ import time
7
+
8
+ import httpx
9
+
10
+ from devsper.providers.router.base import LLMBackend, LLMRequest, LLMResponse
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ GITHUB_MODELS_BASE = "https://models.github.ai"
15
+ GITHUB_CHAT_URL = f"{GITHUB_MODELS_BASE}/inference/chat/completions"
16
+ GITHUB_API_VERSION = "2022-11-28"
17
+ DEFAULT_GITHUB_MODEL = "openai/gpt-4.1"
18
+ GITHUB_429_MAX_RETRIES = 3
19
+ GITHUB_429_BASE_DELAY = 1.0
20
+
21
+
22
+ def _normalize_model_id(model: str) -> str:
23
+ s = model.split(":", 1)[-1].strip() if ":" in model else model.strip()
24
+ if not s or s.lower() == "copilot":
25
+ return DEFAULT_GITHUB_MODEL
26
+ return s
27
+
28
+
29
+ class GitHubBackend(LLMBackend):
30
+ """GitHub Models API. Uses GITHUB_TOKEN."""
31
+
32
+ def __init__(self, token: str | None = None) -> None:
33
+ self.token = token or os.environ.get("GITHUB_TOKEN")
34
+ if not self.token:
35
+ raise ValueError("GitHub backend requires GITHUB_TOKEN")
36
+
37
+ @property
38
+ def name(self) -> str:
39
+ return "github"
40
+
41
+ def _headers(self) -> dict[str, str]:
42
+ return {
43
+ "Accept": "application/vnd.github.v3+json",
44
+ "Content-Type": "application/json",
45
+ "Authorization": f"Bearer {self.token}",
46
+ "X-GitHub-Api-Version": GITHUB_API_VERSION,
47
+ }
48
+
49
+ def supports_model(self, model_name: str) -> bool:
50
+ return True
51
+
52
+ async def complete(self, request: LLMRequest) -> LLMResponse:
53
+ api_model = _normalize_model_id(request.model)
54
+ messages = request.messages
55
+ last_content = ""
56
+ for m in reversed(messages):
57
+ if (m.get("role") or "user").lower() == "user":
58
+ last_content = m.get("content") or ""
59
+ break
60
+ payload = {
61
+ "model": api_model,
62
+ "messages": [{"role": "user", "content": last_content}] if last_content else [{"role": "user", "content": "Hi"}],
63
+ "max_tokens": request.max_tokens,
64
+ "temperature": request.temperature,
65
+ }
66
+ from devsper.utils.http import ssl_verify, format_retry_after
67
+ async with httpx.AsyncClient(timeout=60.0, verify=ssl_verify()) as client:
68
+ for attempt in range(GITHUB_429_MAX_RETRIES):
69
+ try:
70
+ resp = await client.post(
71
+ GITHUB_CHAT_URL,
72
+ headers=self._headers(),
73
+ json=payload,
74
+ )
75
+ resp.raise_for_status()
76
+ data = resp.json()
77
+ break
78
+ except httpx.HTTPStatusError as e:
79
+ retry_hint = format_retry_after(e.response)
80
+ if e.response.status_code == 429 and attempt < GITHUB_429_MAX_RETRIES - 1:
81
+ delay = min(GITHUB_429_BASE_DELAY * (2**attempt), 32.0)
82
+ log.warning(
83
+ "GitHub 429, retry %s/%s in %.1fs%s",
84
+ attempt + 1, GITHUB_429_MAX_RETRIES, delay, retry_hint,
85
+ )
86
+ time.sleep(delay)
87
+ else:
88
+ if retry_hint:
89
+ raise httpx.HTTPStatusError(
90
+ str(e) + retry_hint, request=e.request, response=e.response
91
+ ) from e
92
+ raise
93
+ choices = data.get("choices") or []
94
+ content = ""
95
+ if choices:
96
+ msg = choices[0].get("message") or {}
97
+ c = msg.get("content")
98
+ if isinstance(c, str):
99
+ content = c
100
+ elif isinstance(c, list):
101
+ parts = [p.get("text", "") for p in c if isinstance(p, dict) and p.get("type") == "text"]
102
+ content = "\n".join(parts) if parts else ""
103
+ else:
104
+ content = str(c or "")
105
+ usage = data.get("usage") or {}
106
+ return LLMResponse(
107
+ content=content,
108
+ model=api_model,
109
+ usage={
110
+ "prompt_tokens": usage.get("prompt_tokens", 0),
111
+ "completion_tokens": usage.get("completion_tokens", 0),
112
+ "total_tokens": usage.get("total_tokens", 0),
113
+ },
114
+ finish_reason="stop",
115
+ backend=self.name,
116
+ )
117
+
118
+ async def stream(self, request: LLMRequest):
119
+ api_model = _normalize_model_id(request.model)
120
+ last_content = ""
121
+ for m in reversed(request.messages):
122
+ if (m.get("role") or "user").lower() == "user":
123
+ last_content = m.get("content") or ""
124
+ break
125
+ payload = {
126
+ "model": api_model,
127
+ "messages": [{"role": "user", "content": last_content or "Hi"}],
128
+ "temperature": request.temperature,
129
+ "stream": True,
130
+ }
131
+ from devsper.utils.http import ssl_verify
132
+ async with httpx.AsyncClient(timeout=60.0, verify=ssl_verify()) as client:
133
+ async with client.stream(
134
+ "POST",
135
+ GITHUB_CHAT_URL,
136
+ headers=self._headers(),
137
+ json=payload,
138
+ ) as resp:
139
+ resp.raise_for_status()
140
+ async for line in resp.aiter_lines():
141
+ if not line or not line.strip():
142
+ continue
143
+ if line.startswith("data: "):
144
+ chunk = line[6:].strip()
145
+ if chunk == "[DONE]":
146
+ return
147
+ try:
148
+ data = json.loads(chunk)
149
+ choices = data.get("choices") or []
150
+ if choices:
151
+ delta = choices[0].get("delta") or {}
152
+ part = delta.get("content")
153
+ if part:
154
+ yield part
155
+ except Exception:
156
+ pass
157
+
158
+ async def health(self) -> bool:
159
+ try:
160
+ await self.complete(
161
+ LLMRequest(model="openai/gpt-4.1", messages=[{"role": "user", "content": "Hi"}], max_tokens=2)
162
+ )
163
+ return True
164
+ except Exception:
165
+ return False
@@ -0,0 +1,104 @@
1
+ """Ollama local LLM backend (http://localhost:11434). No API key required."""
2
+
3
+ import httpx
4
+
5
+ from devsper.providers.router.base import LLMBackend, LLMRequest, LLMResponse
6
+
7
+
8
+ class OllamaBackend(LLMBackend):
9
+ """Ollama local. supports_model via GET /api/tags; complete via POST /api/chat."""
10
+
11
+ def __init__(self, base_url: str = "http://localhost:11434") -> None:
12
+ self.base_url = base_url.rstrip("/")
13
+ self._models_cache: list[str] | None = None
14
+
15
+ @property
16
+ def name(self) -> str:
17
+ return "ollama"
18
+
19
+ async def _fetch_models(self) -> list[str]:
20
+ if self._models_cache is not None:
21
+ return self._models_cache
22
+ try:
23
+ async with httpx.AsyncClient(timeout=10.0) as client:
24
+ r = await client.get(f"{self.base_url}/api/tags")
25
+ r.raise_for_status()
26
+ data = r.json()
27
+ models = data.get("models") or []
28
+ self._models_cache = [m.get("name", "").split(":")[0] for m in models if m.get("name")]
29
+ return self._models_cache
30
+ except Exception:
31
+ self._models_cache = []
32
+ return []
33
+
34
+ def supports_model(self, model_name: str) -> bool:
35
+ """Ollama can serve any model that exists on the server; we rely on prefix 'ollama:' for routing."""
36
+ return True
37
+
38
+ async def complete(self, request: LLMRequest) -> LLMResponse:
39
+ model = request.model or "llama3"
40
+ messages = request.messages
41
+ prompt = ""
42
+ for m in reversed(messages):
43
+ if (m.get("role") or "user").lower() == "user":
44
+ prompt = m.get("content") or ""
45
+ break
46
+ payload = {
47
+ "model": model,
48
+ "messages": [{"role": "user", "content": prompt}] if prompt else [{"role": "user", "content": "Hi"}],
49
+ "stream": False,
50
+ "options": {"num_predict": request.max_tokens, "temperature": request.temperature},
51
+ }
52
+ async with httpx.AsyncClient(timeout=120.0) as client:
53
+ r = await client.post(f"{self.base_url}/api/chat", json=payload)
54
+ r.raise_for_status()
55
+ data = r.json()
56
+ msg = data.get("message") or {}
57
+ content = msg.get("content") or ""
58
+ usage = data.get("eval_count") or 0
59
+ return LLMResponse(
60
+ content=content,
61
+ model=model,
62
+ usage={"prompt_tokens": 0, "completion_tokens": usage, "total_tokens": usage},
63
+ finish_reason="stop",
64
+ backend=self.name,
65
+ )
66
+
67
+ async def stream(self, request: LLMRequest):
68
+ model = request.model or "llama3"
69
+ prompt = ""
70
+ for m in reversed(request.messages):
71
+ if (m.get("role") or "user").lower() == "user":
72
+ prompt = m.get("content") or ""
73
+ break
74
+ payload = {
75
+ "model": model,
76
+ "messages": [{"role": "user", "content": prompt or "Hi"}],
77
+ "stream": True,
78
+ "options": {"num_predict": request.max_tokens, "temperature": request.temperature},
79
+ }
80
+ async with httpx.AsyncClient(timeout=120.0) as client:
81
+ async with client.stream("POST", f"{self.base_url}/api/chat", json=payload) as resp:
82
+ resp.raise_for_status()
83
+ async for line in resp.aiter_lines():
84
+ if not line.strip():
85
+ continue
86
+ import json
87
+ try:
88
+ data = json.loads(line)
89
+ msg = data.get("message") or {}
90
+ part = msg.get("content")
91
+ if part:
92
+ yield part
93
+ if data.get("done"):
94
+ break
95
+ except Exception:
96
+ pass
97
+
98
+ async def health(self) -> bool:
99
+ try:
100
+ async with httpx.AsyncClient(timeout=5.0) as client:
101
+ r = await client.get(f"{self.base_url}/api/tags")
102
+ return r.status_code == 200
103
+ except Exception:
104
+ return False