@@ -98,16 +98,18 @@ inline Model modelFromString(const std::string& modelStr) {
9898 */
9999inline std::vector<std::string> getAvailableModels () {
100100 return {// Latest Claude 4 models
101- " claude-opus-4-1-20250805" , " claude-opus-4-20250514" , " claude-sonnet-4-20250514" ,
101+ toString (Model::CLAUDE_OPUS_4_1), toString (Model::CLAUDE_OPUS_4),
102+ toString (Model::CLAUDE_SONNET_4),
102103
103104 // Claude 3.7
104- " claude-3-7-sonnet-20250219 " ,
105+ toString (Model::CLAUDE_SONNET_3_7) ,
105106
106107 // Claude 3.5 models
107- " claude-3-5-sonnet-20241022" , " claude-3-5-sonnet-20240620" , " claude-3-5-haiku-20241022" ,
108+ toString (Model::CLAUDE_SONNET_3_5_V2), toString (Model::CLAUDE_SONNET_3_5),
109+ toString (Model::CLAUDE_HAIKU_3_5),
108110
109111 // Legacy Claude 3 models
110- " claude-3-opus-20240229 " , " claude-3-haiku-20240307 " };
112+ toString (Model::CLAUDE_OPUS_3), toString (Model::CLAUDE_HAIKU_3) };
111113}
112114
113115/* *
@@ -127,13 +129,64 @@ inline std::string toString(MessageRole role) {
127129}
128130
129131/* *
130- * Anthropic message content (text-only for now )
132+ * Anthropic message content (supports text, tool_use, and tool_result )
131133 */
132134struct MessageContent {
133135 std::string type = " text" ;
134136 std::string text;
135137
136- json toJson () const { return json{{" type" , type}, {" text" , text}}; }
138+ // For tool_use content
139+ std::string id;
140+ std::string name;
141+ json input;
142+
143+ // For tool_result content
144+ std::string toolUseId;
145+ json content;
146+ bool isError = false ;
147+
148+ json toJson () const {
149+ if (type == " text" ) {
150+ return json{{" type" , type}, {" text" , text}};
151+ } else if (type == " tool_use" ) {
152+ return json{{" type" , type}, {" id" , id}, {" name" , name}, {" input" , input}};
153+ } else if (type == " tool_result" ) {
154+ json j = {{" type" , type}, {" tool_use_id" , toolUseId}, {" content" , content}};
155+ if (isError) {
156+ j[" is_error" ] = true ;
157+ }
158+ return j;
159+ }
160+ return json{{" type" , type}, {" text" , text}};
161+ }
162+
163+ // Convenience constructors
164+ static MessageContent createText (const std::string& txt) {
165+ MessageContent content;
166+ content.type = " text" ;
167+ content.text = txt;
168+ return content;
169+ }
170+
171+ static MessageContent createToolUse (const std::string& toolId, const std::string& toolName,
172+ const json& toolInput) {
173+ MessageContent content;
174+ content.type = " tool_use" ;
175+ content.id = toolId;
176+ content.name = toolName;
177+ content.input = toolInput;
178+ return content;
179+ }
180+
181+ static MessageContent createToolResult (const std::string& useId, const json& result,
182+ bool error = false ) {
183+ MessageContent content;
184+ content.type = " tool_result" ;
185+ content.toolUseId = useId;
186+ content.content = result;
187+ content.isError = error;
188+ return content;
189+ }
137190};
138191
139192/* *
@@ -159,13 +212,61 @@ struct AnthropicConfig {
159212 std::string apiKey;
160213 std::string baseUrl = " https://api.anthropic.com" ;
161214 std::string anthropicVersion = " 2023-06-01" ;
162- Model defaultModel = Model::CLAUDE_SONNET_3_5_V2; // Latest stable model
215+ Model defaultModel = Model::CLAUDE_SONNET_3_5_V2;
163216 int timeoutSeconds = 30 ;
164217
165218 AnthropicConfig () = default ;
166219 explicit AnthropicConfig (const std::string& key) : apiKey(key) {}
167220};
168221
222+ /* *
223+ * Tool definition for function calling
224+ */
225+ struct Tool {
226+ std::string name;
227+ std::string description;
228+ json inputSchema;
229+
230+ json toJson () const {
231+ return json{{" name" , name}, {" description" , description}, {" input_schema" , inputSchema}};
232+ }
233+ };
234+
235+ /* *
236+ * Tool use content (when model calls a tool)
237+ */
238+ struct ToolUse {
239+ std::string type = " tool_use" ;
240+ std::string id;
241+ std::string name;
242+ json input;
243+
244+ json toJson () const {
245+ return json{{" type" , type}, {" id" , id}, {" name" , name}, {" input" , input}};
246+ }
247+ };
248+
249+ /* *
250+ * Tool result content (response to tool use)
251+ */
252+ struct ToolResult {
253+ std::string type = " tool_result" ;
254+ std::string toolUseId;
255+ json content;
256+ bool isError = false ;
257+
258+ json toJson () const {
259+ json j = {{" type" , type}, {" tool_use_id" , toolUseId}};
260+
261+ if (isError) {
262+ j[" is_error" ] = true ;
263+ }
264+
265+ j[" content" ] = content;
266+ return j;
267+ }
268+ };
269+
169270/* *
170271 * Anthropic Messages API request
171272 */
@@ -177,6 +278,8 @@ struct MessagesRequest {
177278 std::optional<double > topP;
178279 std::optional<std::string> system;
179280 std::vector<std::string> stopSequences;
281+ std::vector<Tool> tools; // Tool definitions for function calling
282+ std::optional<std::string> toolChoice; // "auto", "any", or specific tool name
180283
181284 json toJson () const {
182285 json j = {{" model" , model}, {" messages" , json::array ()}};
@@ -202,6 +305,15 @@ struct MessagesRequest {
202305 if (!stopSequences.empty ()) {
203306 j[" stop_sequences" ] = stopSequences;
204307 }
308+ if (!tools.empty ()) {
309+ j[" tools" ] = json::array ();
310+ for (const auto & tool : tools) {
311+ j[" tools" ].push_back (tool.toJson ());
312+ }
313+ }
314+ if (toolChoice.has_value ()) {
315+ j[" tool_choice" ] = json{{" type" , toolChoice.value ()}};
316+ }
205317
206318 return j;
207319 }
@@ -277,20 +389,32 @@ struct MessagesResponse {
277389 /* *
278390 * Convert to common LLMResponse
279391 */
280- LLMResponse toLLMResponse () const {
392+ LLMResponse toLLMResponse (bool expectStructuredOutput = false ) const {
281393 LLMResponse response;
282394 response.success = !content.empty ();
283395
284- // Combine all text content into result JSON
396+ // Combine all text content and parse as JSON
285397 std::string fullText;
286398 for (const auto & c : content) {
287399 if (c.type == " text" ) {
288400 fullText += c.text ;
289401 }
290402 }
291403
292- // Store content as a simple text result
293- response.result = json{{" text" , fullText}};
404+ // For tool calls, we return the input JSON; for text responses, handle based on expectation
405+ if (!content.empty () && content[0 ].type == " tool_use" ) {
406+ // Extract tool call input as the result
407+ response.result = content[0 ].input ;
408+ } else {
409+ // For text responses, handle based on whether structured output is expected
410+ if (expectStructuredOutput) {
411+ // Parse as JSON for structured output
412+ response.result = json::parse (fullText);
413+ } else {
414+ // Wrap free-form text in text field
415+ response.result = json{{" text" , fullText}};
416+ }
417+ }
294418
295419 response.usage .inputTokens = usage.inputTokens ;
296420 response.usage .outputTokens = usage.outputTokens ;
@@ -337,6 +461,18 @@ struct MessagesResponse {
337461 if (contentItem.contains (" text" )) {
338462 content.text = contentItem[" text" ];
339463 }
464+ // Parse tool_use content
465+ if (content.type == " tool_use" ) {
466+ if (contentItem.contains (" id" )) {
467+ content.id = contentItem[" id" ];
468+ }
469+ if (contentItem.contains (" name" )) {
470+ content.name = contentItem[" name" ];
471+ }
472+ if (contentItem.contains (" input" )) {
473+ content.input = contentItem[" input" ];
474+ }
475+ }
340476 response.content .push_back (content);
341477 }
342478 }
0 commit comments