Skip to content

Commit 257e0e3

Browse files
authored
Merge pull request #4 from 0xeb/feat/v0.1.18-parity
feat: add configDir and sendAndWait for v0.1.18 parity
2 parents fdf5bed + 3c7007a commit 257e0e3

4 files changed

Lines changed: 84 additions & 0 deletions

File tree

include/copilot/session.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@ class Session : public std::enable_shared_from_this<Session>
151151
/// @return Future that resolves to list of session events
152152
std::future<std::vector<SessionEvent>> get_messages();
153153

154+
/// Send a message and wait until the session becomes idle.
155+
/// @param options Message options including prompt and attachments
156+
/// @param timeout Maximum time to wait (default: 60 seconds)
157+
/// @return Future that resolves to the final assistant message, or nullopt if none
158+
/// @throws std::runtime_error if timeout is reached or session error occurs
159+
std::future<std::optional<SessionEvent>> send_and_wait(
160+
MessageOptions options,
161+
std::chrono::seconds timeout = std::chrono::seconds(60));
162+
154163
// =========================================================================
155164
// Event Handling
156165
// =========================================================================

include/copilot/types.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,10 @@ struct SessionConfig
605605
/// When enabled (default), sessions automatically manage context limits and persist state.
606606
std::optional<InfiniteSessionConfig> infinite_sessions;
607607

608+
/// Custom configuration directory for the CLI.
609+
/// When set, overrides the default config location.
610+
std::optional<std::string> config_dir;
611+
608612
/// If true and provider/model not explicitly set, load from COPILOT_SDK_BYOK_* env vars.
609613
/// Default: false (explicit configuration preferred over environment variables)
610614
bool auto_byok_from_env = false;
@@ -626,6 +630,10 @@ struct ResumeSessionConfig
626630
/// List of skill names to disable.
627631
std::optional<std::vector<std::string>> disabled_skills;
628632

633+
/// Custom configuration directory for the CLI.
634+
/// When set, overrides the default config location.
635+
std::optional<std::string> config_dir;
636+
629637
/// If true and provider not explicitly set, load from COPILOT_SDK_BYOK_* env vars.
630638
/// Default: false (explicit configuration preferred over environment variables)
631639
bool auto_byok_from_env = false;

src/client.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ json build_session_create_request(const SessionConfig& config)
9393
request["disabledSkills"] = *config.disabled_skills;
9494
if (config.infinite_sessions.has_value())
9595
request["infiniteSessions"] = *config.infinite_sessions;
96+
if (config.config_dir.has_value())
97+
request["configDir"] = *config.config_dir;
9698

9799
return request;
98100
}
@@ -146,6 +148,8 @@ json build_session_resume_request(const std::string& session_id, const ResumeSes
146148
request["skillDirectories"] = *config.skill_directories;
147149
if (config.disabled_skills.has_value())
148150
request["disabledSkills"] = *config.disabled_skills;
151+
if (config.config_dir.has_value())
152+
request["configDir"] = *config.config_dir;
149153

150154
return request;
151155
}

src/session.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <copilot/client.hpp>
55
#include <copilot/session.hpp>
6+
#include <condition_variable>
67

78
namespace copilot
89
{
@@ -83,6 +84,68 @@ std::future<std::vector<SessionEvent>> Session::get_messages()
8384
);
8485
}
8586

87+
std::future<std::optional<SessionEvent>> Session::send_and_wait(
88+
MessageOptions options,
89+
std::chrono::seconds timeout)
90+
{
91+
return std::async(
92+
std::launch::async,
93+
[this, options = std::move(options), timeout]() -> std::optional<SessionEvent>
94+
{
95+
std::mutex mtx;
96+
std::condition_variable cv;
97+
bool done = false;
98+
std::optional<SessionEvent> last_assistant_message;
99+
std::optional<std::string> error_message;
100+
101+
// Subscribe to events
102+
auto subscription = on(
103+
[&](const SessionEvent& evt)
104+
{
105+
std::lock_guard<std::mutex> lock(mtx);
106+
if (evt.type == SessionEventType::AssistantMessage)
107+
{
108+
last_assistant_message = evt;
109+
}
110+
else if (evt.type == SessionEventType::SessionIdle)
111+
{
112+
done = true;
113+
cv.notify_one();
114+
}
115+
else if (evt.type == SessionEventType::SessionError)
116+
{
117+
if (auto* data = evt.try_as<SessionErrorData>())
118+
error_message = data->message;
119+
else
120+
error_message = "Session error";
121+
done = true;
122+
cv.notify_one();
123+
}
124+
}
125+
);
126+
127+
// Send the message
128+
send(options).get();
129+
130+
// Wait for completion or timeout
131+
{
132+
std::unique_lock<std::mutex> lock(mtx);
133+
if (!cv.wait_for(lock, timeout, [&] { return done; }))
134+
{
135+
throw std::runtime_error("Timeout waiting for session to become idle");
136+
}
137+
}
138+
139+
if (error_message.has_value())
140+
{
141+
throw std::runtime_error("Session error: " + *error_message);
142+
}
143+
144+
return last_assistant_message;
145+
}
146+
);
147+
}
148+
86149
// =============================================================================
87150
// Event Handling
88151
// =============================================================================

0 commit comments

Comments
 (0)