teddy-cli 0.1.17.dev1396__tar.gz → 0.1.17.dev1397__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/PKG-INFO +1 -1
  2. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/pyproject.toml +1 -1
  3. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/__main__.py +1 -37
  4. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/session_cli_handlers.py +163 -32
  5. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/litellm_adapter.py +75 -20
  6. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/session.py +0 -1
  7. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/planning_service.py +1 -10
  8. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_service.py +1 -3
  9. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/config.yaml +3 -0
  10. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/.gitignore +0 -0
  11. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/LICENSE +0 -0
  12. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/README.md +0 -0
  13. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/__init__.py +0 -0
  14. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/__init__.py +0 -0
  15. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/__init__.py +0 -0
  16. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/cli_formatter.py +0 -0
  17. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/cli_helpers.py +0 -0
  18. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer.py +0 -0
  19. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +0 -0
  20. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +0 -0
  21. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +0 -0
  22. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +0 -0
  23. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +0 -0
  24. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +0 -0
  25. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +0 -0
  26. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/__init__.py +0 -0
  27. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/console_interactor.py +0 -0
  28. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/console_interactor_ask_loop.py +0 -0
  29. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/console_interactor_helpers.py +0 -0
  30. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/console_tooling.py +0 -0
  31. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/filesystem_helpers.py +0 -0
  32. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/local_file_system_adapter.py +0 -0
  33. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +0 -0
  34. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/openrouter_hydrator.py +0 -0
  35. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/shell_adapter.py +0 -0
  36. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/shell_command_builder.py +0 -0
  37. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/suppressed_logging.py +0 -0
  38. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/system_environment_adapter.py +0 -0
  39. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/system_environment_inspector.py +0 -0
  40. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/system_time_adapter.py +0 -0
  41. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/terminal_quit_key_listener.py +0 -0
  42. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/web_scraper_adapter.py +0 -0
  43. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/web_searcher_adapter.py +0 -0
  44. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/adapters/outbound/yaml_config_adapter.py +0 -0
  45. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/container.py +0 -0
  46. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/__init__.py +0 -0
  47. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/__init__.py +0 -0
  48. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/__init__.py +0 -0
  49. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/action_ports.py +0 -0
  50. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/change_set.py +0 -0
  51. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/drift_report.py +0 -0
  52. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/exceptions.py +0 -0
  53. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/execution_report.py +0 -0
  54. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/orchestrator_ports.py +0 -0
  55. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/plan.py +0 -0
  56. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/planning_ports.py +0 -0
  57. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/project_context.py +0 -0
  58. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/report_assembly_data.py +0 -0
  59. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/shell_output.py +0 -0
  60. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/domain/models/web_search_results.py +0 -0
  61. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/__init__.py +0 -0
  62. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/__init__.py +0 -0
  63. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/edit_simulator.py +0 -0
  64. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/get_context_use_case.py +0 -0
  65. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/init.py +0 -0
  66. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/plan_parser.py +0 -0
  67. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/plan_reviewer.py +0 -0
  68. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/plan_validator.py +0 -0
  69. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/planning_use_case.py +0 -0
  70. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/inbound/run_plan_use_case.py +0 -0
  71. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/__init__.py +0 -0
  72. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/config_service.py +0 -0
  73. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/environment_inspector.py +0 -0
  74. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/execution_report_assembler.py +0 -0
  75. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/file_system_manager.py +0 -0
  76. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/llm_client.py +0 -0
  77. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/markdown_report_formatter.py +0 -0
  78. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/prompt_manager.py +0 -0
  79. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/quit_key_listener.py +0 -0
  80. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/repo_tree_generator.py +0 -0
  81. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/session_loop_guard.py +0 -0
  82. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/session_manager.py +0 -0
  83. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/session_repository.py +0 -0
  84. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/shell_executor.py +0 -0
  85. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/system_environment.py +0 -0
  86. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/time_service.py +0 -0
  87. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/user_interactor.py +0 -0
  88. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/web_scraper.py +0 -0
  89. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/ports/outbound/web_searcher.py +0 -0
  90. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/__init__.py +0 -0
  91. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_changeset_builder.py +0 -0
  92. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_diff_manager.py +0 -0
  93. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_dispatcher.py +0 -0
  94. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_executor.py +0 -0
  95. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_factory.py +0 -0
  96. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_parser_complex.py +0 -0
  97. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/action_parser_strategies.py +0 -0
  98. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/context_service.py +0 -0
  99. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/edit_simulator.py +0 -0
  100. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/execution_orchestrator.py +0 -0
  101. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/execution_report_assembler.py +0 -0
  102. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/init_service.py +0 -0
  103. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/markdown_plan_parser.py +0 -0
  104. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/markdown_report_formatter.py +0 -0
  105. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/parser_infrastructure.py +0 -0
  106. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/parser_metadata.py +0 -0
  107. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/parser_reporting.py +0 -0
  108. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/plan_validator.py +0 -0
  109. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/prompt_manager.py +0 -0
  110. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_lifecycle_manager.py +0 -0
  111. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_loop_guard.py +0 -0
  112. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_orchestrator.py +0 -0
  113. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_planner.py +0 -0
  114. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_pruning_service.py +0 -0
  115. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_replanner.py +0 -0
  116. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/session_repository.py +0 -0
  117. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/templates/execution_report.md.j2 +0 -0
  118. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/update_checker.py +0 -0
  119. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/__init__.py +0 -0
  120. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/edit.py +0 -0
  121. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/edit_matcher.py +0 -0
  122. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +0 -0
  123. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/execute.py +0 -0
  124. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/filesystem.py +0 -0
  125. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/helpers.py +0 -0
  126. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/services/validation_rules/message.py +0 -0
  127. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/__init__.py +0 -0
  128. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/diff.py +0 -0
  129. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/editor_names.py +0 -0
  130. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/interrupt_guard.py +0 -0
  131. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/io.py +0 -0
  132. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/markdown.py +0 -0
  133. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/paths.py +0 -0
  134. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/serialization.py +0 -0
  135. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/session_notices.py +0 -0
  136. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/stdin_ownership.py +0 -0
  137. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/string.py +0 -0
  138. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/core/utils/terminal.py +0 -0
  139. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/prompts.py +0 -0
  140. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/registries/__init__.py +0 -0
  141. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/registries/infrastructure.py +0 -0
  142. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/registries/reviewer.py +0 -0
  143. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/registries/validators.py +0 -0
  144. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/MRP.md +0 -0
  145. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/__init__.py +0 -0
  146. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/__init__.py +0 -0
  147. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/init.context +0 -0
  148. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/prompts/architect.md +0 -0
  149. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/prompts/assistant.md +0 -0
  150. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/prompts/debugger.md +0 -0
  151. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/prompts/developer.md +0 -0
  152. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/prompts/pathfinder.md +0 -0
  153. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/config/prompts/prototyper.md +0 -0
  154. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/ARCHITECTURE.md +0 -0
  155. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/PROJECT.md +0 -0
  156. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/case-file.md +0 -0
  157. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/ci.md +0 -0
  158. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/component-design.md +0 -0
  159. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/makefile.md +0 -0
  160. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/milestone.md +0 -0
  161. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/pre-commit.md +0 -0
  162. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/specification-document.md +0 -0
  163. {teddy_cli-0.1.17.dev1396 → teddy_cli-0.1.17.dev1397}/src/teddy_executor/resources/templates/vertical-slice.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: teddy-cli
3
- Version: 0.1.17.dev1396
3
+ Version: 0.1.17.dev1397
4
4
  Summary: The local-first, test-driven agentic engineering harness. Because you should own the means of development.
5
5
  Author: Raphael Atteritano
6
6
  License: AGPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "teddy-cli"
3
- version = "0.1.17.dev1396"
3
+ version = "0.1.17.dev1397"
4
4
  description = "The local-first, test-driven agentic engineering harness. Because you should own the means of development."
5
5
  readme = "README.md"
6
6
  keywords = [
@@ -112,31 +112,6 @@ def _resolve_setup_api_key(
112
112
  return system_env.isatty() and not pipeline
113
113
 
114
114
 
115
- def _run_init_setup_gates(container) -> None:
116
- """Run the interactive setup gates after ``teddy init`` scaffolding.
117
-
118
- Bare ``teddy init`` and ``teddy init config`` scaffold (or reset) the
119
- ``.teddy`` configuration, then run the SAME one-time setup as ``teddy
120
- start``: the required LLM API-key prompt (when the key is missing and the
121
- configured model requires one) and the optional editor selection (when no
122
- editor is configured). Both gates reuse the existing preflight helpers, so
123
- no prompt logic is duplicated. The gates are TTY-gated: a non-interactive
124
- run (stdin is not a TTY) prompts for neither, keeping automated and CI
125
- invocations clean.
126
- """
127
- from teddy_executor.adapters.inbound.session_cli_handlers import (
128
- _ensure_api_key_configured,
129
- _validate_editor_config,
130
- )
131
-
132
- system_env = container.resolve(ISystemEnvironment)
133
- if not system_env.isatty():
134
- return
135
- config_service = container.resolve(IConfigService)
136
- _ensure_api_key_configured(config_service, setup_api_key=True)
137
- _validate_editor_config(container)
138
-
139
-
140
115
  def _display_init_drift(container) -> None:
141
116
  """Print the prompt/template drift advice after bare ``teddy init``.
142
117
 
@@ -144,8 +119,7 @@ def _display_init_drift(container) -> None:
144
119
  "unchanged" summary cannot mislead: it flags drifted prompts and missing
145
120
  templates and names the actionable ``teddy init prompts`` /
146
121
  ``teddy init templates`` commands. The two shared helpers are imported
147
- lazily, mirroring ``_run_init_setup_gates``, and they are advisory only --
148
- they never block the command.
122
+ lazily, and they are advisory only -- they never block the command.
149
123
  """
150
124
  from teddy_executor.adapters.inbound.session_cli_handlers import (
151
125
  _display_checks_footer,
@@ -262,9 +236,6 @@ def start( # noqa: PLR0913
262
236
  ),
263
237
  ),
264
238
  model: Optional[str] = typer.Option(None, "--model", help="LLM model override."),
265
- api_key: Optional[str] = typer.Option(
266
- None, "--api-key", help="LLM API key override."
267
- ),
268
239
  max_turns: Optional[int] = OPT_MAX_TURNS,
269
240
  max_cost: Optional[float] = OPT_MAX_COST,
270
241
  ):
@@ -297,7 +268,6 @@ def start( # noqa: PLR0913
297
268
  pipeline=pipeline,
298
269
  additional_context=additional_context,
299
270
  model=model,
300
- api_key=api_key,
301
271
  max_turns=max_turns,
302
272
  max_cost=max_cost,
303
273
  )
@@ -326,7 +296,6 @@ def init_callback(ctx: typer.Context):
326
296
  summary = init_use_case.ensure_initialized()
327
297
  typer.echo(f"TeDDy initialized in .teddy folder. {summary}")
328
298
  _display_init_drift(container)
329
- _run_init_setup_gates(container)
330
299
 
331
300
 
332
301
  @init_app.command()
@@ -353,7 +322,6 @@ def config():
353
322
  init_use_case = container.resolve(IInitUseCase)
354
323
  status = init_use_case.ensure_config_initialized(overwrite=True)
355
324
  typer.echo(status)
356
- _run_init_setup_gates(container)
357
325
 
358
326
 
359
327
  @init_app.command()
@@ -542,9 +510,6 @@ def resume( # noqa: PLR0913
542
510
  ),
543
511
  no_copy: bool = OPT_NO_COPY,
544
512
  model: Optional[str] = typer.Option(None, "--model", help="LLM model override."),
545
- api_key: Optional[str] = typer.Option(
546
- None, "--api-key", help="LLM API key override."
547
- ),
548
513
  agent: Optional[str] = typer.Option(
549
514
  None,
550
515
  "--agent",
@@ -587,7 +552,6 @@ def resume( # noqa: PLR0913
587
552
  setup_api_key=_resolve_setup_api_key(system_env, pipeline=pipeline),
588
553
  no_copy=no_copy,
589
554
  model=model,
590
- api_key=api_key,
591
555
  agent=agent,
592
556
  message=message,
593
557
  pipeline=pipeline,
@@ -666,7 +666,6 @@ def handle_new_session( # noqa: PLR0913
666
666
  pipeline: bool = False,
667
667
  additional_context: Optional[list[str]] = None,
668
668
  model: Optional[str] = None,
669
- api_key: Optional[str] = None,
670
669
  setup_editor: Optional[bool] = None,
671
670
  setup_api_key: Optional[bool] = None,
672
671
  max_turns: Optional[int] = None,
@@ -712,7 +711,6 @@ def handle_new_session( # noqa: PLR0913
712
711
  initial_request=message,
713
712
  additional_context=additional_context or [],
714
713
  model=model,
715
- api_key=api_key,
716
714
  )
717
715
  session_dir = session_manager.create_session(options)
718
716
  typer.echo(f"Session created at: {session_dir}")
@@ -790,8 +788,7 @@ def _run_cli_preflight_check(
790
788
 
791
789
  # API-key setup runs BEFORE the LLM client is resolved so its transient
792
790
  # config-service instance reads the freshly-persisted on-disk state. The
793
- # gate itself is single-sourced in ``_ensure_api_key_configured`` so the
794
- # ``init`` command shares identical gating.
791
+ # gate itself is single-sourced in ``_ensure_api_key_configured``.
795
792
  config_service = container.resolve(IConfigService)
796
793
  _ensure_api_key_configured(config_service, setup_api_key)
797
794
 
@@ -829,20 +826,24 @@ def _ensure_api_key_configured(
829
826
  config_service: IConfigService,
830
827
  setup_api_key: Optional[bool],
831
828
  ) -> None:
832
- """Runs the interactive API-key gate when the key is missing and required.
833
-
834
- The gate fires only when ``setup_api_key`` is truthy, the resolved
835
- ``llm.api_key`` is absent or blank, AND the configured model actually
836
- requires a provider key (key-requiredness is delegated to litellm). It is
837
- ADDITIVE and inert by default (``setup_api_key`` of ``None``/``False`` ->
838
- skip), so callers that never opt in are unaffected. Single-sourced so
839
- ``_run_cli_preflight_check`` and the ``init`` command share identical gating.
829
+ """Runs the API-key gate when the key is missing and a key may be required.
830
+
831
+ The gate fires only when ``setup_api_key`` is truthy and the resolved
832
+ ``llm.api_key`` is absent or blank. A local endpoint short-circuits the
833
+ prompt with a persisted placeholder key, because a server on the developer
834
+ machine never needs a real credential. Otherwise the interactive prompt
835
+ fires when the configured model actually requires a provider key
836
+ (key-requiredness is delegated to litellm). It is ADDITIVE and inert by
837
+ default (``setup_api_key`` of ``None``/``False`` -> skip), so callers that
838
+ never opt in are unaffected. Single-sourced so both session entry points
839
+ share identical gating.
840
840
  """
841
- if (
842
- setup_api_key
843
- and _is_llm_api_key_missing(config_service)
844
- and _llm_model_requires_api_key(config_service)
845
- ):
841
+ if not setup_api_key or not _is_llm_api_key_missing(config_service):
842
+ return
843
+ if _is_local_endpoint(config_service):
844
+ _persist_local_placeholder_key(config_service)
845
+ return
846
+ if _llm_model_requires_api_key(config_service):
846
847
  _prompt_for_api_key(config_service)
847
848
 
848
849
 
@@ -895,29 +896,49 @@ def _prompt_for_api_key(config_service: IConfigService) -> None:
895
896
  The key is written to ``.teddy/.env`` (never ``os.environ``) and the config
896
897
  is migrated to the ``${TEDDY_LLM_API_KEY}`` interpolation form so the
897
898
  transient ``IConfigService`` instance held by ``ILlmClient`` resolves it on
898
- the same run. A truly empty entry re-prompts (Click's native loop, now that
899
- no default is supplied); a whitespace-only entry is ignored, and an aborted
899
+ the same run. The warning names the resolved provider and the stripped model
900
+ so the user knows which credential to paste. An empty line skips the prompt
901
+ with a notice, a whitespace-only entry persists nothing, and an aborted
900
902
  prompt (Ctrl-C / Ctrl-D) returns WITHOUT raising so the downstream
901
903
  validation error can surface the missing key with its own hint.
902
904
  """
905
+ provider, model = _resolve_model_identity(config_service)
906
+ api_base = config_service.get_setting("llm.api_base")
907
+ remote_api_base = (
908
+ api_base if isinstance(api_base, str) and api_base.strip() else None
909
+ )
910
+
903
911
  typer.echo("", err=True)
904
912
  typer.secho("LLM API Key", fg=typer.colors.CYAN, bold=True, err=True)
905
913
  typer.echo("", err=True)
906
- typer.secho(
907
- "\u26a0 No LLM API key configured. Paste your key below.\n"
908
- "OpenRouter is the default provider. To use a different provider, change llm.model in .teddy/config.yaml.",
909
- fg=typer.colors.YELLOW,
910
- err=True,
911
- )
914
+ warning_lines = ["\u26a0 No LLM API key configured."]
915
+ if remote_api_base:
916
+ warning_lines.append(f" Endpoint: {remote_api_base}")
917
+ elif provider:
918
+ warning_lines.append(f" Provider: {provider}")
919
+ if model:
920
+ warning_lines.append(f" Model: {model}")
921
+ typer.secho("\n".join(warning_lines), fg=typer.colors.YELLOW, err=True)
922
+ typer.echo("", err=True)
923
+
924
+ instructions = ["Paste your key below, or press Enter on an empty line to skip."]
925
+ if not remote_api_base:
926
+ instructions.append(
927
+ "To use a different provider, change llm.model in .teddy/config.yaml."
928
+ )
929
+ typer.secho("\n".join(instructions), fg=typer.colors.YELLOW, err=True)
912
930
  typer.echo("", err=True)
913
931
 
914
932
  try:
915
- value = typer.prompt("LLM API key", hide_input=True)
933
+ value = typer.prompt(
934
+ "LLM API key", hide_input=True, default="", show_default=False
935
+ )
916
936
  except (EOFError, typer.Abort):
917
937
  return
918
938
 
919
939
  value = value.strip()
920
940
  if not value:
941
+ typer.secho("\u2139 No key saved.", fg=typer.colors.YELLOW, err=True)
921
942
  return
922
943
 
923
944
  config_service.set_env_variable("TEDDY_LLM_API_KEY", value)
@@ -930,6 +951,121 @@ def _prompt_for_api_key(config_service: IConfigService) -> None:
930
951
  )
931
952
 
932
953
 
954
+ def _resolve_model_identity(
955
+ config_service: IConfigService,
956
+ ) -> tuple[Optional[str], str]:
957
+ """Resolves the provider name and the prefix-stripped model for the prompt.
958
+
959
+ litellm is the single source of truth for provider resolution. A failed
960
+ resolution prints the provider list to stdout, so the call runs inside a
961
+ stdout redirect. An unresolvable model falls back to the configured string
962
+ with no provider, so the prompt still shows which model the key belongs to.
963
+ """
964
+ import contextlib
965
+ import io
966
+ import logging
967
+
968
+ model = config_service.get_setting("llm.model")
969
+ if not (isinstance(model, str) and model.strip()):
970
+ return None, ""
971
+ api_base = config_service.get_setting("llm.api_base")
972
+ resolved_api_base = (
973
+ api_base if isinstance(api_base, str) and api_base.strip() else None
974
+ )
975
+ try:
976
+ import litellm
977
+
978
+ with contextlib.redirect_stdout(io.StringIO()):
979
+ resolved_model, provider, _dynamic_key, _resolved_base = (
980
+ litellm.get_llm_provider(model, api_base=resolved_api_base)
981
+ )
982
+ return str(provider), str(resolved_model)
983
+ except Exception:
984
+ logging.getLogger(__name__).warning(
985
+ "Could not resolve a provider for model '%s'; showing the configured "
986
+ "name instead.",
987
+ model,
988
+ exc_info=True,
989
+ )
990
+ return None, model.strip()
991
+
992
+
993
+ _LOCAL_PLACEHOLDER_KEY = "dummy-key-custom-endpoints"
994
+
995
+
996
+ def _is_local_endpoint(config_service: IConfigService) -> bool:
997
+ """True when ``llm.api_base`` points at the developer machine.
998
+
999
+ A local host means no real credential can be required, so the gate writes a
1000
+ placeholder instead of prompting. Host classification performs no DNS
1001
+ lookup, so an unresolvable name never blocks startup.
1002
+ """
1003
+ api_base = config_service.get_setting("llm.api_base")
1004
+ if not (isinstance(api_base, str) and api_base.strip()):
1005
+ return False
1006
+ return _host_is_local(_api_base_host(api_base))
1007
+
1008
+
1009
+ def _api_base_host(api_base: str) -> Optional[str]:
1010
+ """Extracts the host from an ``api_base`` value, tolerating a missing scheme."""
1011
+ from urllib.parse import urlsplit
1012
+
1013
+ value = api_base.strip()
1014
+ candidate = value if "://" in value else f"//{value}"
1015
+ try:
1016
+ return urlsplit(candidate).hostname
1017
+ except ValueError:
1018
+ return None
1019
+
1020
+
1021
+ def _host_is_local(host: Optional[str]) -> bool:
1022
+ """True when a host names the developer machine or a private network.
1023
+
1024
+ Loopback names, the ``host.docker.internal`` alias, and ``.local`` names all
1025
+ address the developer machine. Loopback, link-local, and private address
1026
+ ranges cannot be a public provider. No DNS lookup runs, so an unresolvable
1027
+ name never blocks startup.
1028
+ """
1029
+ import ipaddress
1030
+
1031
+ if not host:
1032
+ return False
1033
+ normalized = host.strip("[]").casefold()
1034
+ if normalized == "localhost" or normalized.endswith(".localhost"):
1035
+ return True
1036
+ if normalized == "host.docker.internal" or normalized.endswith(".local"):
1037
+ return True
1038
+ try:
1039
+ address = ipaddress.ip_address(normalized)
1040
+ except ValueError:
1041
+ return False
1042
+ return address.is_loopback or address.is_link_local or address.is_private
1043
+
1044
+
1045
+ def _persist_local_placeholder_key(config_service: IConfigService) -> None:
1046
+ """Writes the placeholder key for a local endpoint and prints the notice (stderr).
1047
+
1048
+ The placeholder is written to ``.teddy/.env`` and the config migrates to the
1049
+ ``${TEDDY_LLM_API_KEY}`` interpolation form, mirroring the interactive
1050
+ prompt path so a server that checks the Authorization header still starts.
1051
+ """
1052
+ config_service.set_env_variable("TEDDY_LLM_API_KEY", _LOCAL_PLACEHOLDER_KEY)
1053
+ config_service.set_setting("llm.api_key", "${TEDDY_LLM_API_KEY}")
1054
+ api_base = config_service.get_setting("llm.api_base") or ""
1055
+ typer.echo("", err=True)
1056
+ typer.secho(
1057
+ f"\u2139 Local endpoint detected: {api_base}",
1058
+ fg=typer.colors.CYAN,
1059
+ err=True,
1060
+ )
1061
+ typer.secho(
1062
+ f' Saved placeholder API key "{_LOCAL_PLACEHOLDER_KEY}" to .teddy/.env.\n'
1063
+ " Replace TEDDY_LLM_API_KEY there if the server checks the Authorization header.",
1064
+ fg=typer.colors.YELLOW,
1065
+ err=True,
1066
+ )
1067
+
1068
+
933
1069
  def _persist_editor_choice(config_service: IConfigService, value: str) -> None:
934
1070
  """Persists the editor choice and prints the green save confirmation (stderr)."""
935
1071
  config_service.set_setting("editor", value)
@@ -1167,7 +1303,6 @@ def _sync_and_display_session_meta(
1167
1303
  container: Container,
1168
1304
  session_name: str,
1169
1305
  model: Optional[str] = None,
1170
- api_key: Optional[str] = None,
1171
1306
  agent: Optional[str] = None,
1172
1307
  ) -> None:
1173
1308
  """Reads latest turn meta.yaml, syncs config overrides, and clears actual_model.
@@ -1201,9 +1336,6 @@ def _sync_and_display_session_meta(
1201
1336
  elif config_model != "unknown" and meta.get("model") != config_model:
1202
1337
  meta["model"] = config_model
1203
1338
  changed = True
1204
- if api_key:
1205
- meta["api_key"] = api_key
1206
- changed = True
1207
1339
  if changed:
1208
1340
  repository.save_meta(f"{latest_turn_path}/meta.yaml", meta)
1209
1341
 
@@ -1214,7 +1346,6 @@ def handle_resume_session( # noqa: PLR0913
1214
1346
  interactive: bool = True,
1215
1347
  no_copy: bool = False,
1216
1348
  model: Optional[str] = None,
1217
- api_key: Optional[str] = None,
1218
1349
  agent: Optional[str] = None,
1219
1350
  message: Optional[str] = None,
1220
1351
  pipeline: bool = False,
@@ -1240,7 +1371,7 @@ def handle_resume_session( # noqa: PLR0913
1240
1371
 
1241
1372
  # 3. Display actual_model and sync config overrides
1242
1373
  _sync_and_display_session_meta(
1243
- container, session_name, model=model, api_key=api_key, agent=agent
1374
+ container, session_name, model=model, agent=agent
1244
1375
  )
1245
1376
 
1246
1377
  # 4. Display session path (below the Agent/Model identity block).
@@ -108,7 +108,9 @@ class LiteLLMAdapter(ILlmClient):
108
108
  resolved = "gpt-4o"
109
109
  return str(resolved)
110
110
 
111
- def get_completion(
111
+ # Request, retry, and hydration form one linear flow whose branching is
112
+ # inherent to the provider protocol. Extracting it is outside this change.
113
+ def get_completion( # noqa: C901
112
114
  self, messages: List[Dict[str, str]], model: Optional[str] = None, **kwargs: Any
113
115
  ) -> Any:
114
116
  """
@@ -414,29 +416,82 @@ class LiteLLMAdapter(ILlmClient):
414
416
  "(or your shell environment)."
415
417
  )
416
418
 
417
- # 3. Optional Remote Check: Verify key validity/expiration
418
- if not errors and include_remote and is_api_key_provided:
419
- from concurrent.futures import TimeoutError
420
-
421
- executor = self._get_executor()
422
- future = executor.submit(
423
- litellm.check_valid_key, model=model, api_key=api_key
424
- )
425
- try:
426
- is_valid = future.result(timeout=10.0)
427
- if not is_valid:
428
- errors.append(
429
- "The API key appears to be invalid, expired, or deactivated."
430
- )
431
- except TimeoutError:
432
- errors.append(
433
- "The remote connectivity check timed out after 10 seconds."
419
+ # 2. Provider resolution: a bare model name cannot be routed, so the
420
+ # failure surfaces at preflight with a paste-ready fix instead of at the
421
+ # first completion. Skipped when an earlier error already explains the
422
+ # configuration problem.
423
+ if not errors:
424
+ errors.extend(self._provider_resolution_errors(model))
425
+
426
+ # 3. Optional remote check: verify key validity and expiration.
427
+ if not errors:
428
+ errors.extend(
429
+ self._remote_check_errors(
430
+ model, api_key, include_remote, is_api_key_provided
434
431
  )
435
- except Exception as e:
436
- errors.append(f"Remote connectivity check failed: {str(e)}")
432
+ )
437
433
 
438
434
  return errors
439
435
 
436
+ def _provider_resolution_errors(self, model: str) -> List[str]:
437
+ """Returns the paste-ready error when litellm cannot infer a provider.
438
+
439
+ A custom ``api_base`` is left to the user's endpoint, so no resolution
440
+ runs there. The library prints its provider list to stdout on failure,
441
+ so that output is captured and never reaches the CLI. The list shape lets
442
+ the caller extend unconditionally and keeps its branching flat.
443
+ """
444
+ import contextlib
445
+ import io
446
+
447
+ if self._config_service.get_setting("llm.api_base"):
448
+ return []
449
+
450
+ litellm = self._get_litellm()
451
+ try:
452
+ with contextlib.redirect_stdout(io.StringIO()):
453
+ litellm.get_llm_provider(model=model)
454
+ except Exception:
455
+ # A resolution failure is the signal; the library raises several
456
+ # exception types depending on the model string and provider table.
457
+ return [
458
+ f"Cannot infer a provider for model '{model}'. "
459
+ "Change llm.model in .teddy/config.yaml to prefix it with the "
460
+ f"provider, e.g. 'openai/{model}'."
461
+ ]
462
+ return []
463
+
464
+ def _remote_check_errors(
465
+ self,
466
+ model: str,
467
+ api_key: Any,
468
+ include_remote: bool,
469
+ is_api_key_provided: bool,
470
+ ) -> List[str]:
471
+ """Validates the key against the provider when a remote check is due.
472
+
473
+ Returns zero or one error. A missing request, an absent key, or a valid
474
+ key all yield an empty list, so the caller can extend unconditionally and
475
+ the request path stays flat.
476
+ """
477
+ if not (include_remote and is_api_key_provided):
478
+ return []
479
+
480
+ from concurrent.futures import TimeoutError
481
+
482
+ litellm = self._get_litellm()
483
+ executor = self._get_executor()
484
+ future = executor.submit(litellm.check_valid_key, model=model, api_key=api_key)
485
+ try:
486
+ is_valid = future.result(timeout=10.0)
487
+ if not is_valid:
488
+ return ["The API key appears to be invalid, expired, or deactivated."]
489
+ except TimeoutError:
490
+ return ["The remote connectivity check timed out after 10 seconds."]
491
+ except Exception as e:
492
+ return [f"Remote connectivity check failed: {str(e)}"]
493
+ return []
494
+
440
495
  def get_context_window(self, model: Optional[str] = None) -> int:
441
496
  """
442
497
  Returns the maximum context window size (tokens) for the specified model.
@@ -14,4 +14,3 @@ class SessionOptions:
14
14
  additional_context: list[str] = field(default_factory=list)
15
15
  model: Optional[str] = None
16
16
  provider: Optional[str] = None
17
- api_key: Optional[str] = None
@@ -111,7 +111,6 @@ class PlanningService(IPlanningUseCase):
111
111
  response, plan_content, turn_cost = self._perform_generation_with_retry(
112
112
  messages,
113
113
  model=model,
114
- api_key=meta.get("api_key"),
115
114
  )
116
115
 
117
116
  cost_val = self._prompt_manager.log_telemetry(token_count, turn_cost)
@@ -132,7 +131,6 @@ class PlanningService(IPlanningUseCase):
132
131
  self,
133
132
  messages: list[Dict[str, str]],
134
133
  model: str,
135
- api_key: Optional[str] = None,
136
134
  ) -> tuple[Any, str, float]:
137
135
  """Implements retry loop for empty LLM content."""
138
136
  max_retries_val = self._config_service.get_setting("llm.max_retries")
@@ -141,15 +139,8 @@ class PlanningService(IPlanningUseCase):
141
139
  plan_content = ""
142
140
  turn_cost = 0.0
143
141
 
144
- # Construct overrides dict for kwargs
145
- overrides = {}
146
- if api_key:
147
- overrides["api_key"] = api_key
148
-
149
142
  for attempt in range(max_retries):
150
- response = self._llm_client.get_completion(
151
- messages=messages, model=model, **overrides
152
- )
143
+ response = self._llm_client.get_completion(messages=messages, model=model)
153
144
  plan_content = self._extract_plan_content(response)
154
145
  attempt_cost = self._llm_client.get_completion_cost(
155
146
  response, model_override=model
@@ -137,8 +137,6 @@ class SessionService(ISessionManager):
137
137
  }
138
138
  if options.model:
139
139
  meta_data["model"] = options.model
140
- if options.api_key:
141
- meta_data["api_key"] = options.api_key
142
140
  return meta_data
143
141
 
144
142
  def _resolve_agent_prompt(self, agent_name: str) -> tuple[str, str]:
@@ -486,7 +484,7 @@ class SessionService(ISessionManager):
486
484
  }
487
485
 
488
486
  # Carry over LLM overrides and actual_model for display continuity
489
- for key in ["model", "actual_model", "provider", "api_key"]:
487
+ for key in ["model", "actual_model", "provider"]:
490
488
  if key in current_meta:
491
489
  meta[key] = current_meta[key]
492
490
 
@@ -46,6 +46,9 @@ llm:
46
46
  X-OpenRouter-Title: "TeDDy"
47
47
  X-OpenRouter-Categories: "cli-agent"
48
48
 
49
+ # llm.model must start with the provider prefix, e.g. "openai/qwen3-coder-30b".
50
+ # A bare model name fails preflight, because litellm cannot infer the provider.
51
+
49
52
  # Local models (Ollama, LM Studio):
50
53
  # model: "ollama/qwen3-coder:30b"
51
54
  # model: "lm_studio/qwen3-coder-30b"