Changes
diff --git a/demo/main.cpp b/demo/main.cpp
index 3e55e93..21fc201 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -1,12 +1,263 @@
+#include <cstdlib>
#include <iostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
#include <agent_smith/agent_smith.hpp>
-int main()
+namespace
{
- [[maybe_unused]] agent_smith::Runtime runtime;
- [[maybe_unused]] agent_smith::Agent agent;
- std::cout << "Agent Smith " << agent_smith::VERSION << '\n';
+std::string envOr(std::string_view name, std::string_view fallback)
+{
+ const char* value = std::getenv(std::string(name).c_str());
+ return value == nullptr ? std::string(fallback) : std::string(value);
+}
+
+void printUsage(std::ostream& output)
+{
+ output << "Usage: agent_smith_demo\n"
+ << "\n"
+ << "Environment variables:\n"
+ << " LLAMA_SERVER_URL Provider origin "
+ "(default: http://127.0.0.1:8080)\n"
+ << " LLAMA_MODEL Model name (default: local-model)\n"
+ << " LLAMA_API_KEY Optional bearer token\n"
+ << "\n"
+ << "The demo provides a tool for current weather by city.\n"
+ << "\n"
+ << "Commands: /help, /quit\n";
+}
+
+agent_smith::ToolSchema weatherSchema()
+{
+ return agent_smith::ToolSchema{
+ .name = "get_weather",
+ .description = "Get the current weather for a city.",
+ .parameters =
+ {
+ {"type", "object"},
+ {"properties", {{"city", {{"type", "string"}}}}},
+ {"required", {"city"}},
+ {"additionalProperties", false},
+ },
+ };
+}
+
+mw::Task<mw::E<agent_smith::ToolResult>>
+queryWeather(nlohmann::json arguments, agent_smith::ToolContext context)
+{
+ if(context.runtime == nullptr)
+ {
+ co_return std::unexpected(
+ mw::runtimeError("weather tool needs a runtime"));
+ }
+
+ const auto city = arguments.find("city");
+ if(city == arguments.end() || !city->is_string() || city->empty())
+ {
+ co_return std::unexpected(
+ mw::runtimeError("weather tool requires a non-empty city"));
+ }
+
+ const std::string city_name = *city;
+ const std::string url =
+ "https://wttr.in/" + mw::URL::encode(city_name) + "?format=3";
+ auto response =
+ co_await context.runtime->http().get(mw::HTTPRequest(url));
+ if(!response)
+ {
+ co_return std::unexpected(response.error());
+ }
+ if(response->status != 200)
+ {
+ co_return std::unexpected(
+ mw::runtimeError("weather service returned HTTP " +
+ std::to_string(response->status)));
+ }
+
+ co_return agent_smith::ToolResult{
+ .content = std::string(response->payloadAsStr()),
+ };
+}
+
+void appendToolResults(std::vector<agent_smith::Message>& messages,
+ agent_smith::ToolRegistry& tools,
+ agent_smith::Runtime& runtime,
+ const std::vector<agent_smith::ToolCall>& calls)
+{
+ for(const agent_smith::ToolCall& call : calls)
+ {
+ agent_smith::Tool* tool = tools.find(call.name);
+ if(tool == nullptr)
+ {
+ const std::string content = "unknown tool: " + call.name;
+ std::cout << "[tool error: " << content << "]\n";
+ messages.emplace_back(agent_smith::ToolMessage{
+ .tool_call_id = call.id,
+ .name = call.name,
+ .content = content,
+ .is_error = true,
+ });
+ continue;
+ }
+
+ auto result = syncWait(tool->invoke(
+ call.arguments,
+ agent_smith::ToolContext{
+ .call_id = call.id,
+ .runtime = &runtime,
+ }));
+ if(!result)
+ {
+ std::cout << "[tool error: " << result.error().msg() << "]\n";
+ messages.emplace_back(agent_smith::ToolMessage{
+ .tool_call_id = call.id,
+ .name = call.name,
+ .content = result.error().msg(),
+ .is_error = true,
+ });
+ continue;
+ }
+
+ std::cout << "[tool result: " << result->content << "]\n";
+ messages.emplace_back(agent_smith::ToolMessage{
+ .tool_call_id = call.id,
+ .name = call.name,
+ .content = result->content,
+ .is_error = result->is_error,
+ });
+ }
+}
+
+void printStreamEvent(const agent_smith::StreamEvent& event)
+{
+ std::visit(
+ [](const auto& value)
+ {
+ using Event = std::decay_t<decltype(value)>;
+ if constexpr(std::is_same_v<Event, agent_smith::StreamTokenDelta>)
+ {
+ std::cout << value.text << std::flush;
+ }
+ else if constexpr(std::is_same_v<
+ Event, agent_smith::StreamToolCallStarted>)
+ {
+ std::cout << "\n[tool call " << value.index << ": "
+ << value.name << "]\n";
+ }
+ else if constexpr(std::is_same_v<
+ Event, agent_smith::StreamToolCallCompleted>)
+ {
+ std::cout << "[tool arguments " << value.index << ": "
+ << value.arguments.dump() << "]\n";
+ }
+ else
+ {
+ std::cout << '\n';
+ }
+ },
+ event);
+}
+
+} // namespace
+
+int main(int argc, char* argv[])
+{
+ if(argc > 1 && std::string_view(argv[1]) == "--help")
+ {
+ printUsage(std::cout);
+ return 0;
+ }
+
+ const std::string endpoint_text =
+ envOr("LLAMA_SERVER_URL", "http://127.0.0.1:8080");
+ auto endpoint = mw::URL::fromStr(endpoint_text);
+ if(!endpoint)
+ {
+ std::cerr << "Invalid LLAMA_SERVER_URL: " << endpoint.error().msg()
+ << '\n';
+ return 2;
+ }
+
+ agent_smith::LlmClientConfig config{
+ .endpoint = std::move(*endpoint),
+ .model = envOr("LLAMA_MODEL", "local-model"),
+ };
+ if(const char* api_key = std::getenv("LLAMA_API_KEY"); api_key != nullptr)
+ {
+ config.api_key = api_key;
+ }
+
+ agent_smith::Runtime runtime;
+ agent_smith::LlmClient client(runtime, std::move(config));
+ agent_smith::ToolRegistry tools;
+ tools.add(agent_smith::makeTool(weatherSchema(), queryWeather));
+ std::vector<agent_smith::Message> messages;
+ std::cout << "Agent Smith " << agent_smith::VERSION
+ << " provider test CLI. Enter /help for commands.\n";
+
+ std::string prompt;
+ while(std::cout << "> " << std::flush, std::getline(std::cin, prompt))
+ {
+ if(prompt == "/quit")
+ {
+ break;
+ }
+ if(prompt == "/help")
+ {
+ printUsage(std::cout);
+ continue;
+ }
+ if(prompt.empty())
+ {
+ continue;
+ }
+
+ const std::size_t message_count = messages.size();
+ messages.emplace_back(agent_smith::UserMessage{.content = prompt});
+ bool request_failed = false;
+ for(std::size_t iteration = 0; iteration < 4; ++iteration)
+ {
+ auto result = syncWait(client.completeStream(
+ {
+ .messages = messages,
+ .tools = tools.schemas(),
+ },
+ printStreamEvent));
+ if(!result)
+ {
+ std::cerr << "\nRequest failed: " << result.error().msg()
+ << '\n';
+ request_failed = true;
+ break;
+ }
+
+ messages.emplace_back(std::move(result->message));
+ const auto& assistant =
+ std::get<agent_smith::AssistantMessage>(messages.back());
+ if(assistant.usage)
+ {
+ std::cout << "[usage: prompt=" << assistant.usage->prompt
+ << ", completion=" << assistant.usage->completion
+ << ", total=" << assistant.usage->total << "]\n";
+ }
+ if(assistant.tool_calls.empty())
+ {
+ break;
+ }
+
+ appendToolResults(messages, tools, runtime, assistant.tool_calls);
+ }
+ if(request_failed)
+ {
+ messages.resize(message_count);
+ }
+ }
+
return 0;
}
diff --git a/designs/design-0-framework.md b/designs/design-0-framework.md
index 74769ca..956968a 100644
--- a/designs/design-0-framework.md
+++ b/designs/design-0-framework.md
@@ -197,7 +197,7 @@ Agent Smith is a single static (or shared) library `libagent_smith`
plus a small CLI demo binary `agent_smith_demo`. The library exposes
one public include directory (`include/agent_smith/`) and a single
namespace `agent_smith`. Internal helpers live in
-`agent_smith::detail` and are not part of the public API.
+`agent_smith::internal` and are not part of the public API.
The library is composed of nine subsystems, each owning one
sub-namespace and one or two header files:
@@ -393,7 +393,7 @@ public:
// Run non-HTTP blocking work on the internal worker pool and return
// an awaitable result. Do not use for HTTP.
template <typename F>
- mw::Task<detail::ExpectedResult<std::invoke_result_t<F>>>
+ mw::Task<internal::ExpectedResult<std::invoke_result_t<F>>>
runOnWorker(F&& f);
// Coroutine-friendly timer used for retry backoff and internal
@@ -403,7 +403,7 @@ public:
private:
RuntimeConfig cfg_;
mw::HTTPSessionAsync http_;
- detail::BlockingPool blocking_pool_;
+ internal::BlockingPool blocking_pool_;
};
} // namespace agent_smith
@@ -411,14 +411,14 @@ private:
### 5.3 Blocking runner
-`detail::BlockingPool` is a private utility implemented with
+`internal::BlockingPool` is a private utility implemented with
`std::jthread`, `std::mutex`, `std::condition_variable`, and a FIFO work
queue. Subsystems do not access it directly; they call
`Runtime::runOnWorker`:
```cpp
template <typename F>
-mw::Task<detail::ExpectedResult<std::invoke_result_t<F>>>
+mw::Task<internal::ExpectedResult<std::invoke_result_t<F>>>
Runtime::runOnWorker(F&& f);
```
@@ -427,7 +427,7 @@ returns an `mw::E<T>` directly or a plain value wrapped by the
adapter. The first implementation may support only the exact shapes
needed by synchronous tools and stdio transport.
-`detail::ExpectedResult<T>` normalizes the return type:
+`internal::ExpectedResult<T>` normalizes the return type:
```cpp
T -> mw::E<T>
@@ -2077,7 +2077,7 @@ Hook failures are not converted into agent, tool, MCP, or LLM failures.
### 16.4 Redaction
Before any header dictionary or body string is logged, run it
-through `agent_smith::detail::redact`:
+through `agent_smith::internal::redact`:
* Replace any value of header keys equal (case-insensitive) to
`Authorization`, `X-Api-Key`, `Api-Key`, `Cookie`, or
diff --git a/include/agent_smith/agent_smith.hpp b/include/agent_smith/agent_smith.hpp
index d1f3c2d..76b7701 100644
--- a/include/agent_smith/agent_smith.hpp
+++ b/include/agent_smith/agent_smith.hpp
@@ -7,5 +7,6 @@
#include "message.hpp"
#include "runtime.hpp"
#include "stream_event.hpp"
+#include "sync_wait.hpp"
#include "tool.hpp"
#include "version.hpp"
diff --git a/include/agent_smith/detail/async_queue.hpp b/include/agent_smith/detail/async_queue.hpp
index b888371..53972e6 100644
--- a/include/agent_smith/detail/async_queue.hpp
+++ b/include/agent_smith/detail/async_queue.hpp
@@ -9,7 +9,7 @@
#include <mw/task.hpp>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
/// A closeable queue that resumes consumers when another thread pushes data.
@@ -143,4 +143,4 @@ private:
bool closed_ = false;
};
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/include/agent_smith/detail/blocking_pool.hpp b/include/agent_smith/detail/blocking_pool.hpp
index 64b2e9a..2a63a03 100644
--- a/include/agent_smith/detail/blocking_pool.hpp
+++ b/include/agent_smith/detail/blocking_pool.hpp
@@ -18,7 +18,7 @@
#include <mw/error.hpp>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
template <typename T> struct ExpectedResultType
@@ -215,4 +215,4 @@ template <typename F> auto BlockingPool::run(F&& function)
return BlockingAwaiter<Result, Function>(std::move(work));
}
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/include/agent_smith/detail/gather_all.hpp b/include/agent_smith/detail/gather_all.hpp
index edcfced..83a8b78 100644
--- a/include/agent_smith/detail/gather_all.hpp
+++ b/include/agent_smith/detail/gather_all.hpp
@@ -7,7 +7,7 @@
#include "started_task.hpp"
-namespace agent_smith::detail
+namespace agent_smith::internal
{
/// Await started tasks in input order without changing their concurrency.
@@ -39,4 +39,4 @@ mw::Task<std::vector<mw::E<T>>> gatherAll(std::vector<mw::Task<mw::E<T>>> tasks)
co_return co_await gatherAllStarted(std::move(started));
}
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/include/agent_smith/detail/sse_parser.hpp b/include/agent_smith/detail/sse_parser.hpp
index 0423d41..e30e52b 100644
--- a/include/agent_smith/detail/sse_parser.hpp
+++ b/include/agent_smith/detail/sse_parser.hpp
@@ -4,7 +4,7 @@
#include <string_view>
#include <vector>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
/// Incrementally extracts SSE data payloads from arbitrary byte chunks.
@@ -22,4 +22,4 @@ private:
bool finished_ = false;
};
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/include/agent_smith/detail/started_task.hpp b/include/agent_smith/detail/started_task.hpp
index d19fe8e..cf98a77 100644
--- a/include/agent_smith/detail/started_task.hpp
+++ b/include/agent_smith/detail/started_task.hpp
@@ -10,7 +10,7 @@
#include <mw/error.hpp>
#include <mw/task.hpp>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
/// An internal eager wrapper around one mw::Task<mw::E<T>> operation.
@@ -180,4 +180,4 @@ private:
std::coroutine_handle<promise_type> handle_ = nullptr;
};
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/include/agent_smith/runtime.hpp b/include/agent_smith/runtime.hpp
index 479bf32..b687f8c 100644
--- a/include/agent_smith/runtime.hpp
+++ b/include/agent_smith/runtime.hpp
@@ -9,7 +9,7 @@
#include "detail/blocking_pool.hpp"
-namespace agent_smith::detail
+namespace agent_smith::internal
{
class TimerService;
}
@@ -51,7 +51,7 @@ public:
/// Run non-HTTP work on the internal worker pool.
template <typename F>
auto runOnWorker(F&& function)
- -> mw::Task<detail::ExpectedResult<std::invoke_result_t<F&>>>;
+ -> mw::Task<internal::ExpectedResult<std::invoke_result_t<F&>>>;
/// Suspend without blocking the caller's coroutine thread.
mw::Task<mw::E<void>> sleepFor(std::chrono::milliseconds duration);
@@ -59,13 +59,13 @@ public:
private:
RuntimeConfig cfg_;
std::unique_ptr<mw::HTTPSessionAsync> http_;
- std::unique_ptr<detail::BlockingPool> blocking_pool_;
- std::unique_ptr<detail::TimerService> timer_service_;
+ std::unique_ptr<internal::BlockingPool> blocking_pool_;
+ std::unique_ptr<internal::TimerService> timer_service_;
};
template <typename F>
auto Runtime::runOnWorker(F&& function)
- -> mw::Task<detail::ExpectedResult<std::invoke_result_t<F&>>>
+ -> mw::Task<internal::ExpectedResult<std::invoke_result_t<F&>>>
{
co_return co_await blocking_pool_->run(std::forward<F>(function));
}
diff --git a/include/agent_smith/sync_wait.hpp b/include/agent_smith/sync_wait.hpp
new file mode 100644
index 0000000..e250c82
--- /dev/null
+++ b/include/agent_smith/sync_wait.hpp
@@ -0,0 +1,136 @@
+#pragma once
+
+#include <condition_variable>
+#include <coroutine>
+#include <exception>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+#include <mw/task.hpp>
+
+namespace agent_smith
+{
+namespace internal
+{
+
+template <typename T> class SyncWaitTask
+{
+public:
+ struct promise_type
+ {
+ SyncWaitTask get_return_object()
+ {
+ return SyncWaitTask(
+ std::coroutine_handle<promise_type>::from_promise(*this));
+ }
+
+ std::suspend_always initial_suspend() noexcept
+ {
+ return {};
+ }
+
+ auto final_suspend() noexcept
+ {
+ struct FinalAwaiter
+ {
+ bool await_ready() noexcept
+ {
+ return false;
+ }
+
+ void await_suspend(
+ std::coroutine_handle<promise_type> handle) noexcept
+ {
+ auto& promise = handle.promise();
+ std::lock_guard lock(promise.mutex);
+ promise.done = true;
+ promise.condition.notify_one();
+ }
+
+ void await_resume() noexcept {}
+ };
+
+ return FinalAwaiter{};
+ }
+
+ template <typename U> void return_value(U&& value)
+ {
+ value_.emplace(std::forward<U>(value));
+ }
+
+ void unhandled_exception()
+ {
+ exception_ = std::current_exception();
+ }
+
+ std::mutex mutex;
+ std::condition_variable condition;
+ bool done = false;
+ std::optional<T> value_;
+ std::exception_ptr exception_;
+ };
+
+ explicit SyncWaitTask(std::coroutine_handle<promise_type> handle)
+ : handle_(handle)
+ {
+ }
+
+ SyncWaitTask(SyncWaitTask&& other) noexcept
+ : handle_(std::exchange(other.handle_, nullptr))
+ {
+ }
+
+ SyncWaitTask(const SyncWaitTask&) = delete;
+ SyncWaitTask& operator=(const SyncWaitTask&) = delete;
+
+ ~SyncWaitTask()
+ {
+ if(handle_ != nullptr)
+ {
+ handle_.destroy();
+ }
+ }
+
+ void start()
+ {
+ handle_.resume();
+ }
+
+ T get()
+ {
+ auto& promise = handle_.promise();
+ std::unique_lock lock(promise.mutex);
+ promise.condition.wait(lock, [&]() { return promise.done; });
+ lock.unlock();
+
+ if(promise.exception_ != nullptr)
+ {
+ std::rethrow_exception(promise.exception_);
+ }
+ return std::move(*promise.value_);
+ }
+
+private:
+ std::coroutine_handle<promise_type> handle_ = nullptr;
+};
+
+template <typename T> SyncWaitTask<T> makeSyncWaiter(mw::Task<T> task)
+{
+ co_return co_await std::move(task);
+}
+
+} // namespace internal
+
+/// Start a task and block this thread until its result is available.
+///
+/// Use only at a synchronous application boundary or in tests. Do not call
+/// this from a thread that must run work required by the task to complete.
+template <typename T> T syncWait(mw::Task<T> task)
+{
+ auto waiter = internal::makeSyncWaiter(std::move(task));
+ waiter.start();
+ return waiter.get();
+}
+
+} // namespace agent_smith
diff --git a/include/agent_smith/tool.hpp b/include/agent_smith/tool.hpp
index cf4d50d..1c79a05 100644
--- a/include/agent_smith/tool.hpp
+++ b/include/agent_smith/tool.hpp
@@ -142,7 +142,7 @@ concept AsyncToolCallable =
} -> std::same_as<mw::Task<mw::E<ToolResult>>>;
};
-namespace detail
+namespace internal
{
inline void validateToolSchema(const ToolSchema& schema)
@@ -234,22 +234,22 @@ private:
F function_;
};
-} // namespace detail
+} // namespace internal
/// Adapt a synchronous callable so it runs on the runtime's worker pool.
template <SyncToolCallable F>
std::unique_ptr<Tool> makeTool(ToolSchema schema, F function)
{
- return std::make_unique<detail::SyncToolAdapter<F>>(std::move(schema),
- std::move(function));
+ return std::make_unique<internal::SyncToolAdapter<F>>(std::move(schema),
+ std::move(function));
}
/// Adapt an asynchronous callable without moving it to a worker thread.
template <AsyncToolCallable F>
std::unique_ptr<Tool> makeTool(ToolSchema schema, F function)
{
- return std::make_unique<detail::AsyncToolAdapter<F>>(std::move(schema),
- std::move(function));
+ return std::make_unique<internal::AsyncToolAdapter<F>>(std::move(schema),
+ std::move(function));
}
/// Owns named tools and their options in deterministic insertion order.
diff --git a/src/detail/blocking_pool.cpp b/src/detail/blocking_pool.cpp
index 71209da..345468c 100644
--- a/src/detail/blocking_pool.cpp
+++ b/src/detail/blocking_pool.cpp
@@ -2,7 +2,7 @@
#include <algorithm>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
BlockingPool::BlockingPool(std::size_t size)
@@ -75,4 +75,4 @@ void BlockingPool::worker(std::stop_token stop)
}
}
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/src/detail/timer_service.cpp b/src/detail/timer_service.cpp
index cfadf11..021f166 100644
--- a/src/detail/timer_service.cpp
+++ b/src/detail/timer_service.cpp
@@ -1,6 +1,6 @@
#include "timer_service.hpp"
-namespace agent_smith::detail
+namespace agent_smith::internal
{
TimerService::TimerService()
@@ -87,4 +87,4 @@ void TimerService::worker(std::stop_token stop)
}
}
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/src/detail/timer_service.hpp b/src/detail/timer_service.hpp
index 96f847b..12bd2c2 100644
--- a/src/detail/timer_service.hpp
+++ b/src/detail/timer_service.hpp
@@ -10,7 +10,7 @@
#include <thread>
#include <vector>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
class TimerService
@@ -71,4 +71,4 @@ private:
std::jthread worker_;
};
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/src/llm_client.cpp b/src/llm_client.cpp
index 0ec5eb7..739ff83 100644
--- a/src/llm_client.cpp
+++ b/src/llm_client.cpp
@@ -517,9 +517,9 @@ private:
mw::Task<mw::E<mw::HTTPResponse>>
streamRequest(mw::HTTPSessionAsyncInterface& http, mw::HTTPRequest request,
- detail::SseFrameParser& frame_parser,
+ internal::SseFrameParser& frame_parser,
StreamDeltaParser& delta_parser,
- detail::AsyncQueue<StreamEvent>& queue)
+ internal::AsyncQueue<StreamEvent>& queue)
{
std::optional<mw::Error> callback_error;
auto response = co_await http.postStream(
@@ -787,10 +787,10 @@ LlmClient::completeStream(LlmRequest request,
LlmClientConfig request_config = config_;
request_config.endpoint = std::move(*endpoint);
- detail::SseFrameParser frame_parser;
+ internal::SseFrameParser frame_parser;
StreamDeltaParser delta_parser;
- detail::AsyncQueue<StreamEvent> queue;
- auto request_task = detail::StartedTask<mw::HTTPResponse>::start(
+ internal::AsyncQueue<StreamEvent> queue;
+ auto request_task = internal::StartedTask<mw::HTTPResponse>::start(
streamRequest(*http_, buildHttpRequest(*body, request_config),
frame_parser, delta_parser, queue));
diff --git a/src/runtime.cpp b/src/runtime.cpp
index 3f2dd0a..4310ca4 100644
--- a/src/runtime.cpp
+++ b/src/runtime.cpp
@@ -7,9 +7,9 @@ namespace agent_smith
Runtime::Runtime(RuntimeConfig cfg)
: cfg_(cfg), http_(std::make_unique<mw::HTTPSessionAsync>()),
- blocking_pool_(std::make_unique<detail::BlockingPool>(
+ blocking_pool_(std::make_unique<internal::BlockingPool>(
cfg_.blocking_pool_size)),
- timer_service_(std::make_unique<detail::TimerService>())
+ timer_service_(std::make_unique<internal::TimerService>())
{
auto& http = static_cast<mw::HTTPSessionAsyncInterface&>(*http_);
[[maybe_unused]] auto connection_timeout =
diff --git a/src/sse_parser.cpp b/src/sse_parser.cpp
index 405ab99..385960d 100644
--- a/src/sse_parser.cpp
+++ b/src/sse_parser.cpp
@@ -4,7 +4,7 @@
#include <string_view>
#include <utility>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
namespace
{
@@ -130,4 +130,4 @@ bool SseFrameParser::finished() const noexcept
return finished_;
}
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/tests/test_llm_client.cpp b/tests/test_llm_client.cpp
index 14ba5c3..f4a8112 100644
--- a/tests/test_llm_client.cpp
+++ b/tests/test_llm_client.cpp
@@ -1,11 +1,6 @@
#include <atomic>
#include <chrono>
-#include <condition_variable>
-#include <coroutine>
#include <cstddef>
-#include <exception>
-#include <mutex>
-#include <optional>
#include <span>
#include <string>
#include <string_view>
@@ -13,6 +8,7 @@
#include <vector>
#include <agent_smith/llm_client.hpp>
+#include <agent_smith/sync_wait.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mw/http_client_async_mock.hpp>
@@ -26,119 +22,6 @@ using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
-template <typename T> class SyncWaitTask
-{
-public:
- struct promise_type
- {
- SyncWaitTask get_return_object()
- {
- return SyncWaitTask(
- std::coroutine_handle<promise_type>::from_promise(*this));
- }
-
- std::suspend_always initial_suspend() noexcept
- {
- return {};
- }
-
- auto final_suspend() noexcept
- {
- struct FinalAwaiter
- {
- bool await_ready() noexcept
- {
- return false;
- }
-
- void await_suspend(
- std::coroutine_handle<promise_type> handle) noexcept
- {
- auto& promise = handle.promise();
- std::lock_guard lock(promise.mutex);
- promise.done = true;
- promise.condition.notify_one();
- }
-
- void await_resume() noexcept {}
- };
-
- return FinalAwaiter{};
- }
-
- template <typename U> void return_value(U&& value)
- {
- value_.emplace(std::forward<U>(value));
- }
-
- void unhandled_exception()
- {
- exception_ = std::current_exception();
- }
-
- std::mutex mutex;
- std::condition_variable condition;
- bool done = false;
- std::optional<T> value_;
- std::exception_ptr exception_;
- };
-
- explicit SyncWaitTask(std::coroutine_handle<promise_type> handle)
- : handle_(handle)
- {
- }
-
- SyncWaitTask(SyncWaitTask&& other) noexcept
- : handle_(std::exchange(other.handle_, nullptr))
- {
- }
-
- SyncWaitTask(const SyncWaitTask&) = delete;
- SyncWaitTask& operator=(const SyncWaitTask&) = delete;
-
- ~SyncWaitTask()
- {
- if(handle_ != nullptr)
- {
- handle_.destroy();
- }
- }
-
- void start()
- {
- handle_.resume();
- }
-
- T get()
- {
- auto& promise = handle_.promise();
- std::unique_lock lock(promise.mutex);
- promise.condition.wait(lock, [&]() { return promise.done; });
- lock.unlock();
-
- if(promise.exception_ != nullptr)
- {
- std::rethrow_exception(promise.exception_);
- }
- return std::move(*promise.value_);
- }
-
-private:
- std::coroutine_handle<promise_type> handle_ = nullptr;
-};
-
-template <typename T> SyncWaitTask<T> makeWaiter(mw::Task<T> task)
-{
- co_return co_await std::move(task);
-}
-
-template <typename T> T syncWait(mw::Task<T> task)
-{
- auto waiter = makeWaiter(std::move(task));
- waiter.start();
- return waiter.get();
-}
-
mw::URL testEndpoint()
{
auto endpoint = mw::URL::fromStr("https://llm.example/api");
diff --git a/tests/test_skeleton.cpp b/tests/test_skeleton.cpp
index 13b5bf3..c9583f3 100644
--- a/tests/test_skeleton.cpp
+++ b/tests/test_skeleton.cpp
@@ -1,10 +1,5 @@
#include <atomic>
#include <chrono>
-#include <condition_variable>
-#include <coroutine>
-#include <exception>
-#include <mutex>
-#include <optional>
#include <stdexcept>
#include <thread>
#include <utility>
@@ -21,122 +16,6 @@ namespace agent_smith
namespace
{
-template <typename T> class SyncWaitTask
-{
-public:
- struct promise_type
- {
- SyncWaitTask get_return_object()
- {
- return SyncWaitTask(
- std::coroutine_handle<promise_type>::from_promise(*this));
- }
-
- std::suspend_always initial_suspend() noexcept
- {
- return {};
- }
-
- auto final_suspend() noexcept
- {
- struct FinalAwaiter
- {
- bool await_ready() noexcept
- {
- return false;
- }
-
- void await_suspend(
- std::coroutine_handle<promise_type> handle) noexcept
- {
- auto& promise = handle.promise();
- {
- std::lock_guard lock(promise.mutex);
- promise.done = true;
- promise.condition.notify_one();
- }
- }
-
- void await_resume() noexcept {}
- };
-
- return FinalAwaiter{};
- }
-
- template <typename U> void return_value(U&& value)
- {
- value_.emplace(std::forward<U>(value));
- }
-
- void unhandled_exception()
- {
- exception_ = std::current_exception();
- }
-
- std::mutex mutex;
- std::condition_variable condition;
- bool done = false;
- std::optional<T> value_;
- std::exception_ptr exception_;
- };
-
- explicit SyncWaitTask(std::coroutine_handle<promise_type> handle)
- : handle_(handle)
- {
- }
-
- SyncWaitTask(SyncWaitTask&& other) noexcept
- : handle_(std::exchange(other.handle_, nullptr))
- {
- }
-
- SyncWaitTask(const SyncWaitTask&) = delete;
- SyncWaitTask& operator=(const SyncWaitTask&) = delete;
-
- ~SyncWaitTask()
- {
- if(handle_ != nullptr)
- {
- handle_.destroy();
- }
- }
-
- void start()
- {
- handle_.resume();
- }
-
- T get()
- {
- auto& promise = handle_.promise();
- std::unique_lock lock(promise.mutex);
- promise.condition.wait(lock, [&]() { return promise.done; });
- lock.unlock();
-
- if(promise.exception_ != nullptr)
- {
- std::rethrow_exception(promise.exception_);
- }
-
- return std::move(*promise.value_);
- }
-
-private:
- std::coroutine_handle<promise_type> handle_ = nullptr;
-};
-
-template <typename T> SyncWaitTask<T> makeWaiter(mw::Task<T> task)
-{
- co_return co_await std::move(task);
-}
-
-template <typename T> T syncWait(mw::Task<T> task)
-{
- auto waiter = makeWaiter(std::move(task));
- waiter.start();
- return waiter.get();
-}
-
mw::Task<mw::E<int>> markAndSleep(Runtime& runtime, std::atomic<bool>& started,
int value, std::chrono::milliseconds duration)
{
@@ -150,7 +29,7 @@ mw::Task<mw::E<int>> markAndSleep(Runtime& runtime, std::atomic<bool>& started,
co_return value;
}
-mw::Task<mw::E<int>> awaitStarted(detail::StartedTask<int> task)
+mw::Task<mw::E<int>> awaitStarted(internal::StartedTask<int> task)
{
co_return co_await std::move(task);
}
@@ -221,7 +100,7 @@ TEST(StartedTaskTest, StartsImmediatelyAndCanBeAwaited)
Runtime runtime;
std::atomic<bool> started = false;
- auto task = detail::StartedTask<int>::start(
+ auto task = internal::StartedTask<int>::start(
markAndSleep(runtime, started, 7, std::chrono::milliseconds(1)));
EXPECT_TRUE(started.load());
@@ -232,7 +111,7 @@ TEST(StartedTaskTest, StartsImmediatelyAndCanBeAwaited)
TEST(AsyncQueueTest, DeliversItemsAcrossThreadsAndCloses)
{
- detail::AsyncQueue<int> queue;
+ internal::AsyncQueue<int> queue;
std::thread producer(
[&queue]()
{
@@ -252,7 +131,7 @@ TEST(AsyncQueueTest, DeliversItemsAcrossThreadsAndCloses)
TEST(AsyncQueueTest, DrainsBufferedItemsBeforeClosure)
{
- detail::AsyncQueue<int> queue;
+ internal::AsyncQueue<int> queue;
queue.push(3);
queue.push(5);
queue.close();
@@ -279,7 +158,7 @@ TEST(GatherAllTest, StartsAllTasksAndPreservesInputOrder)
tasks.push_back(
markAndSleep(runtime, second_started, 2, std::chrono::milliseconds(1)));
- auto results = syncWait(detail::gatherAll(std::move(tasks)));
+ auto results = syncWait(internal::gatherAll(std::move(tasks)));
ASSERT_EQ(results.size(), 2U);
ASSERT_TRUE(results[0].has_value());
diff --git a/tests/test_sse_parser.cpp b/tests/test_sse_parser.cpp
index 3adc718..cb3742d 100644
--- a/tests/test_sse_parser.cpp
+++ b/tests/test_sse_parser.cpp
@@ -4,7 +4,7 @@
#include <agent_smith/detail/sse_parser.hpp>
#include <gtest/gtest.h>
-namespace agent_smith::detail
+namespace agent_smith::internal
{
namespace
{
@@ -95,4 +95,4 @@ TEST(SseFrameParserTest, ProducesIdenticalPayloadsAtEveryByteSplit)
}
} // namespace
-} // namespace agent_smith::detail
+} // namespace agent_smith::internal
diff --git a/tests/test_tool.cpp b/tests/test_tool.cpp
index 0b72373..407677a 100644
--- a/tests/test_tool.cpp
+++ b/tests/test_tool.cpp
@@ -1,13 +1,9 @@
-#include <condition_variable>
-#include <coroutine>
-#include <exception>
-#include <mutex>
-#include <optional>
#include <string>
#include <thread>
#include <utility>
#include <vector>
+#include <agent_smith/sync_wait.hpp>
#include <agent_smith/tool.hpp>
#include <gtest/gtest.h>
@@ -16,119 +12,6 @@ namespace agent_smith
namespace
{
-template <typename T> class SyncWaitTask
-{
-public:
- struct promise_type
- {
- SyncWaitTask get_return_object()
- {
- return SyncWaitTask(
- std::coroutine_handle<promise_type>::from_promise(*this));
- }
-
- std::suspend_always initial_suspend() noexcept
- {
- return {};
- }
-
- auto final_suspend() noexcept
- {
- struct FinalAwaiter
- {
- bool await_ready() noexcept
- {
- return false;
- }
-
- void await_suspend(
- std::coroutine_handle<promise_type> handle) noexcept
- {
- auto& promise = handle.promise();
- std::lock_guard lock(promise.mutex);
- promise.done = true;
- promise.condition.notify_one();
- }
-
- void await_resume() noexcept {}
- };
-
- return FinalAwaiter{};
- }
-
- template <typename U> void return_value(U&& value)
- {
- value_.emplace(std::forward<U>(value));
- }
-
- void unhandled_exception()
- {
- exception_ = std::current_exception();
- }
-
- std::mutex mutex;
- std::condition_variable condition;
- bool done = false;
- std::optional<T> value_;
- std::exception_ptr exception_;
- };
-
- explicit SyncWaitTask(std::coroutine_handle<promise_type> handle)
- : handle_(handle)
- {
- }
-
- SyncWaitTask(SyncWaitTask&& other) noexcept
- : handle_(std::exchange(other.handle_, nullptr))
- {
- }
-
- SyncWaitTask(const SyncWaitTask&) = delete;
- SyncWaitTask& operator=(const SyncWaitTask&) = delete;
-
- ~SyncWaitTask()
- {
- if(handle_ != nullptr)
- {
- handle_.destroy();
- }
- }
-
- void start()
- {
- handle_.resume();
- }
-
- T get()
- {
- auto& promise = handle_.promise();
- std::unique_lock lock(promise.mutex);
- promise.condition.wait(lock, [&]() { return promise.done; });
- lock.unlock();
-
- if(promise.exception_ != nullptr)
- {
- std::rethrow_exception(promise.exception_);
- }
- return std::move(*promise.value_);
- }
-
-private:
- std::coroutine_handle<promise_type> handle_ = nullptr;
-};
-
-template <typename T> SyncWaitTask<T> makeWaiter(mw::Task<T> task)
-{
- co_return co_await std::move(task);
-}
-
-template <typename T> T syncWait(mw::Task<T> task)
-{
- auto waiter = makeWaiter(std::move(task));
- waiter.start();
- return waiter.get();
-}
-
ToolSchema schema(std::string name)
{
return ToolSchema{