Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/copilot/session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ class Session : public std::enable_shared_from_this<Session>
/// @return Future that resolves to list of session events
std::future<std::vector<SessionEvent>> 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<std::optional<SessionEvent>> send_and_wait(
MessageOptions options,
std::chrono::seconds timeout = std::chrono::seconds(60));

// =========================================================================
// Event Handling
// =========================================================================
Expand Down
8 changes: 8 additions & 0 deletions include/copilot/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,10 @@ struct SessionConfig
/// When enabled (default), sessions automatically manage context limits and persist state.
std::optional<InfiniteSessionConfig> infinite_sessions;

/// Custom configuration directory for the CLI.
/// When set, overrides the default config location.
std::optional<std::string> 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;
Expand All @@ -626,6 +630,10 @@ struct ResumeSessionConfig
/// List of skill names to disable.
std::optional<std::vector<std::string>> disabled_skills;

/// Custom configuration directory for the CLI.
/// When set, overrides the default config location.
std::optional<std::string> 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;
Expand Down
4 changes: 4 additions & 0 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
63 changes: 63 additions & 0 deletions src/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <copilot/client.hpp>
#include <copilot/session.hpp>
#include <condition_variable>

namespace copilot
{
Expand Down Expand Up @@ -83,6 +84,68 @@ std::future<std::vector<SessionEvent>> Session::get_messages()
);
}

std::future<std::optional<SessionEvent>> Session::send_and_wait(
MessageOptions options,
std::chrono::seconds timeout)
{
return std::async(
std::launch::async,
[this, options = std::move(options), timeout]() -> std::optional<SessionEvent>
{
std::mutex mtx;
std::condition_variable cv;
bool done = false;
std::optional<SessionEvent> last_assistant_message;
std::optional<std::string> error_message;

// Subscribe to events
auto subscription = on(
[&](const SessionEvent& evt)
{
std::lock_guard<std::mutex> 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<SessionErrorData>())
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<std::mutex> 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
// =============================================================================
Expand Down