@@ -141,6 +141,20 @@ struct schema_type<std::optional<T>>
141141 static json schema () { return schema_type<T>::schema (); }
142142};
143143
144+ // Type trait to detect std::optional
145+ template <typename T>
146+ struct is_optional : std::false_type
147+ {
148+ };
149+
150+ template <typename T>
151+ struct is_optional <std::optional<T>> : std::true_type
152+ {
153+ };
154+
155+ template <typename T>
156+ inline constexpr bool is_optional_v = is_optional<T>::value;
157+
144158// / Convert value to string for tool result
145159template <typename T>
146160std::string to_result_string (const T& value)
@@ -170,7 +184,16 @@ std::string to_result_string(const T& value)
170184template <typename T>
171185T extract_arg (const json& args, const std::string& name)
172186{
173- if constexpr (std::is_same_v<std::decay_t <T>, std::string>)
187+ if constexpr (is_optional_v<std::decay_t <T>>)
188+ {
189+ using value_type = typename std::decay_t <T>::value_type;
190+ if (!args.contains (name) || args.at (name).is_null ())
191+ {
192+ return std::nullopt ;
193+ }
194+ return extract_arg<value_type>(args, name);
195+ }
196+ else if constexpr (std::is_same_v<std::decay_t <T>, std::string>)
174197 {
175198 return args.at (name).get <std::string>();
176199 }
@@ -191,20 +214,6 @@ T extract_arg_or(const json& args, const std::string& name, const T& default_val
191214 return default_val;
192215}
193216
194- // Type trait to detect std::optional
195- template <typename T>
196- struct is_optional : std::false_type
197- {
198- };
199-
200- template <typename T>
201- struct is_optional <std::optional<T>> : std::true_type
202- {
203- };
204-
205- template <typename T>
206- inline constexpr bool is_optional_v = is_optional<T>::value;
207-
208217} // namespace detail
209218
210219// =============================================================================
@@ -528,4 +537,175 @@ inline ToolBuilder tool(std::string name, std::string description)
528537 return ToolBuilder (std::move (name), std::move (description));
529538}
530539
540+ // =============================================================================
541+ // Function Traits for make_tool
542+ // =============================================================================
543+
544+ namespace detail
545+ {
546+
547+ // / Remove cv-ref qualifiers
548+ template <typename T>
549+ using remove_cvref_t = std::remove_cv_t <std::remove_reference_t <T>>;
550+
551+ // / Function traits primary template
552+ template <typename T>
553+ struct function_traits : function_traits<decltype (&T::operator ())>
554+ {
555+ };
556+
557+ // / Specialization for function pointers
558+ template <typename R, typename ... Args>
559+ struct function_traits <R (*)(Args...)>
560+ {
561+ using return_type = R;
562+ static constexpr size_t arity = sizeof ...(Args);
563+
564+ template <size_t N>
565+ using arg_type = std::tuple_element_t <N, std::tuple<Args...>>;
566+ };
567+
568+ // / Specialization for member function pointers (const)
569+ template <typename C, typename R, typename ... Args>
570+ struct function_traits <R (C::*)(Args...) const >
571+ {
572+ using return_type = R;
573+ static constexpr size_t arity = sizeof ...(Args);
574+
575+ template <size_t N>
576+ using arg_type = std::tuple_element_t <N, std::tuple<Args...>>;
577+ };
578+
579+ // / Specialization for member function pointers (non-const)
580+ template <typename C, typename R, typename ... Args>
581+ struct function_traits <R (C::*)(Args...)>
582+ {
583+ using return_type = R;
584+ static constexpr size_t arity = sizeof ...(Args);
585+
586+ template <size_t N>
587+ using arg_type = std::tuple_element_t <N, std::tuple<Args...>>;
588+ };
589+
590+ // / Helper to invoke function with JSON args
591+ template <typename Func, size_t ... Is>
592+ auto invoke_with_json_impl (Func&& func, const json& args,
593+ const std::vector<std::string>& names, std::index_sequence<Is...>)
594+ {
595+ using traits = function_traits<remove_cvref_t <Func>>;
596+ return func (
597+ extract_arg<remove_cvref_t <typename traits::template arg_type<Is>>>(args, names[Is])...);
598+ }
599+
600+ template <typename Func>
601+ auto invoke_with_json (Func&& func, const json& args, const std::vector<std::string>& names)
602+ {
603+ using traits = function_traits<remove_cvref_t <Func>>;
604+ return invoke_with_json_impl<Func>(std::forward<Func>(func), args, names,
605+ std::make_index_sequence<traits::arity>{});
606+ }
607+
608+ template <typename T>
609+ void add_required_if (json& required, const std::string& name)
610+ {
611+ if constexpr (!is_optional_v<T>)
612+ {
613+ required.push_back (name);
614+ }
615+ }
616+
617+ // / Generate schema from function signature
618+ template <typename Func, size_t ... Is>
619+ json generate_schema_impl (const std::vector<std::string>& names, std::index_sequence<Is...>)
620+ {
621+ using traits = function_traits<remove_cvref_t <Func>>;
622+ json schema = {{" type" , " object" }, {" properties" , json::object ()}, {" required" , json::array ()}};
623+
624+ ((schema[" properties" ][names[Is]] =
625+ schema_type<remove_cvref_t <typename traits::template arg_type<Is>>>::schema (),
626+ add_required_if<remove_cvref_t <typename traits::template arg_type<Is>>>(
627+ schema[" required" ], names[Is])),
628+ ...);
629+
630+ return schema;
631+ }
632+
633+ template <typename Func>
634+ json generate_schema (const std::vector<std::string>& names)
635+ {
636+ using traits = function_traits<remove_cvref_t <Func>>;
637+ return generate_schema_impl<Func>(names, std::make_index_sequence<traits::arity>{});
638+ }
639+
640+ } // namespace detail
641+
642+ // =============================================================================
643+ // make_tool - Claude SDK compatible API
644+ // =============================================================================
645+
646+ // / Create a tool from a function with custom parameter names
647+ // / Similar to claude::mcp::make_tool for API consistency
648+ // /
649+ // / Example:
650+ // / @code
651+ // / auto tool = copilot::make_tool("dbg_exec", "Execute debugger command",
652+ // / [](std::string command) { return execute(command); },
653+ // / {"command"});
654+ // / @endcode
655+ template <typename Func>
656+ Tool make_tool (std::string name, std::string description, Func&& func,
657+ std::vector<std::string> param_names)
658+ {
659+ using traits = detail::function_traits<detail::remove_cvref_t <Func>>;
660+
661+ if (param_names.size () != traits::arity)
662+ {
663+ throw std::invalid_argument (" Parameter name count mismatch for tool '" + name +
664+ " ': expected " + std::to_string (traits::arity) + " , got " +
665+ std::to_string (param_names.size ()));
666+ }
667+
668+ Tool tool;
669+ tool.name = std::move (name);
670+ tool.description = std::move (description);
671+ tool.parameters_schema = detail::generate_schema<Func>(param_names);
672+
673+ // Create handler that extracts args and invokes function
674+ tool.handler = [f = std::forward<Func>(func),
675+ names = std::move (param_names)](const ToolInvocation& inv) -> ToolResultObject
676+ {
677+ ToolResultObject result;
678+ try
679+ {
680+ json args = inv.arguments .value_or (json::object ());
681+ auto output = detail::invoke_with_json (f, args, names);
682+ result.text_result_for_llm = detail::to_result_string (output);
683+ }
684+ catch (const std::exception& e)
685+ {
686+ result.result_type = " error" ;
687+ result.error = e.what ();
688+ }
689+ return result;
690+ };
691+
692+ return tool;
693+ }
694+
695+ // / Create a tool with a single string parameter (common case)
696+ // / @code
697+ // / auto tool = copilot::make_tool("echo", "Echo message",
698+ // / [](std::string msg) { return msg; }); // Auto-names param "arg0"
699+ // / @endcode
700+ template <typename Func>
701+ Tool make_tool (std::string name, std::string description, Func&& func)
702+ {
703+ using traits = detail::function_traits<detail::remove_cvref_t <Func>>;
704+ std::vector<std::string> names;
705+ for (size_t i = 0 ; i < traits::arity; ++i)
706+ names.push_back (" arg" + std::to_string (i));
707+ return make_tool (std::move (name), std::move (description), std::forward<Func>(func),
708+ std::move (names));
709+ }
710+
531711} // namespace copilot
0 commit comments