diff --git a/include/copilot/session.hpp b/include/copilot/session.hpp index b9421c8..654e608 100644 --- a/include/copilot/session.hpp +++ b/include/copilot/session.hpp @@ -151,6 +151,15 @@ class Session : public std::enable_shared_from_this /// @return Future that resolves to list of session events std::future> get_messages(); + /// Send a message and wait until the session becomes idle. + /// @param options Message options including prompt and attachments + /// @param timeout Maximum time to wait (default: 60 seconds) + /// @return Future that resolves to the final assistant message, or nullopt if none + /// @throws std::runtime_error if timeout is reached or session error occurs + std::future> send_and_wait( + MessageOptions options, + std::chrono::seconds timeout = std::chrono::seconds(60)); + // ========================================================================= // Event Handling // ========================================================================= diff --git a/include/copilot/types.hpp b/include/copilot/types.hpp index cec8f07..c918310 100644 --- a/include/copilot/types.hpp +++ b/include/copilot/types.hpp @@ -605,6 +605,10 @@ struct SessionConfig /// When enabled (default), sessions automatically manage context limits and persist state. std::optional infinite_sessions; + /// Custom configuration directory for the CLI. + /// When set, overrides the default config location. + std::optional config_dir; + /// If true and provider/model not explicitly set, load from COPILOT_SDK_BYOK_* env vars. /// Default: false (explicit configuration preferred over environment variables) bool auto_byok_from_env = false; @@ -626,6 +630,10 @@ struct ResumeSessionConfig /// List of skill names to disable. std::optional> disabled_skills; + /// Custom configuration directory for the CLI. + /// When set, overrides the default config location. + std::optional config_dir; + /// If true and provider not explicitly set, load from COPILOT_SDK_BYOK_* env vars. /// Default: false (explicit configuration preferred over environment variables) bool auto_byok_from_env = false; diff --git a/src/client.cpp b/src/client.cpp index d4deb5d..98ac52d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -93,6 +93,8 @@ json build_session_create_request(const SessionConfig& config) request["disabledSkills"] = *config.disabled_skills; if (config.infinite_sessions.has_value()) request["infiniteSessions"] = *config.infinite_sessions; + if (config.config_dir.has_value()) + request["configDir"] = *config.config_dir; return request; } @@ -146,6 +148,8 @@ json build_session_resume_request(const std::string& session_id, const ResumeSes request["skillDirectories"] = *config.skill_directories; if (config.disabled_skills.has_value()) request["disabledSkills"] = *config.disabled_skills; + if (config.config_dir.has_value()) + request["configDir"] = *config.config_dir; return request; } diff --git a/src/session.cpp b/src/session.cpp index 27190ab..842818e 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace copilot { @@ -83,6 +84,68 @@ std::future> Session::get_messages() ); } +std::future> Session::send_and_wait( + MessageOptions options, + std::chrono::seconds timeout) +{ + return std::async( + std::launch::async, + [this, options = std::move(options), timeout]() -> std::optional + { + std::mutex mtx; + std::condition_variable cv; + bool done = false; + std::optional last_assistant_message; + std::optional error_message; + + // Subscribe to events + auto subscription = on( + [&](const SessionEvent& evt) + { + std::lock_guard lock(mtx); + if (evt.type == SessionEventType::AssistantMessage) + { + last_assistant_message = evt; + } + else if (evt.type == SessionEventType::SessionIdle) + { + done = true; + cv.notify_one(); + } + else if (evt.type == SessionEventType::SessionError) + { + if (auto* data = evt.try_as()) + error_message = data->message; + else + error_message = "Session error"; + done = true; + cv.notify_one(); + } + } + ); + + // Send the message + send(options).get(); + + // Wait for completion or timeout + { + std::unique_lock lock(mtx); + if (!cv.wait_for(lock, timeout, [&] { return done; })) + { + throw std::runtime_error("Timeout waiting for session to become idle"); + } + } + + if (error_message.has_value()) + { + throw std::runtime_error("Session error: " + *error_message); + } + + return last_assistant_message; + } + ); +} + // ============================================================================= // Event Handling // =============================================================================