Accept the OpenAI store flag
Browse filesAgent clients such as pi send store: false with every request. The server
never stores completions, so a boolean store is accepted and ignored instead
of failing the request as an unsupported parameter.
include/ling3/chat_protocol.h
CHANGED
|
@@ -88,7 +88,7 @@ inline Request Parse(const OrderedJson & j) {
|
|
| 88 |
"max_completion_tokens", "stream", "stream_options", "temperature", "top_p",
|
| 89 |
"seed", "stop", "n", "user", "cache_prompt", "top_k", "repeat_penalty",
|
| 90 |
"enable_thinking", "flow_control", "session_id", "reuse_generated_state",
|
| 91 |
-
"tools", "tool_choice", "parallel_tool_calls"};
|
| 92 |
if (std::find(supported.begin(), supported.end(), it.key()) == supported.end())
|
| 93 |
fail("unsupported parameter: " + it.key());
|
| 94 |
}
|
|
@@ -168,6 +168,8 @@ inline Request Parse(const OrderedJson & j) {
|
|
| 168 |
r.seed = j["seed"].get<std::uint32_t>();
|
| 169 |
}
|
| 170 |
if (j.contains("n") && (!j["n"].is_number_integer() || j["n"] != 1)) fail("only n=1 is supported");
|
|
|
|
|
|
|
| 171 |
if (j.contains("user") && !j["user"].is_string()) fail("user must be a string");
|
| 172 |
if (j.contains("user")) r.cache_user = j["user"].get<std::string>();
|
| 173 |
if (j.contains("stop") && !j["stop"].is_null()) {
|
|
|
|
| 88 |
"max_completion_tokens", "stream", "stream_options", "temperature", "top_p",
|
| 89 |
"seed", "stop", "n", "user", "cache_prompt", "top_k", "repeat_penalty",
|
| 90 |
"enable_thinking", "flow_control", "session_id", "reuse_generated_state",
|
| 91 |
+
"tools", "tool_choice", "parallel_tool_calls", "store"};
|
| 92 |
if (std::find(supported.begin(), supported.end(), it.key()) == supported.end())
|
| 93 |
fail("unsupported parameter: " + it.key());
|
| 94 |
}
|
|
|
|
| 168 |
r.seed = j["seed"].get<std::uint32_t>();
|
| 169 |
}
|
| 170 |
if (j.contains("n") && (!j["n"].is_number_integer() || j["n"] != 1)) fail("only n=1 is supported");
|
| 171 |
+
// Agent clients send OpenAI's store flag; this server never stores completions.
|
| 172 |
+
if (j.contains("store") && !j["store"].is_boolean()) fail("store must be boolean");
|
| 173 |
if (j.contains("user") && !j["user"].is_string()) fail("user must be a string");
|
| 174 |
if (j.contains("user")) r.cache_user = j["user"].get<std::string>();
|
| 175 |
if (j.contains("stop") && !j["stop"].is_null()) {
|
tests/chat_tools_test.cpp
CHANGED
|
@@ -26,12 +26,14 @@ int main(int argc, char ** argv) {
|
|
| 26 |
Check(Parse(body).tool_calls && Parse(body).parallel_tool_calls, "tools must enable call parsing");
|
| 27 |
auto none = body; none["tool_choice"] = "none";
|
| 28 |
Check(!Parse(none).tool_calls && Parse(none).prompt == prompt, "tool_choice none still renders tools");
|
|
|
|
|
|
|
| 29 |
auto parts = body;
|
| 30 |
parts["messages"][1]["content"] = OrderedJson::array({{{"type", "text"}, {"text", "Fix /tmp/a.txt."}}});
|
| 31 |
Check(Parse(parts).prompt == prompt, "text-part content");
|
| 32 |
for (const auto & [pointer, value] : std::vector<std::pair<std::string, OrderedJson>>{
|
| 33 |
{"/tools", "read_file"}, {"/tools/0/type", "code"}, {"/tool_choice", "required"},
|
| 34 |
-
{"/parallel_tool_calls", "yes"}, {"/messages/2/tool_calls/0/function/arguments", "{oops"},
|
| 35 |
{"/messages/2/tool_calls/0/function/arguments", "[1]"}, {"/messages/3/content", nullptr},
|
| 36 |
{"/messages/3/id", "x"}, {"/messages/1/content", OrderedJson::array({{{"type", "image_url"}}})}}) {
|
| 37 |
auto bad = body; bad[OrderedJson::json_pointer(pointer)] = value; bool failed = false;
|
|
|
|
| 26 |
Check(Parse(body).tool_calls && Parse(body).parallel_tool_calls, "tools must enable call parsing");
|
| 27 |
auto none = body; none["tool_choice"] = "none";
|
| 28 |
Check(!Parse(none).tool_calls && Parse(none).prompt == prompt, "tool_choice none still renders tools");
|
| 29 |
+
auto stored = body; stored["store"] = false;
|
| 30 |
+
Check(Parse(stored).prompt == prompt, "store flag rejected");
|
| 31 |
auto parts = body;
|
| 32 |
parts["messages"][1]["content"] = OrderedJson::array({{{"type", "text"}, {"text", "Fix /tmp/a.txt."}}});
|
| 33 |
Check(Parse(parts).prompt == prompt, "text-part content");
|
| 34 |
for (const auto & [pointer, value] : std::vector<std::pair<std::string, OrderedJson>>{
|
| 35 |
{"/tools", "read_file"}, {"/tools/0/type", "code"}, {"/tool_choice", "required"},
|
| 36 |
+
{"/parallel_tool_calls", "yes"}, {"/store", "no"}, {"/messages/2/tool_calls/0/function/arguments", "{oops"},
|
| 37 |
{"/messages/2/tool_calls/0/function/arguments", "[1]"}, {"/messages/3/content", nullptr},
|
| 38 |
{"/messages/3/id", "x"}, {"/messages/1/content", OrderedJson::array({{{"type", "image_url"}}})}}) {
|
| 39 |
auto bad = body; bad[OrderedJson::json_pointer(pointer)] = value; bool failed = false;
|