2026-10-14 08:36:39 +03:00
/* json.cpp — native RFC 8259 JSON parser/serializer, request building,
* and non-streaming response parsing. */
2026-10-14 00:01:03 +03:00
#include "bantam.hpp"
2026-10-14 08:36:39 +03:00
#include <cerrno>
2026-10-14 15:30:12 +03:00
#include <charconv>
2026-10-14 08:36:39 +03:00
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
2026-10-14 00:01:03 +03:00
2026-10-14 08:36:39 +03:00
/* ----------------------------- Json methods --------------------------- */
Json :: Json () = default ;
Json ::~ Json () = default ;
Json :: Json ( const Json & ) = default ;
Json :: Json ( Json && ) noexcept = default ;
Json & Json :: operator = ( const Json & ) = default ;
Json & Json :: operator = ( Json && ) noexcept = default ;
2026-10-14 00:01:03 +03:00
2026-10-14 08:36:39 +03:00
Json :: Json ( std :: nullptr_t ) : type ( JType :: Null ) {}
Json :: Json ( bool b ) : type ( JType :: Bool ), bval ( b ) {}
Json :: Json ( int n ) : type ( JType :: Int ), ival ( n ), dval ( n ) {}
Json :: Json ( int64_t n ) : type ( JType :: Int ), ival ( n ), dval (( double ) n ) {}
Json :: Json ( double d ) : type ( JType :: Float ), ival (( int64_t ) d ), dval ( d ) {}
Json :: Json ( const char * s ) : type ( JType :: Str ), sval ( s ? s : "" ) {}
Json :: Json ( std :: string s ) : type ( JType :: Str ), sval ( std :: move ( s )) {}
Json :: Json ( std :: string_view s ) : type ( JType :: Str ), sval ( s ) {}
Json Json :: object () {
Json j ;
j . type = JType :: Obj ;
return j ;
}
Json Json :: array () {
Json j ;
j . type = JType :: Arr ;
return j ;
}
static const Json kNullJson {};
bool Json :: empty () const {
if ( type == JType :: Arr ) return aval . empty ();
if ( type == JType :: Obj ) return oval . empty ();
if ( type == JType :: Str ) return sval . empty ();
return true ;
}
size_t Json :: size () const {
if ( type == JType :: Arr ) return aval . size ();
if ( type == JType :: Obj ) return oval . size ();
if ( type == JType :: Str ) return sval . size ();
return 0 ;
}
bool Json :: contains ( std :: string_view k ) const {
if ( type != JType :: Obj ) return false ;
for ( auto & kv : oval ) {
if ( kv . first == k ) return true ;
}
return false ;
}
Json & Json :: operator []( std :: string_view k ) {
if ( type != JType :: Obj ) {
* this = Json :: object ();
}
for ( auto & kv : oval ) {
if ( kv . first == k ) return kv . second ;
}
oval . emplace_back ( std :: string ( k ), Json {});
return oval . back (). second ;
}
const Json & Json :: operator []( std :: string_view k ) const {
if ( type != JType :: Obj ) return kNullJson ;
for ( auto & kv : oval ) {
if ( kv . first == k ) return kv . second ;
}
return kNullJson ;
}
Json & Json :: at ( size_t i ) {
return aval [ i ];
}
const Json & Json :: at ( size_t i ) const {
if ( type != JType :: Arr || i >= aval . size ()) return kNullJson ;
return aval [ i ];
}
void Json :: push_back ( Json v ) {
if ( type != JType :: Arr ) {
* this = Json :: array ();
}
aval . push_back ( std :: move ( v ));
}
std :: string Json :: str ( std :: string_view def ) const {
if ( type == JType :: Str ) return sval ;
return std :: string ( def );
}
int64_t Json :: as_int64 ( int64_t def ) const {
if ( type == JType :: Int ) return ival ;
return def ;
}
int Json :: as_int ( int def ) const {
if ( type != JType :: Int ) return def ;
if ( ival > INT_MAX ) return INT_MAX ;
if ( ival < INT_MIN ) return INT_MIN ;
return ( int ) ival ;
}
/* ----------------------------- JSON serializer ------------------------ */
static void dump_str ( std :: string & out , std :: string_view s ) {
out += '"' ;
const unsigned char * p = ( const unsigned char * ) s . data ();
size_t avail = s . size ();
while ( avail > 0 ) {
2026-10-14 15:30:12 +03:00
/* Fast path: scan contiguous unescaped printable ASCII. */
size_t run = 0 ;
while ( run < avail ) {
unsigned char b = p [ run ];
if ( b >= 0x20 && b < 0x80 && b != '"' && b != '\\' ) run ++ ;
else break ;
}
if ( run > 0 ) {
out . append (( const char * ) p , run );
p += run ;
avail -= run ;
if ( avail == 0 ) break ;
}
2026-10-14 08:36:39 +03:00
unsigned char c = p [ 0 ];
if ( c < 0x80 ) {
p ++ ;
avail -- ;
switch ( c ) {
case '"' : out += " \\\" " ; break ;
case '\\' : out += " \\\\ " ; break ;
case '\b' : out += " \\ b" ; break ;
case '\f' : out += " \\ f" ; break ;
case '\n' : out += " \\ n" ; break ;
case '\r' : out += " \\ r" ; break ;
case '\t' : out += " \\ t" ; break ;
2026-10-14 15:30:12 +03:00
default : {
char ubuf [ 7 ];
std :: snprintf ( ubuf , sizeof ubuf , " \\ u%04x" , ( unsigned ) c );
out . append ( ubuf , 6 );
2026-10-14 08:36:39 +03:00
break ;
2026-10-14 15:30:12 +03:00
}
2026-10-14 08:36:39 +03:00
}
} else {
size_t pl = 0 ;
int cp = utf8_decode ( p , avail , & pl );
if ( cp == 0xFFFD && pl == 1 ) {
out += " \xEF\xBF\xBD " ;
p ++ ;
avail -- ;
} else {
out . append (( const char * ) p , pl );
p += pl ;
avail -= pl ;
}
}
}
out += '"' ;
}
static void dump_rec ( std :: string & out , const Json & j , int indent , int depth ) {
switch ( j . type ) {
case JType :: Null :
out += "null" ;
break ;
case JType :: Bool :
out += ( j . bval ? "true" : "false" );
break ;
2026-10-14 15:30:12 +03:00
case JType :: Int : {
char ibuf [ 32 ];
auto [ ptr , ec ] = std :: to_chars ( ibuf , ibuf + sizeof ibuf , j . ival );
if ( ec == std :: errc ()) out . append ( ibuf , ( size_t )( ptr - ibuf ));
else str_appendf ( out , "%lld" , ( long long ) j . ival );
2026-10-14 08:36:39 +03:00
break ;
2026-10-14 15:30:12 +03:00
}
2026-10-14 08:36:39 +03:00
case JType :: Float :
2026-10-14 15:30:12 +03:00
if ( ! std :: isfinite ( j . dval )) {
out += "null" ;
} else {
char fbuf [ 32 ];
int n = std :: snprintf ( fbuf , sizeof fbuf , "%.15g" , j . dval );
if ( n > 0 && ( size_t ) n < sizeof fbuf ) out . append ( fbuf , ( size_t ) n );
else str_appendf ( out , "%.15g" , j . dval );
}
2026-10-14 08:36:39 +03:00
break ;
case JType :: Str :
dump_str ( out , j . sval );
break ;
case JType :: Arr :
if ( j . aval . empty ()) {
out += "[]" ;
} else {
out += '[' ;
for ( size_t i = 0 ; i < j . aval . size (); i ++ ) {
if ( i > 0 ) out += ',' ;
if ( indent >= 0 ) {
out += '\n' ;
out . append (( size_t )( depth + 1 ) * ( size_t ) indent , ' ' );
}
dump_rec ( out , j . aval [ i ], indent , depth + 1 );
}
if ( indent >= 0 ) {
out += '\n' ;
out . append (( size_t ) depth * ( size_t ) indent , ' ' );
}
out += ']' ;
}
break ;
case JType :: Obj :
if ( j . oval . empty ()) {
out += "{}" ;
} else {
out += '{' ;
for ( size_t i = 0 ; i < j . oval . size (); i ++ ) {
if ( i > 0 ) out += ',' ;
if ( indent >= 0 ) {
out += '\n' ;
out . append (( size_t )( depth + 1 ) * ( size_t ) indent , ' ' );
}
dump_str ( out , j . oval [ i ]. first );
out += ( indent >= 0 ) ? ": " : ":" ;
dump_rec ( out , j . oval [ i ]. second , indent , depth + 1 );
}
if ( indent >= 0 ) {
out += '\n' ;
out . append (( size_t ) depth * ( size_t ) indent , ' ' );
}
out += '}' ;
}
break ;
}
}
std :: string Json :: dump ( int indent ) const {
std :: string out ;
2026-10-14 15:30:12 +03:00
out . reserve ( 256 );
2026-10-14 08:36:39 +03:00
dump_rec ( out , * this , indent , 0 );
return out ;
}
/* ----------------------------- JSON parser ---------------------------- */
struct JParser {
std :: string_view src ;
size_t pos = 0 ;
void skip_ws () {
while ( pos < src . size ()) {
char c = src [ pos ];
if ( c == ' ' || c == '\t' || c == '\n' || c == '\r' ) pos ++ ;
else break ;
}
}
static int hex_val ( char c ) {
if ( c >= '0' && c <= '9' ) return c - '0' ;
if ( c >= 'a' && c <= 'f' ) return c - 'a' + 10 ;
if ( c >= 'A' && c <= 'F' ) return c - 'A' + 10 ;
return - 1 ;
}
bool parse_hex4 ( uint32_t & out ) {
if ( pos + 4 > src . size ()) return false ;
uint32_t v = 0 ;
for ( int i = 0 ; i < 4 ; i ++ ) {
int h = hex_val ( src [ pos + ( size_t ) i ]);
if ( h < 0 ) return false ;
v = ( v << 4 ) | ( uint32_t ) h ;
}
pos += 4 ;
out = v ;
return true ;
}
static void append_utf8 ( std :: string & s , uint32_t cp ) {
if ( cp <= 0x7F ) {
s += ( char ) cp ;
} else if ( cp <= 0x7FF ) {
s += ( char )( 0xC0 | ( cp >> 6 ));
s += ( char )( 0x80 | ( cp & 0x3F ));
} else if ( cp <= 0xFFFF ) {
s += ( char )( 0xE0 | ( cp >> 12 ));
s += ( char )( 0x80 | (( cp >> 6 ) & 0x3F ));
s += ( char )( 0x80 | ( cp & 0x3F ));
} else if ( cp <= 0x10FFFF ) {
s += ( char )( 0xF0 | ( cp >> 18 ));
s += ( char )( 0x80 | (( cp >> 12 ) & 0x3F ));
s += ( char )( 0x80 | (( cp >> 6 ) & 0x3F ));
s += ( char )( 0x80 | ( cp & 0x3F ));
} else {
s += " \xEF\xBF\xBD " ;
}
}
bool parse_string ( std :: string & out ) {
if ( pos >= src . size () || src [ pos ] != '"' ) return false ;
pos ++ ;
out . clear ();
2026-10-14 15:30:12 +03:00
const char * base = src . data ();
size_t n = src . size ();
while ( pos < n ) {
/* Fast path: scan contiguous unescaped bytes >= 0x20. */
size_t run_start = pos ;
while ( pos < n ) {
unsigned char b = ( unsigned char ) base [ pos ];
if ( b == '"' || b == '\\' || b < 0x20 ) break ;
pos ++ ;
}
if ( pos > run_start ) {
out . append ( base + run_start , pos - run_start );
}
if ( pos >= n ) return false ;
unsigned char c = ( unsigned char ) base [ pos ++ ];
2026-10-14 08:36:39 +03:00
if ( c == '"' ) return true ;
if ( c < 0x20 ) return false ;
2026-10-14 15:30:12 +03:00
if ( pos >= n ) return false ;
char esc = base [ pos ++ ];
2026-10-14 08:36:39 +03:00
switch ( esc ) {
case '"' : out += '"' ; break ;
case '\\' : out += '\\' ; break ;
case '/' : out += '/' ; break ;
case 'b' : out += '\b' ; break ;
case 'f' : out += '\f' ; break ;
case 'n' : out += '\n' ; break ;
case 'r' : out += '\r' ; break ;
case 't' : out += '\t' ; break ;
case 'u' : {
uint32_t cp = 0 ;
if ( ! parse_hex4 ( cp )) return false ;
if ( cp >= 0xD800 && cp <= 0xDBFF ) {
2026-10-14 15:30:12 +03:00
if ( pos + 6 <= n && base [ pos ] == '\\' && base [ pos + 1 ] == 'u' ) {
2026-10-14 08:36:39 +03:00
size_t saved = pos ;
pos += 2 ;
uint32_t low = 0 ;
if ( parse_hex4 ( low ) && low >= 0xDC00 && low <= 0xDFFF ) {
cp = 0x10000u + ((( cp - 0xD800u ) << 10 ) | ( low - 0xDC00u ));
} else {
pos = saved ;
cp = 0xFFFDu ;
}
} else {
cp = 0xFFFDu ;
}
} else if ( cp >= 0xDC00 && cp <= 0xDFFF ) {
cp = 0xFFFDu ;
}
append_utf8 ( out , cp );
break ;
}
default :
return false ;
}
}
return false ;
}
bool parse_number ( Json & out ) {
size_t start = pos ;
if ( src [ pos ] == '-' ) pos ++ ;
if ( pos >= src . size ()) return false ;
if ( src [ pos ] == '0' ) {
pos ++ ;
if ( pos < src . size () && src [ pos ] >= '0' && src [ pos ] <= '9' ) return false ;
} else if ( src [ pos ] >= '1' && src [ pos ] <= '9' ) {
while ( pos < src . size () && src [ pos ] >= '0' && src [ pos ] <= '9' ) pos ++ ;
} else {
return false ;
}
bool is_float = false ;
if ( pos < src . size () && src [ pos ] == '.' ) {
is_float = true ;
pos ++ ;
if ( pos >= src . size () || src [ pos ] < '0' || src [ pos ] > '9' ) return false ;
while ( pos < src . size () && src [ pos ] >= '0' && src [ pos ] <= '9' ) pos ++ ;
}
if ( pos < src . size () && ( src [ pos ] == 'e' || src [ pos ] == 'E' )) {
is_float = true ;
pos ++ ;
if ( pos < src . size () && ( src [ pos ] == '+' || src [ pos ] == '-' )) pos ++ ;
if ( pos >= src . size () || src [ pos ] < '0' || src [ pos ] > '9' ) return false ;
while ( pos < src . size () && src [ pos ] >= '0' && src [ pos ] <= '9' ) pos ++ ;
}
2026-10-14 15:30:12 +03:00
const char * first = src . data () + start ;
const char * last = src . data () + pos ;
2026-10-14 08:36:39 +03:00
if ( ! is_float ) {
2026-10-14 15:30:12 +03:00
int64_t iv = 0 ;
auto [ ptr , ec ] = std :: from_chars ( first , last , iv );
if ( ec == std :: errc () && ptr == last ) {
out = Json ( iv );
2026-10-14 08:36:39 +03:00
return true ;
}
}
2026-10-14 15:30:12 +03:00
char stack_num [ 64 ];
std :: string heap_num ;
const char * cstr = nullptr ;
size_t nlen = pos - start ;
if ( nlen < sizeof stack_num ) {
std :: memcpy ( stack_num , first , nlen );
stack_num [ nlen ] = '\0' ;
cstr = stack_num ;
} else {
heap_num . assign ( first , nlen );
cstr = heap_num . c_str ();
}
2026-10-14 08:36:39 +03:00
errno = 0 ;
char * end = nullptr ;
2026-10-14 15:30:12 +03:00
double dv = std :: strtod ( cstr , & end );
if ( end != cstr + nlen || ! std :: isfinite ( dv )) return false ;
2026-10-14 08:36:39 +03:00
out = Json ( dv );
return true ;
}
bool parse_value ( Json & out , int depth ) {
if ( depth > 256 ) return false ;
skip_ws ();
if ( pos >= src . size ()) return false ;
char c = src [ pos ];
if ( c == 'n' ) {
if ( src . substr ( pos , 4 ) != "null" ) return false ;
pos += 4 ;
out = Json ( nullptr );
return true ;
}
if ( c == 't' ) {
if ( src . substr ( pos , 4 ) != "true" ) return false ;
pos += 4 ;
out = Json ( true );
return true ;
}
if ( c == 'f' ) {
if ( src . substr ( pos , 5 ) != "false" ) return false ;
pos += 5 ;
out = Json ( false );
return true ;
}
if ( c == '"' ) {
std :: string s ;
if ( ! parse_string ( s )) return false ;
out = Json ( std :: move ( s ));
return true ;
}
if ( c == '[' ) {
2026-10-14 11:11:28 +03:00
pos ++ ;
skip_ws ();
out = Json :: array ();
if ( pos < src . size () && src [ pos ] == ']' ) {
pos ++ ;
return true ;
}
while ( true ) {
Json elem ;
if ( ! parse_value ( elem , depth + 1 )) return false ;
out . aval . push_back ( std :: move ( elem ));
skip_ws ();
if ( pos < src . size () && src [ pos ] == ',' ) {
pos ++ ;
continue ;
}
if ( pos < src . size () && src [ pos ] == ']' ) {
pos ++ ;
return true ;
}
return false ;
}
}
if ( c == '{' ) {
2026-10-14 08:36:39 +03:00
pos ++ ;
skip_ws ();
out = Json :: object ();
if ( pos < src . size () && src [ pos ] == '}' ) {
pos ++ ;
return true ;
}
while ( true ) {
skip_ws ();
std :: string key ;
if ( ! parse_string ( key )) return false ;
skip_ws ();
if ( pos >= src . size () || src [ pos ] != ':' ) return false ;
pos ++ ;
Json val ;
if ( ! parse_value ( val , depth + 1 )) return false ;
out [ key ] = std :: move ( val );
skip_ws ();
if ( pos < src . size () && src [ pos ] == ',' ) {
pos ++ ;
continue ;
}
if ( pos < src . size () && src [ pos ] == '}' ) {
pos ++ ;
return true ;
}
return false ;
}
}
if ( c == '-' || ( c >= '0' && c <= '9' )) {
return parse_number ( out );
}
return false ;
}
};
bool Json :: parse ( std :: string_view src , Json & out ) {
JParser p { src , 0 };
Json tmp ;
if ( ! p . parse_value ( tmp , 0 )) return false ;
p . skip_ws ();
if ( p . pos != src . size ()) return false ;
out = std :: move ( tmp );
return true ;
}
/* ----------------------------- message helpers ------------------------ */
2026-10-14 00:01:03 +03:00
/* The tool definitions, identical to the Go reference implementation. */
static const char * TOOLS_JSON = R "JSON([
{ "type" : "function" , "function" : {
"name" : "bash" ,
"description" : "Executes a given bash command in a shell session, returning its output and exit code." ,
"parameters" : { "type" : "object" , "properties" : {
"command" : { "type" : "string" , "description" : "The command to execute" },
"workdir" : { "type" : "string" , "description" : "The working directory to run the command in. Defaults to the current directory." }
}, "required" : [ "command" ]}}},
{ "type" : "function" , "function" : {
"name" : "read" ,
"description" : "Read a file or directory from the local filesystem. For files, returns numbered lines prefixed as `<line>: <content>`. For directories, returns entry names with trailing slashes for subdirectories." ,
"parameters" : { "type" : "object" , "properties" : {
"filePath" : { "type" : "string" , "description" : "The path to the file or directory to read" },
"offset" : { "type" : "integer" , "description" : "The line number to start reading from (1-indexed, defaults to 1)" },
"limit" : { "type" : "integer" , "description" : "The maximum number of lines to read (defaults to 2000)" }
}, "required" : [ "filePath" ]}}},
{ "type" : "function" , "function" : {
"name" : "write" ,
2026-10-14 10:58:08 +03:00
"description" : "Write content to a file. If none of lineOffset, byteOffset, delLines and delBytes are provided, the entire file is overwritten with the new content (the file and any necessary parent directories are created if missing); otherwise content is written at the given line or byte offset, optionally deleting lines or bytes first. Line-based editing is primary." ,
2026-10-14 00:01:03 +03:00
"parameters" : { "type" : "object" , "properties" : {
"filePath" : { "type" : "string" , "description" : "The path to the file to write" },
"content" : { "type" : "string" , "description" : "The content to write to the file" },
2026-10-14 10:58:08 +03:00
"lineOffset" : { "type" : "integer" , "description" : "1-indexed line number where writing begins (1 = first line). Takes precedence over byteOffset when both are given." },
"byteOffset" : { "type" : "integer" , "description" : "Byte offset where writing begins. Used only when lineOffset is not given." },
"delLines" : { "type" : "integer" , "description" : "Number of lines to delete starting at the insertion point. Takes precedence over delBytes when both are given." },
"delBytes" : { "type" : "integer" , "description" : "Number of bytes to delete starting at the insertion point. Used only when delLines is not given." }
2026-10-14 00:01:03 +03:00
}, "required" : [ "filePath" , "content" ]}}}
]) JSON ";
2026-10-14 08:36:39 +03:00
void serialize_message_json ( Json & obj , const Message & m ) {
obj = Json :: object ();
2026-10-14 00:01:03 +03:00
obj [ "role" ] = m . role . empty () ? "" : m . role ;
if ( ! m . content . empty ()) obj [ "content" ] = m . content ;
if ( ! m . reasoning . empty ()) obj [ "reasoning_content" ] = m . reasoning ;
if ( ! m . calls . empty ()) {
2026-10-14 08:36:39 +03:00
Json arr = Json :: array ();
2026-10-14 00:01:03 +03:00
for ( auto & tc : m . calls ) {
2026-10-14 08:36:39 +03:00
Json t = Json :: object ();
2026-10-14 00:01:03 +03:00
if ( ! tc . id . empty ()) t [ "id" ] = tc . id ;
t [ "type" ] = "function" ;
2026-10-14 08:36:39 +03:00
Json fn = Json :: object ();
2026-10-14 00:01:03 +03:00
fn [ "name" ] = tc . name . empty () ? "" : tc . name ;
fn [ "arguments" ] = tc . arguments . empty () ? "{}" : tc . arguments ;
2026-10-14 08:36:39 +03:00
t [ "function" ] = std :: move ( fn );
arr . push_back ( std :: move ( t ));
2026-10-14 00:01:03 +03:00
}
2026-10-14 08:36:39 +03:00
obj [ "tool_calls" ] = std :: move ( arr );
2026-10-14 00:01:03 +03:00
}
if ( ! m . tool_call_id . empty ()) obj [ "tool_call_id" ] = m . tool_call_id ;
}
2026-10-14 08:36:39 +03:00
void parse_message_json ( const Json & obj , Message & m ) {
m = Message {};
if ( ! obj . is_object ()) return ;
m . role = obj [ "role" ]. str ( "user" );
m . content = obj [ "content" ]. str ();
m . reasoning = obj [ "reasoning_content" ]. str ();
m . tool_call_id = obj [ "tool_call_id" ]. str ();
const Json & tcs = obj [ "tool_calls" ];
if ( tcs . is_array ()) {
for ( auto & tc : tcs . aval ) {
if ( ! tc . is_object ()) continue ;
ToolCall t ;
t . id = tc [ "id" ]. str ();
const Json & fn = tc [ "function" ];
if ( fn . is_object ()) {
t . name = fn [ "name" ]. str ();
t . arguments = fn [ "arguments" ]. str ();
}
m . calls . push_back ( std :: move ( t ));
}
}
}
/* ----------------------------- request / response --------------------- */
2026-10-14 00:01:03 +03:00
std :: string build_request_json ( const Cfg & cfg , const Messages & msgs ,
bool use_tools , bool stream ) {
2026-10-14 08:36:39 +03:00
Json root = Json :: object ();
2026-10-14 00:01:03 +03:00
root [ "model" ] = cfg . model ;
root [ "temperature" ] = cfg . temperature ;
2026-10-14 08:36:39 +03:00
Json arr = Json :: array ();
2026-10-14 15:30:12 +03:00
arr . aval . reserve ( msgs . items . size ());
2026-10-14 00:01:03 +03:00
for ( auto & m : msgs . items ) {
2026-10-14 08:36:39 +03:00
Json mo ;
serialize_message_json ( mo , m );
arr . push_back ( std :: move ( mo ));
2026-10-14 00:01:03 +03:00
}
2026-10-14 08:36:39 +03:00
root [ "messages" ] = std :: move ( arr );
2026-10-14 00:01:03 +03:00
root [ "stream" ] = stream ;
if ( use_tools ) {
2026-10-14 15:30:12 +03:00
static const Json kTools = []() {
Json t ;
Json :: parse ( TOOLS_JSON , t );
return t ;
}();
root [ "tools" ] = kTools ;
2026-10-14 00:01:03 +03:00
}
if ( stream ) {
2026-10-14 08:36:39 +03:00
Json so = Json :: object ();
2026-10-14 00:01:03 +03:00
so [ "include_usage" ] = true ;
2026-10-14 08:36:39 +03:00
root [ "stream_options" ] = std :: move ( so );
2026-10-14 00:01:03 +03:00
}
/* Forward non-internal raw params, parsed as JSON when possible. */
for ( auto & kv : cfg . raw ) {
if ( internal_key ( kv . first )) continue ;
2026-10-14 08:36:39 +03:00
Json jv ;
if ( Json :: parse ( kv . second , jv )) {
root [ kv . first ] = std :: move ( jv );
} else {
2026-10-14 00:01:03 +03:00
root [ kv . first ] = kv . second ;
}
}
2026-10-14 08:36:39 +03:00
return root . dump ();
2026-10-14 00:01:03 +03:00
}
bool parse_nonstream_response ( std :: string_view body , Message & out , Usage & usage ) {
out = Message {};
usage = Usage {};
2026-10-14 08:36:39 +03:00
Json root_mut ;
if ( ! Json :: parse ( body , root_mut ) || ! root_mut . is_object ()) return false ;
const Json & root = root_mut ;
const Json & choices = root [ "choices" ];
if ( ! choices . is_array () || choices . empty ()) return false ;
const Json & ch = choices . at ( 0 );
if ( ! ch . is_object ()) return false ;
const Json & msg = ch [ "message" ];
if ( ! msg . is_object ()) return false ;
out . role = msg [ "role" ]. str ( "assistant" );
out . content = msg [ "content" ]. str ();
std :: string reas = msg [ "reasoning_content" ]. str ();
if ( reas . empty ()) reas = msg [ "reasoning" ]. str ();
if ( reas . empty ()) reas = msg [ "thought" ]. str ();
if ( ! reas . empty ()) out . reasoning = std :: move ( reas );
const Json & tcs = msg [ "tool_calls" ];
if ( tcs . is_array ()) {
for ( auto & tc : tcs . aval ) {
if ( ! tc . is_object ()) continue ;
ToolCall t ;
t . id = tc [ "id" ]. str ();
const Json & fn = tc [ "function" ];
if ( fn . is_object ()) {
t . name = fn [ "name" ]. str ();
t . arguments = fn [ "arguments" ]. str ( "{}" );
2026-10-14 00:01:03 +03:00
}
2026-10-14 08:36:39 +03:00
out . calls . push_back ( std :: move ( t ));
2026-10-14 00:01:03 +03:00
}
}
2026-10-14 08:36:39 +03:00
const Json & u = root [ "usage" ];
if ( u . is_object ()) {
usage . prompt_tokens = u [ "prompt_tokens" ]. as_int ();
usage . completion_tokens = u [ "completion_tokens" ]. as_int ();
usage . total_tokens = u [ "total_tokens" ]. as_int ();
const Json & ptd = u [ "prompt_tokens_details" ];
if ( ptd . is_object ()) {
usage . cached_tokens = ptd [ "cached_tokens" ]. as_int ();
}
if ( usage . cached_tokens == 0 ) {
usage . cached_tokens = u [ "cached_tokens" ]. as_int ();
}
}
usage . model = root [ "model" ]. str ();
return true ;
2026-10-14 00:01:03 +03:00
}