123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112(* Meta Model API (Muse Spark) adapter.
Muse Spark is served at https://api.meta.ai/v1 with OpenAI-compatible
endpoints; this adapter rides /v1/responses like the first-party OpenAI
adapter and reuses its serializers and client. Wire rules that differ from
OpenAI (per Meta's Model API docs, verified 2026-08):
- reasoning is always on; [reasoning_effort] "none" is rejected with an
HTTP 400 — the field is omitted instead (the model picks its own level).
Accepted efforts: minimal, low, medium, high, xhigh.
- [tool_choice] supports only "auto"; named, "required", and "none" are
rejected with an HTTP 400 — anything else is omitted.
- [stop] sequences are not supported and are dropped.
- sampling (temperature, top_p, penalties) is fully supported.
- web search runs as a built-in server-side tool ({"type":"web_search"}). *)letdefault_base_url="https://api.meta.ai/v1"letmodels=["muse-spark-1.2";"muse-spark-1.1";"muse-spark-1.2-contributor"]letstringvalue=Chatoyant_runtime.Json.Stringvalueletreasoning_json(options:Provider.options)=matchoptions.reasoning_effortwith|Some"none"|Some"off"|None->None|Someeffort->Some(Chatoyant_runtime.Json.Object[("effort",stringeffort)])lettool_choice(options:Provider.options)=matchoptions.tool_choicewithSome"auto"->SomeOpenai.Auto|_->Noneletweb_search_tool=Chatoyant_runtime.Json.Object[("type",string"web_search")]lettools_json(options:Provider.options)=letfunction_tools=List.mapOpenai.responses_tool_of_provider_tooloptions.toolsinmatchoptions.web_searchwith|Sometrue->function_tools@[web_search_tool]|_->function_toolsletextra_fields(options:Provider.options)=letfields=matchoptions.extrawith|Some(Chatoyant_runtime.Json.Objectfields)->fields|_->[]inletaddnamevaluefields=ifList.mem_assocnamefieldsthenfieldselse(name,value)::fieldsin(fields|>funfields->matchoptions.frequency_penaltywith|None->fields|Somevalue->add"frequency_penalty"(Chatoyant_runtime.Json.Floatvalue)fields)|>funfields->matchoptions.presence_penaltywith|None->fields|Somevalue->add"presence_penalty"(Chatoyant_runtime.Json.Floatvalue)fieldsletresponses_requestmessages(options:Provider.options):Openai.responses_request={responses_model=options.model;responses_input=Input_items(List.concat_mapOpenai.responses_input_items_of_provider_messagemessages);responses_instructions=None;responses_previous_response_id=None;responses_store=Somefalse;responses_stream=false;responses_temperature=options.temperature;responses_top_p=options.top_p;responses_max_output_tokens=options.max_tokens;responses_reasoning=reasoning_jsonoptions;responses_tools=tools_jsonoptions;responses_tool_choice=tool_choiceoptions;responses_text_format=None;responses_parallel_tool_calls=None;responses_truncation=None;responses_metadata=[];responses_extra=extra_fieldsoptions;}moduleMake_provider(Http:Chatoyant_runtime.Effect.HTTP)(Config:sigvalapi_key:stringvalbase_url:stringvaltimeout_ms:intoptionend)=structmoduleClient=Openai.Make_client(Http)letid=Provider.Metaletgeneratemessages(options:Provider.options)=letrequest=responses_requestmessagesoptionsinletconfig={Client.api_key=Config.api_key;base_url=Config.base_url;timeout_ms=Config.timeout_ms;}inmatchClient.create_responseconfigrequestwith|Okresponse->Ok(Openai.generation_of_responses_responseresponse)|Errorerror->Error(Provider.Runtime_errorerror.error_message)end