Providers
Native wire protocols have their own request mappers:
- Gateway (
AI_GATEWAY_API_KEY) posts V4 call options to/language-model,/embedding-model, and/image-model.new GatewayImageModel(provider, modelId, providerName)setsProvider(upstreamGatewayConfig.provider); it defaults togateway.DecisionModel(id)posts the state and questions to/decision-model.GatewayProvider.EvaluationModel(id)is a deprecated alias ofDecisionModel(id), andGatewayProvideralso implements the deprecatedIEvaluationProvider. Every Gateway request carries observability headers fromGatewayOptions.O11yHeaders. When it is null, they come fromVERCEL_DEPLOYMENT_ID(ai-o11y-deployment-id),VERCEL_ENV(ai-o11y-environment),VERCEL_REGION(ai-o11y-region), andVERCEL_PROJECT_ID(ai-o11y-project-id). Empty variables are left out. Theai-o11y-request-idheader is not sent, because the port has no Vercel request context. - Google
DecisionModel(id)answersDecidequestions with Gemini structured output (provider idgoogle.decision). It wraps the Gemini language model inDecisionLanguageModel, which sends one request for every Choice, Score, and Boolean question and calls the model with reasoningnone(the minimum thinking level on Gemini 3 models).providerOptions.googlepasses through, sothinkingConfigoverrides the default. A finish reason other thanSTOP, invalid JSON, an unknown choice, or a score or probability outside its range throwsInvalidResponseDataException. File parts must be images. - OpenAI speaks Chat Completions, the Responses API, embeddings, images, speech, and transcription.
DecisionModel(id)answersDecidequestions through the Decisions API (POST /decisions; provider idopenai.decision).OpenAIProvider.EvaluationModel(id)is a deprecated alias ofDecisionModel(id). Its only provider option issafetyIdentifier(a string of at most 128 characters). Any otheropenaioption is reported as anunsupportedwarning and not sent.OpenAITranscriptionModelreportsLanguageas a two-letter code, whether OpenAI sends the code, a known language name, or a single entry inlanguages. The fulllanguagesarray is kept underProviderMetadata.openai.languages. A refused question returns arefusalanswer, andDecide.DecideAsyncthrowsDecisionRefusalErrorfor it. - OpenAI Responses on GPT-6 and later models accept
reasoningEffortUpdate(none,low,medium,high,xhigh, ormax). Set it in theopenaiprovider options to prepend aconfiguration_updateitem to the input. Set it on aSystemModelMessagewith empty content (through itsproviderOptions) to place the update inside the conversation. Pass that message in the messages list. Theinstructionsargument is rebuilt from text only and drops message provider options. Sampling parameters (temperature,topP) andlogprobsfollow the last update, so anoneupdate on GPT-6 Sol or Luna keeps them. Adjacent updates, unsupported efforts, and message updates on other models throwUnsupportedFunctionalityException. A request-level update on an unsupported model is dropped with a warning. - OpenAI Responses adds
web_search_call.action.sourcestoincludewhen the request has aweb_searchorweb_search_previewtool, so the visited URLs come back as sources. SetincludeWebSearchSources: falsein theopenaiprovider options to turn it off.OpenAIResponsesLanguageModel.SupportsWebSearchSourcesInclude(default true) turns it off for every call of a model. - OpenAI Responses exposes the URLs in
web_search_call.action.sourcesasGeneratedSourceparts, and each retrievedfile_search_callfile once as aGeneratedDocumentSource(even when several passages come from one file, or the file is not cited). Inlineurl_citation,file_citation, andcontainer_file_citationannotations becomeGeneratedText.Citations, with the output range asStartIndexandEndIndex, and on a streamedTextEndStreamPartfor each completed message. Repeated ranges and URLs with query strings or fragments stay distinct. The raw annotations stay in the text part’s provider metadata asopenai.annotations. Afile_pathannotation is a generated file, not a citation. When the response has no retrieval data, the annotation’s own source is still emitted as a source. In a stream that decision is made when the stream ends. Provider-executedweb_search_callandfile_search_callresults are reported as tool results namedweb_searchandfile_search. - OpenAI Responses sends the
reasoningSummaryprovider option (any string, such asauto,concise, ordetailed; OpenAI validates it) asreasoning.summaryon reasoning models, next toreasoning.effort. When a reasoning effort other thannoneis set andreasoningSummaryis absent, the summary defaults todetailed. PassreasoningSummary: nullto send none. SettingreasoningSummaryon a non-reasoning model drops it with anunsupportedwarning. OpenAIToolsbuilds the OpenAI provider-defined tools:ImageGeneration(OpenAIImageGenerationToolOptions),WebSearch(OpenAIWebSearchToolOptions),Mcp(OpenAIMcpToolOptions)(one ofServerUrlorConnectorIdis required),LocalShell(),ApplyPatch(),Computer(),FileSearch(OpenAIFileSearchToolOptions),CodeInterpreter(OpenAICodeInterpreterToolOptions)(ContainerId, orFileIdsfor an automatic container),Shell(OpenAIShellToolOptions),CustomTool(OpenAICustomToolOptions)(the tool name isName;GrammarSyntaxisregexorlark),ToolSearch(OpenAIToolSearchToolOptions),WebSearchPreview(OpenAIWebSearchPreviewToolOptions), andProgrammaticToolCalling(). Each returns aLanguageModelToolwith the upstream camelCase args. The factories do not validate ranges (upstream validates with zod).- OpenAI Responses maps provider-defined tools (
LanguageModelTool.ProviderDefined(id, name, args)) to hosted tools.argsuse the upstream camelCase names. Supported ids areopenai.file_search,openai.local_shell,openai.shell,openai.apply_patch,openai.computer,openai.web_search,openai.web_search_preview,openai.code_interpreter,openai.image_generation,openai.mcp,openai.custom,openai.programmatic_tool_calling, andopenai.tool_search. AToolChoice.Tool(name)that namescode_interpreter,file_search,image_generation,web_search_preview,web_search,mcp,apply_patch,computer, orprogrammatic_tool_callingis sent as that hosted tool, a name that matches anopenai.customtool is sent as acustomchoice, and any other name is a function choice. The provider validatesargswith the upstream schemas and throwsTypeValidationExceptionfor invalid ones. It also reads the OpenAI function tool options inLanguageModelTool.ProviderOptions({ "openai": { ... } }):namespace(nameanddescription, grouped into onenamespacetool),deferLoading,allowedCallers,async, andoutputSchema.asyncis dropped with a warning on models before GPT-6. AskillReferenceskill ofopenai.shellresolves itsproviderReferenceforopenaiand defaultsversiontolatest. OpenAI batches accept function and provider-defined tools. A provider tool other thanopenai.code_interpreter,openai.custom,openai.file_search,openai.web_search, andopenai.web_search_previewadds anunsupportedwarning, because the batch results cannot convert its output. - A
shelltool result may carrymaxOutputLength. OpenAI Responses sends it back asshell_call_output.max_output_length, and ashell_callinput keepstimeoutMsandmaxOutputLengthastimeout_msandmax_output_length. - An
apply_patchtool result ({ status: "completed" | "failed", output?: string }) is sent back as anapply_patch_call_outputitem withcall_id,status, andoutput, also when it shares a tool message with other tool results. An assistantapply_patchcall replays as anitem_referencewhenstoreis on (nothing with a previous response id or a conversation, which already hold it). Otherwise it is rebuilt as anapply_patch_callitem (call_id,id,status: "completed",operation), so theapply_patch_call_outputhas a matching call. Alocal_shelltool result ({ output: string }) is sent back as alocal_shell_call_outputitem, and an assistantlocal_shellcall replays the same way: anitem_referencewhenstoreis on (nothing with a previous response id or a conversation), otherwise alocal_shell_callitem (call_id,id, and anexecactionwithcommand,timeout_ms,user,working_directory, andenv). - OpenAI Responses loads tools after an ordinary tool result. Set
additionalToolsin theopenai(orazure) provider options of aToolModelMessage(itsproviderMetadata) to a list of function definitions (type,name,parameters, and optionaldescriptionandstrict; other keys, or a malformed list, throw). The provider keeps thefunction_call_outputand sends anadditional_toolsitem (role: "developer") right after it, so replaying the same messages keeps the insertion point. An empty list sends no item. SetTool.ProviderOptions(orLanguageModelTool.ProviderOptions) to{ "openai": { "omitFromRequestTools": true } }to keep a discovered tool active for validation and execution without listing its definition in the requesttools. Forcing such a tool withToolChoice.Tool(name)throwsUnsupportedFunctionalityException. Set theallowedToolsprovider option ({ "toolNames": [...], "mode": "auto" | "required" }) to sendtool_choice: { type: "allowed_tools" }and replace the request tool choice. It lists function, hosted, MCP, and custom tools by tool name or by provider tool name. Tools omitted from the request, deferred tools, tools in a namespace, andtool_searchcannot be listed, so they are dropped with a warning. If no entry is left, the call throwsUnsupportedFunctionalityException.OpenAIResponsesLanguageModel.PrepareResponsesToolstakes the same settings inPrepareResponsesToolsOptions. - OpenAI Responses reports each
function_callas aGeneratedToolCall(aToolCallStreamPartwhen streaming, aftertool-input-start,tool-input-delta, andtool-input-endparts). Itsopenaiprovider metadata carriesitemId, plusasync,namespace, andcallerwhen the output item has them. Aprogramcaller becomes{ type: "program", callerId }. Other callers pass through unchanged. A streamedtool-input-endcarriesnamespacetoo. When the model calls an internalparallelwrapper (tool_useswithfunctions.<name>recipients) and you have not declared a tool namedparallel, each nested call whose tool you declared becomes its own tool call with the id{callId}_{index}and anopenai.parallelToolCallmetadata object (itemId,toolCallId,toolName,input,index,count). If any recipient is undeclared, the wrapper is returned as a plainparallelcall. A streamed wrapper buffers its input and emits the expanded calls when the item completes. - OpenAI Responses reports a
programoutput item (programmatic tool calling) as a provider-executedGeneratedToolCall(ProviderExecuted = true; aToolCallStreamPartwhen streaming, onresponse.output_item.done). The tool name is the name you gaveOpenAITools.ProgrammaticToolCalling()(orLanguageModelTool.ProviderDefined("openai.programmatic_tool_calling", name, args)). Its input is{"code","fingerprint"}. Aprogram_outputitem becomes aGeneratedToolResult(aToolResultStreamPartwhen streaming) with{"result","status"}(completedorincomplete). Both carryopenai.itemIdin provider metadata. Function calls the program makes carryopenai.calleras described above. When you send these parts back, a program call becomes anitem_reference(withstore) or aprogramitem, its result becomes anitem_referenceor aprogram_outputitem, and a function call or its result that has anopenai.callerkeeps it ascaller. - OpenAI Responses reads a
custom_tool_callas aGeneratedToolCallwhoseArgumentsJsonis the JSON-encoded input string and whose finish reason isToolCalls, and streams it as aToolInputStartStreamPartwithInputEncoding = ToolInputEncoding.Text, oneToolInputDeltaStreamPartperresponse.custom_tool_call_input.deltaevent (the raw text, not JSON-escaped), aToolInputEndStreamPart, and aToolCallStreamPartwith the sameArgumentsJson. Itsopenaiprovider metadata carriesitemId, plusasyncwhen present. LanguageModelGenerateResult.StructuredOutputTextis the text a JSONOutputparses when it differs from the concatenation of all text parts (null by default; an empty string is an explicit selection and is not replaced by the text parts).GenerateTextparses it instead ofTextfor the final step only, whileText,Content,ResponseMessages, and usage keep the whole response. OpenAI and Azure Responses set it for a JSON call (LanguageModelCallOptions.JsonResponse) to the text of the last message withphase: "final_answer", so commentary or repeated final answers are not joined into invalid JSON. Every message stays inContent, and the messagephaseis reported asopenai.phasein the text part provider metadata. A text part with an empty string is kept.ExtractJsonMiddlewareandExtractReasoningMiddlewareapply their transform to it too. Streaming is unchanged.- OpenAI Responses also reads these provider options:
reasoningMode(standardorpro) is sent asreasoning.mode, andreasoningContext(auto,current_turn, orall_turns) asreasoning.context. Both warn on non-reasoning models.reasoningMode: "pro",contextManagement, andtruncation: "auto"do not allowreasoningEffortUpdate.contextManagement(a list of{ "type": "compaction", "compactThreshold": n }) is sent ascontext_management.compactionTrigger: trueappends acompaction_triggeritem as the last input item.conversationis sent asconversation. It warns when combined withpreviousResponseId. With eitherconversationorpreviousResponseId, assistant reasoning that carries an OpenAIitemIdis not resent. Build that reasoning with theAssistantModelMessageconstructor that takesreasoningProviderOptions. Without a continuation, a reasoningitemIdis sent as anitem_reference(or as areasoningitem whenstoreisfalse).- With
storeenabled, an assistant text part that carries an OpenAIitemId(anAssistantTextPartwith provider options) is sent as anitem_reference. For a reasoning model, the reference is sent only when a reasoning part comes before the text in the same assistant message, because OpenAI rejects a stored message that is sent without its reasoning item. When the reasoning has been removed (for example by pruning), the text is sent inline instead. Withconversation, text parts that carry anitemIdare skipped because the conversation already holds them. - In multi-step
GenerateTextandStreamText, the provider metadata of reasoning (GeneratedReasoning.ProviderMetadata, or the metadata onReasoningStartStreamPartandReasoningEndStreamPart) is carried to the next step asAssistantModelMessage.ReasoningProviderOptions. OpenAI Responses reasoning returnsitemIdandreasoningEncryptedContentthis way, so withstore: falsethe encrypted reasoning is sent back.
- Azure OpenAI Responses:
AzureOpenAIProvider.ResponsesModel(id)posts to/openai/v1/responses(provider idazure.responses). Provider options and message options are read fromazurefirst and fall back toopenaionly when theazurekey is absent. An emptyazureobject therefore hides theopenaioptions. - Azure deployment and model IDs:
AzureOpenAIProvider.ResponsesModel(deploymentId, settings),ChatModel(deploymentId, settings)andCompletionModel(deploymentId, settings)take an optionalAzureOpenAILanguageModelSettings { ModelId = "gpt-5" }, for exampleprovider.ResponsesModel("production-chat", new AzureOpenAILanguageModelSettings { ModelId = "gpt-5" }). The model reportsModelIdas its identity (including telemetry) and, for Responses and Chat, uses it to detect capabilities such as reasoning support (notemperature,developersystem role). API requests (and the deployment route, withUseDeploymentBasedUrls) still use the deployment name. The model ID Azure returns in response metadata is preserved. IfModelIdis omitted, the deployment name is used for identity and capability detection. Keep the supplied model ID aligned with the model running in your deployment. The Responses reasoning-effort update check reads the model ID, so a custom deployment name such asproductionis only recognized when you passModelId = "gpt-6-astra"(or another supported GPT-6-or-later model). - TypeSafe (
TYPESAFE_AI_API_KEY) posts the state and questions to/systemonethroughTypeSafeAiProvider.DecisionModel(id)(provider idtypesafe.decision).providerOptions.typesafe.longContextraises the context window from 64k input and 32k output tokens to 262k. PNG and JPEG file parts are sent asmultipart/form-dataimages; other files throwUnsupportedFunctionalityException. See TypeSafe. - Anthropic speaks the Messages API.
AnthropicAwsProviderpoints that body at the AWS external endpoint.DecisionModel(id)answersDecidequestions by wrapping the Messages language model inDecisionLanguageModel(provider idanthropic.decision).AnthropicProviderimplementsIDecisionProvider, and the deprecatedIEvaluationProvider.EvaluationModel(id)resolves to the same model.AnthropicProvider.EvaluationModel(id)is a deprecated alias ofDecisionModel(id).AnthropicProvider.RubricModel(id)returns the rubric model. A stream whose events end without amessage_stopfor the last message finishes withFinishReason.Interruptedand no raw finish reason, even when an earlier message in the same stream completed. A stream that ends after an error event or an invalid event gets no interrupted finish. A stream that ends before it produced any output also gets no interrupted finish, and the provider reports no finish part for it (the core no-output rejection is tracked in #769). The interrupted finish keeps the usage of the last completed message, or unset usage when none completed.GeneratedSourceparts come fromweb_search_tool_resultblocks, one per search result. The citations on a text block are attached to itsGeneratedText.Citations(aCitationfor each ofweb_search_result_location,search_result_location,page_location,char_location, andcontent_block_location), including on a streamedTextEndStreamPart. They are not repeated as sources. Aweb_search_result_locationcitation only becomes aGeneratedSourcewhen the response has no search results. The raw citations stay in the text part’s provider metadata asanthropic.citations. Page, character, and block locations refer to the source document, not to the generated answer. A document citation uses thetext/plainmedia type, and the title falls back toDocument {index}, because the port does not track the prompt’s documents.compactionin theanthropicprovider options (typeand optionalinstructions) asks Anthropic to summarize the conversation on demand. It is sent ascompactionwith thecompact-2026-09-04beta header. Combining it withcontextManagementthrowsInvalidArgumentExceptionforproviderOptions. Acompactionresponse block becomes a text part whoseanthropicmetadata holdstype: "compaction"and itssignature. Replay it as an assistant text part with that sameanthropicprovider metadata, and the signedcompactionblock (withsignature) goes back to the API.claude-sonnet-5-5always thinks and rejects thinking token budgets and forced tool use. Its lowest thinking setting isthinking: { type: "between_tools" }: no upfront thinking, only short progress notes between tool calls.thinking: { type: "disabled" }andreasoning: "none"are sent asbetween_tools(the first with a warning).between_toolsis accepted only atlow,medium, andhigheffort. Atxhighormaxthe effort is lowered tohighwith a warning.thinking: { type: "enabled", budgetTokens }is sent as{ type: "adaptive" }with a warning. Arequiredor named tool choice falls back toauto, andstructuredOutputMode: "jsonTool"falls back to nativeoutput_config.format, each with a warning.claude-haiku-5-5supports every effort level, includingxhigh. It uses adaptive thinking and rejects thinking token budgets:thinking: { type: "enabled", budgetTokens }is sent as{ type: "adaptive" }with a warning, so useeffortto control how much it thinks. Unlikeclaude-sonnet-5-5, thinking can be disabled, but only atlow,medium, andhigheffort. Atxhighormaxthe effort is lowered tohighwith a warning. Structured output uses nativeoutput_config.format.- With manual thinking (
thinking: { type: "enabled", budgetTokens }), arequiredor named tool choice falls back toautowith a warning. A named tool choice sends only the selected tool. This also applies when the top-levelReasoningoption enables manual thinking, including when structured output uses the internaljsonresponse tool. Tool use is no longer guaranteed: instruct the model to use the tool in your prompt and verify that a tool call was made.
- Google speaks Gemini
generateContent.GoogleVertexProviderchanges the base URL and sends a bearer token.GoogleVertexAnthropicProviderreadsproviderOptions.vertex.labels, a string-to-string map for billing labels, and sends it as the base64 JSONx-vertex-ai-labelsheader for generate and stream (never in the body). Invalid labels throwInvalidArgumentErrorbefore any request, and an explicitx-vertex-ai-labelsheader, from the call orGoogleVertexAnthropicOptions.Headers, wins. - A Gemini stream that carries an
errorobject mid-stream (code,message,status,details) emits anErrorStreamPartwhoseErroris aProviderStreamError(Typeis thestatus,CodeandStatusCodethecode,Datathe whole error object) and finishes withFinishReason.Error.IsRetryableis true for codes 408, 409, 429, and 500 and above. Text already streamed is still closed and its usage is reported.GoogleVertexProviderlanguage models do the same. - Google
responseModalities(agoogleprovider option) lists the response modalities:TEXT,IMAGE, andAUDIO. Support is model-dependent. When it is absent or empty, the model returns only text. Non-textinlineDatain the response, such asaudio/wav, comes back as aGeneratedFilewith its media type.- Long decode (Gemini 4 models such as
gemini-4-argon):MaxOutputTokensis a cumulative budget across requests, not a target length. A request that reaches the per-request cap while budget remains finishes withCONTINUATION, which maps toFinishReason.Lengthand keeps the raw reason.DoGenerateAsyncandDoStreamAsyncthen send the same request again with a top-levelcontinuationTokenand combine the results into one response. SetMaxOutputTokensabove the per-request cap to enable this. Without it the call ends after one request. The stream keeps one text block across requests. Allow HTTP read timeouts of at least 15 to 20 minutes, because a complex request can run for several minutes. LanguageModelUsageis the sum of all requests, which matches billing. Each continued request re-sends the prompt plus the output so far, so do not use the summed input tokens for context window checks. When a response spanned more than one request,ProviderMetadatagoogle.initialPromptTokenCountholds the prompt token count of the first request.- A response that used up its
MaxOutputTokensbudget finishes withFinishReason.Lengthand exposesProviderMetadatagoogle.continuationToken. Pass it back asproviderOptions.google.continuationTokenwith a higherMaxOutputTokensand the original prompt to resume. A stream exposes the token only when it ended withMAX_TOKENSorCONTINUATION. If a continuation request fails, the stream emits anErrorStreamPartand finishes withLength, the raw reasonCONTINUATION, and the last token.GoogleVertexProviderlanguage models do the same. GoogleProvider.Interactions(id)withproviderOptions.google.background: trueruns the model call on the server, andDoGenerateAsyncsendsgeneration_config.max_output_tokensfromMaxOutputTokensand pollsGET /interactions/{id}until the status is terminal. The server runs every request, so there is no client-side continuation. SetMaxOutputTokensabove 65,536 for long decode. Withoutbackground: true, an interaction generates at most 65,536 tokens. Polling starts at 1 second, doubles up to 10 seconds, and gives up after 30 minutes. SetproviderOptions.google.pollingTimeoutMs(a positive integer, in milliseconds) to change that limit for slower runs. If you cancel the call while it polls, the SDK sends a best-effortPOST /interactions/{id}/cancelso the run stops on Google’s side. A polling timeout only stops the wait and does not send that cancel request, so the server-side interaction may keep running and keep billing.providerOptions.google.store(a boolean, defaulttrue) is sent asstore. Set it tofalsefor stateless multi-turn conversations: re-send the full message history on every turn. The API then returns no interaction id.providerOptions.google.previousInteractionIdis sent asprevious_interaction_id. Withstore: falsethe SDK sends both and adds anotherwarning, because the server has nothing stored to refer to. The SDK does not drop earlier turns from the history yet: it always sends the whole prompt.providerOptions.google.serviceTier(flex,standard, orpriority) is sent asservice_tier. The applied tier comes back asProviderMetadatagoogle.serviceTier, from the response body or, as a fallback, thex-gemini-service-tierheader.ProviderMetadatagoogle.interactionIdandLanguageModelGenerateResult.ResponseIdcarry the interaction id when the API returns one.google.outputTokensByModalitymaps each output modality to its token count, for example{ "video": 57920, "text": 19 }, and is absent when the response has no breakdown.Temperature,TopP,TopK,Seed,StopSequences,MaxOutputTokens,providerOptions.google.thinkingLevel(minimal,low,medium, orhigh), andproviderOptions.google.thinkingSummaries(autoornone) go intogeneration_config.FrequencyPenaltyandPresencePenaltyare not supported and add anunsupportedwarning.JsonResponsesendsresponse_format: [{ type: "text", mime_type: "application/json", schema }], withJsonSchemaas the schema when set. An agent call drops it with anotherwarning.GoogleProvider.Interactions(new GoogleInteractionsAgent(name))calls a Gemini agent such asdeep-research-pro-preview-12-2025.GoogleInteractionsAgent.Managed(name)calls a managed agent you created on Google’s side. The request sendsagentinstead ofmodeland nogeneration_config.Temperature,TopP,TopK,FrequencyPenalty,PresencePenalty,Seed,StopSequences,MaxOutputTokens,thinkingLevel, andthinkingSummariesare dropped with oneotherwarning.DoGenerateAsyncpollsGET /interactions/{id}until the status is terminal, like abackground: truecall.DoStreamAsyncposts the call withstream: trueand reads the event stream as it arrives. Text, reasoning (with its signature), client function calls (tool-input-start,tool-input-delta,tool-input-end,tool-call), built-in tool calls and results (providerExecutedcalls andtool-resultparts), sources (fromtext_annotationdeltas and built-in tool results, each URL or file once),google.processing_callandgoogle.processing_resultcustom parts, response metadata, errors, and the finish part come through as they arrive. TheserviceTieron the finish part comes frominteraction.completed, with thex-gemini-service-tierresponse header as the fallback (not read for a background call). Withbackground: true, the call is posted first, then the SDK streamsGET /interactions/{id}?stream=true. When the connection drops, the SDK reconnects withlast_event_idand keeps the stream going. After three failed attempts in a row the stream throws. If the POST already returns a terminal status, the stream replays that response. If you cancel the call, or stop reading before the run ends, the SDK sends a best-effortPOST /interactions/{id}/cancel. Image and video outputs are not yet streamed:FileStreamPartcarries bytes only, and the API can return auri.
- Long decode (Gemini 4 models such as
- Amazon Bedrock signs a Converse request with Signature Version 4. It signs the caller’s headers too, and sends a header with a non-ASCII value without signing it. Provider options come from
amazonBedrock, thenbedrock.reasoningConfig(type,budgetTokens,maxReasoningEffortoflow,medium,high,xhigh, ormax, anddisplay) becomesadditionalModelRequestFields.thinkingandoutput_config.efforton Anthropic models,reasoning_effortorreasoning.efforton OpenAI models, and areasoningConfigfield on other models such as Nova. The top-levelReasoningoption fills inreasoningConfig, and fields you set win.serviceTier(reserved,priority,default, orflex) selects the inference service tier and is sent asserviceTier.type.Temperatureis clamped to the 0 to 1 range with a warning. Models that reject sampling settings, such as Claude Opus 4.7, dropTemperature,TopP, andTopKwith a warning. OpenAI models dropStopSequences, and OpenAI models other than gpt-oss also dropTemperatureandTopP, each with a warning. Structured output (JsonSchema) on an Anthropic model followsstructuredOutputMode(outputFormat,jsonTool, orauto, read fromamazonBedrock, thenanthropic). Inauto, models that support it send a sanitizedadditionalModelRequestFields.output_config.format, andjsonToolremoves a manually setformat. Otherwise a forcedjsontool carries it, and the tool input comes back as the response text withproviderMetadata.bedrock.isJsonResponseFromToolset. Models that reject forced tool use, and models that rejectstricttools when you also pass tools, get a JSON instruction in the system message instead, and the first JSON object in the reply becomes the text.DoStreamAsynccalls/converse-streamand decodes the AWS event stream (application/vnd.amazon.eventstream) as it arrives, so text, reasoning, and tool input are emitted as they are generated. A frame that fails its checksum, or bytes left over when the stream ends, throw. A modeled exception frame (internalServerException,modelStreamErrorException,serviceUnavailableException,throttlingException,validationException) becomes anErrorStreamPartwhoseErrorcarriesStatusCode(500, 424, 503, 429, 400) andIsRetryable, and the stream finishes withFinishReason.Error. Streamed reasoning text carries its signature on aReasoningDeltaStreamPart.ProviderMetadata, and redacted reasoning content is attached once on theReasoningEndStreamPart. ATextContentPartor an imageFileContentPartwith bytes sends aguardContentblock when itsProviderOptionsholdguardContent: trueunderamazonBedrockorbedrock. A text part can also setguardContentQualifiers(grounding_source,query, orguard_content). AcachePointinProviderOptions(for example{ "type": "default", "ttl": "1h" }, withttlof5mor1h) adds acachePointblock right after the content it belongs to. It works onSystemModelMessage,UserModelMessage,AssistantModelMessage,TextContentPart,FileContentPart,ToolModelMessage(setProviderOptionson each tool result, andMessageProviderOptionsto add one after all results of a grouped message), and the parts of an assistant message, reasoning parts included. An assistant message left with only cache points is dropped. A provider-executedAssistantToolResultPartends the assistant message in front of it and is sent as a usertoolResultmessage, so a result that ends an assistant message joins the next user message. Assistant reasoning is replayed only when itsProviderOptionshold asignature,redactedContent, orredactedDataunderamazonBedrockorbedrock. Reasoning without one of them is left out, and the text of signed reasoning is sent untrimmed. An empty or whitespace text part is left out unless the message has a reasoning part. The last assistant message of a prompt has its trailing whitespace trimmed, because Bedrock rejects it in a pre-filled response. Every provider option that is not one ofreasoningConfig,additionalModelRequestFields,serviceTier,structuredOutputMode, orrequestMetadata(for exampleguardrailConfig) goes into the request as is. Anthropic models also ask Bedrock for/delta/stop_sequencesoproviderMetadata.bedrock.stopSequenceis set.AmazonBedrockOptions.HeadersandLanguageModelCallOptions.Headersare sent on Converse, Converse stream, embedding, and rerank requests before signing.AmazonBedrockOptions.HeadersProvideris called on every request (upstream’s resolvableheaders); its values win overHeaders, and a null value drops a header. Call headers win over provider headers, and every request carries theai-sdk-amazon-bedrockuser agent.AmazonBedrockFetchandAmazonBedrockSigV4Fetchapply the same signing and theai-sdk-amazon-bedrockuser agent to any other Bedrock call.DoStreamAsyncreturns aLanguageModelStreamResult:Streamholds the parts,Request.Bodyholds the JSON request body, andResponse.Headersholds the response headers.DoGenerateAsyncreturns the headers onLanguageModelGenerateResult.ResponseHeaders. A stream that ends without amessageStopframe finishes withFinishReason.Interrupted, unless a chunk or modeled exception frame already setFinishReason.Error. - Azure OpenAI uses the
api-keyheader and the/openai/v1routes (/openai/v1/responses,/openai/v1/chat/completions,/openai/v1/completions,/openai/v1/embeddings) withapi-version=v1. SetAzureOpenAIOptions.UseDeploymentBasedUrls = truefor the legacy/openai/deployments/{deployment}/...routes;ApiVersionsets theapi-versionvalue.AzureOpenAIProvider.LanguageModel(id)returns a Responses model. UseChatModel(id, settings)for Chat Completions. Breaking changes: Azure chat now runs onOpenAIChatLanguageModel(ChatModel(id, settings)returns it), so it reads only theopenaiprovider options (theazurekey is no longer read) and its error parsing and usage follow that class.ChatUri,CompletionsUri,EmbeddingsUriandCreateChatModel(id)on an Azure provider route to/openai/v1. An empty or nullApiVersionfalls back tov1. - Bedrock Runtime and Agent Runtime origins resolve in this order:
AmazonBedrockOptions.BaseUrl(orAmazonBedrockAnthropicOptions.BaseUrl) andAgentRuntimeBaseUrlfor rerank, thenAWS_ENDPOINT_URL_BEDROCK_RUNTIMEorAWS_ENDPOINT_URL_BEDROCK_AGENT_RUNTIME, thenAWS_ENDPOINT_URL, thenhttps://{service}.{region}.{suffix}. The region is only validated when it builds the host.AmazonBedrockProviderConverse, embedding, and rerank models andAmazonBedrockAnthropicProviderall follow it. AmazonBedrockAnthropicProviderruns the Anthropic Messages model through BedrockInvokeModel(/model/{id}/invoke, or/invoke-with-response-streamfor streaming), so tools, files, images, reasoning, structured output, andanthropicprovider options all work as they do onAnthropicProvider. It signs the request with Signature Version 4, or sendsAuthorization: BearerwhenApiKeyorAWS_BEARER_TOKEN_BEDROCKis set. Before sending it dropsmodelandstream, addsanthropic_version: bedrock-2023-05-31, keeps only thetypeandnameoftool_choice, renames the thinkingblock_binding.prefix_mismatch_behaviorfield tomismatch_behavior, upgrades thebash_20241022,text_editor_20241022, andcomputer_20241022tools to the versions Bedrock supports, turns a tool’seager_input_streaminginto thefine-grained-tool-streaming-2025-05-14beta, and moves the betas intoanthropic_beta. Bedrock does not accept URL sources, so images and files are downloaded and sent as base64. Bedrock errors become Anthropic error bodies, and the AWS event stream becomes server-sent events (AmazonBedrockAnthropicFetch.Createwraps anyHttpMessageHandlerwith that conversion).AmazonBedrockAnthropicOptions.Headersare sent with every call.CredentialProvidersupplies SigV4 keys for each request instead ofAccessKeyId,SecretAccessKey, andSessionToken; the region still comes fromRegion.AmazonBedrockAnthropicProvider.Toolslists the Anthropic provider tool ids, andEmbeddingModelandImageModelthrowNoSuchModelError.BedrockMantleProviderruns the OpenAI Chat Completions model (LanguageModel,Chat) or the OpenAI Responses model (Responses) against the Mantle endpoint, with the request signed for thebedrock-mantleservice (or sent with a bearer token). Tools, files, images, reasoning, structured output, streaming, andopenaiprovider options work as they do onOpenAIProvider.BedrockMantleOptions.Headersare sent with every call. The Mantle Responses model never addsweb_search_call.action.sourcestoinclude.CredentialProvidersupplies SigV4 keys for each request instead ofAccessKeyId,SecretAccessKey, andSessionToken.EmbeddingModelandImageModelthrowNoSuchModelError.- Cohere speaks chat, embed, and rerank. A chat stream that ends without a
message-endevent finishes withFinishReason.Interrupted, unless an invalid chunk already setFinishReason.Error. - OpenResponses posts to
{base}/responses. The default base is the Gateway Open Responses route.OpenResponsesOptions.Name(defaultopen-responses) is the key for provider options and provider metadata, and the model’s provider id is{Name}.responses. System messages becomeinstructions. Text and tool calls stream as they arrive, and reasoning streams fromreasoning_summary_textandreasoning_textdeltas. Reasoning, text, and tool-call parts carry theiritemIdin provider metadata (GeneratedText.ProviderMetadataand streamedtext-endparts also carryurl_citationannotations), and reasoning keeps its summary, content, and encrypted content. Each reasoning content part becomes its ownGeneratedReasoning.AssistantModelMessage.Partskeeps the text, reasoning, and tool-call parts in order, each with its own provider options (AssistantTextPart,AssistantReasoningPart,AssistantToolCallPart), so a later turn replays the items in the original order with their ids and annotations.GenerateTextbuilds these parts from the model output.StrictResponseInputserializes assistant history using the strict OpenAI Responses input schemas: assistant text without an item id is sent as an easy input message, and text with an item id as a complete output message. - OpenResponses warns and ignores
StopSequences,TopK, andSeed.OpenResponsesOptions.Headersare sent with every request. It sends function tools. A provider-defined tool (LanguageModelTool.ProviderDefined(id, name, args)) is omitted with anunsupportedwarning (provider-defined tool <id>) unless an extension registers it. AToolChoice.Tool(name)that selects an omitted provider tool is omitted too. - OpenResponses stream
response.failedanderrorevents becomeErrorStreamParts whoseErroris aProviderStreamErrorwith the event type, the errorcodeandmessage, and the whole event asData.OpenResponsesOptions.GetResponseErrorMetadatareads optionalStatusCodeandIsRetryablevalues from a responseerrorobject, including endpoint-specific fields. It applies to generation failures and to streamingresponse.failedanderrorevents. The standard Open Responses error fields arecodeandmessage, so endpoint-specific status fields need this callback. Without it, a generation failure has status 400. When only a status code is supplied, retryability comes from that status.
OpenResponses extensions (experimental)
Section titled “OpenResponses extensions (experimental)”Open Responses implementations can add namespaced tools, items, and streaming events. Register an OpenResponsesExtension in OpenResponsesOptions.Extensions to encode and decode an implementation’s wire formats. This API is experimental and may change.
The provider-tool id uses dot notation (acme.document_search). Open Responses wire types use colon notation (acme:document_search). The namespace in Id must match the namespace in every registered wire type.
var extension = new OpenResponsesExtension{ Id = "acme.document_search", ToolType = "acme:document_search", ItemTypes = new[] { "acme:document_search_receipt" }, EncodeTool = context => new JsonObject { ["name"] = context.Name, ["index"] = context.Args.GetProperty("index").GetString(), }, DecodeItem = context => new GeneratedContent[] { new GeneratedToolCall((string)context.Item["call_id"]!, (string)context.Item["name"]!, context.Item["query"]!.ToJsonString()) { ProviderExecuted = true, }, new GeneratedToolResult((string)context.Item["call_id"]!, (string)context.Item["name"]!, context.Item["result"]!.ToJsonString()), },};
var acme = OpenResponsesProvider.Create(new OpenResponsesOptions{ Name = "acme", BaseUrl = "https://api.acme.example/v1", Extensions = new[] { extension },});Set ProviderExecuted on each decoded tool call or ToolInputStartStreamPart. It can vary by item or event. An extension can define:
EncodeTool: encodes a provider tool fromOpenResponsesToolContext(Name,Args). Returningnullomits the tool with anunsupportedwarning. A tool choice that selects the omitted tool is omitted too.EncodeToolChoice: encodes a specific tool choice. The default is an object with only thetype.DecodeItem: maps a completed item (OpenResponsesDecodeItemContext.Item, plusModeofGenerateorStream) to content parts.EncodeInputItem: encodes a tool call or tool result in history (AssistantToolCallPartorAssistantToolResultPart) when the original wire item is unavailable. Every returned item needsid,status, and a registered namespacedtype.DecodeEvent: maps a streaming event to stream parts.OpenResponsesDecodeEventContext.Statelasts for one response stream.
Tool, item, and event capabilities are independent. Capability fields must be provided in pairs.
Each decoded item adds a GeneratedCustom part of kind open-responses.extension-replay (a CustomStreamPart when streaming). It stores the original item in provider metadata, and the other decoded parts reference it by itemId. Pass the result back with AssistantModelMessage.FromGeneratedContent(result.Content) to replay the original item once, opaque fields included. This also preserves items that decode only to source parts.
The model validates only the extension discriminator fields. Validate the other item and event fields in the codec. The original item is stored in provider metadata and can appear in persisted messages, telemetry, logs, or UI payloads.
Extension delegates cannot cross a workflow boundary. OpenResponsesLanguageModel.Serialize() throws a SerializationError when extensions are registered. Create the provider inside the workflow step.
- Perplexity
DecisionModel(id)answersDecidequestions through the Decisions API (POST {base}/v1/decisions; provider idperplexity.decision).PerplexityProviderimplementsIDecisionProvider. Any model id is sent unchanged (for examplepplx-decider-latest,pplx-decider-flash-latest,pplx-decider-v1.2, orperplexity/pplx-decider-latest). Abooleanquestion is sent as Perplexity’s nativenoultype, and thenoulvalue comes back as the answer’sProbability. Choice and Score answers keep the API’s probability distributions without rounding or normalizing, so noDecisionRoundingis declared. Aboxquestion throwsDecisionUnsupportedQuestionTypeException. State parts map to the nativestatefield: a single text, object, or array part is sent as is, and anything else is sent as an array. PNG, JPEG, and WebP files become base64image_urldata URLs, at most eight per request, counting images already nested in a json part. A file with another media type, or a URL file, throwsUnsupportedFunctionalityException. The model rejects, before any request, more than 128 questions, an empty question name, more than 255 choice options, and a score with no level, more than 10 levels, or a null level. A single score level returns score0with probability1. Choice and Score confidence is inProviderMetadataatperplexity.confidence, and Score legends atperplexity.legend, both keyed by question id. The model has no provider options, so every entry inproviderOptions.perplexityis reported as anunsupportedwarning and not sent. The responseIdis thex-request-idheader. A failed request throws anApiExceptionthat carries the response headers inResponseHeaders, so a caller can readretry-after.PerplexityDecisionModel.Serialize()returns aSerializedModelOptionswith the model id,baseURL, and the resolved request headers, including theAuthorizationheader.PerplexityDecisionModel.Deserialize(options, httpClient)restores the model. The HTTP client is not serialized. Runnable examples are inexamples/DecidePerplexityBasic,examples/DecidePerplexityCriteriaOnly, andexamples/DecidePerplexitySingleLevelScore. - Perplexity language generation posts to
{base}/v1/agent(the Agent API). Preset ids arefast,low,medium,high, andxhigh. Any other id is sent as an Agent API model id. Legacy Sonar model ids are not aliased, and Sonar provider options are not translated. Embeddings stay on the OpenAI-compatible embeddings route. Sonar PDF input, video input, and image or video results have no Agent API equivalent. A stream that ends without aresponse.completedorresponse.incompleteevent finishes withFinishReason.Interrupted. One of those events without response details finishes withFinishReason.Other.
Set LanguageModelCallOptions.JsonResponse to ask for JSON without a schema (upstream responseFormat: { type: 'json' }). OpenAI, OpenAI-compatible, Open Responses, Cohere, and xAI providers send json_object, Google sends responseMimeType: application/json, and the Gateway sends json. Anthropic ignores it and warns that the JSON response format requires a schema. Amazon Bedrock and Hugging Face also ignore it, as upstream does. JsonSchema wins when both are set.
The portable Reasoning option on GenerateTextOptions and LanguageModelCallOptions accepts none, minimal, low, medium, high, xhigh, and max. Budget-based reasoning uses 95 percent of the output budget for max. Providers with a lower ceiling coerce max and return a compatibility warning:
- Anthropic, DeepSeek, Moonshot, and OpenAI models that list
maxsend it unchanged, with no warning. - Google (Gemini 3), Groq, and Fireworks send
high. Fireworks also sendslowforminimalandhighforxhigh. Each change adds a warning. AreasoningEffortprovider option wins overReasoningand adds no warning. - Perplexity sends
xhigh. - Mistral sends
reasoning_efforthighfor every level exceptnone, and returns acompatibilitywarning when it changes the level. Models without reasoning effort (such asmistral-large-latest) get noreasoning_effortand anunsupportedwarning. AreasoningEffortprovider option wins. - OpenResponses sends
reasoning.effort(minimalbecomeslow;xhighandmaxare sent unchanged). The provider optionsreasoningEffortandreasoningSummary, under the providerName, setreasoning.effortandreasoning.summary. - xAI Responses (
XaiProvider.LanguageModelandXaiProvider.ResponsesModel) sendsreasoning.effort(minimalbecomeslow).xhighandmaxare sent asxhighongrok-4.6and ashighon other models, with acompatibilitywarning when the level changes. Thegrok-4.20reasoning and non-reasoning models reject the parameter: they get noreasoningobject and anunsupportedwarning. AreasoningEffortprovider option wins and adds no warning.
Moonshot native structured outputs are enabled for every model id, including Kimi, Moonshot V1, and custom or future ids. A request with a JSON schema sends response_format.type = json_schema with strict: true. A JSON request with no schema uses json_object.
Moonshot validates tool and response schemas with MFJS, so Moonshot requests normalize them first. The root must be an object: a root anyOf or oneOf whose branches are all object schemas (a branch may be a local #/$defs/X or #/definitions/X reference) gets type: "object" added, and a root that already has type: "object" next to anyOf keeps it. Any other root throws UnsupportedFunctionalityException, such as a nullable object branch or a non-local $ref. Below the root, tuple items become prefixItems, and a type next to anyOf moves into the branches. The original schema still validates the result.
Groq rejects response_format together with tools, and Gemini before Gemini 3 rejects JSON response formatting together with function tools. When a call has a JSON schema and at least one function tool, these providers send an internal function tool whose arguments are the structured output, and turn that tool call back into text. The tool is named json, or json_1, json_2, and so on when a caller tool already uses the name. Its description is the output description (OutputSpec.Object(schema, name, description), or LanguageModelCallOptions.JsonSchemaDescription), or Respond with a JSON object.. Tool choice Tool("name") is kept, None selects the response tool, and any other choice becomes Required. Groq sets parallel_tool_calls to false and skips the structuredOutputs warning on this path. Gemini 3 keeps native responseMimeType and responseJsonSchema with automatic or omitted tool choice, and uses the response tool only for Required or a named tool. Conversational text is dropped, calls to your own tools stay tool calls, and a response that has only the response tool finishes with Stop. Without function tools, both providers keep native structured output. JsonResponseTools.ConvertJsonResponseToolStream applies the same rewrite to a stream of LanguageModelStreamPart.
OpenAI-compatible providers (Alibaba, Groq, DeepSeek, Mistral, Together, and the other chat wrappers) are thin wrappers over Vercel.AI.OpenAICompatible. They set the base URL, the provider id, and the environment variable. Perplexity embeddings use that client. Perplexity language generation uses the Agent API described above.
An OpenAI-compatible image model (ImageModel(id)) reads image generation and editing responses that hold base64 data (b64_json), image URLs (url), or a mix of both, and keeps the images in order. A url image is downloaded, so GeneratedImage.Data and GeneratedImage.Base64 are filled. When an image has both fields, base64 wins and nothing is downloaded. A data item with neither field throws InvalidResponseDataException. The download request carries the provider headers only when the URL is on the provider origin. Any other host is checked first (private addresses and non-http(s) schemes throw DownloadException), redirects are checked hop by hop, and no provider header is sent. Set OpenAICompatibleOptions.DownloadHttpClient to control the client used for those hosts.
Speech, transcription, image, video, and Voyage each have a package that calls that provider’s public HTTP API. Use SpeechModel, TranscriptionModel, ImageModel, VideoModel, or EmbeddingModel rather than LanguageModel. Every provider (ProviderBase.SpecificationVersion) and every video model (IVideoModel.SpecificationVersion) reports v4.
Alibaba chat
Section titled “Alibaba chat”The Alibaba chat model (AlibabaProvider.LanguageModel(id)) builds its own request from the OpenAI-compatible chat model. Pass options as JSON under alibaba in LanguageModelCallOptions.ProviderOptions.
| Option | Request field | Notes |
|---|---|---|
enableThinking |
enable_thinking |
Turns thinking mode on or off. |
thinkingBudget |
thinking_budget |
Maximum reasoning tokens. |
preserveThinking |
preserve_thinking |
Replays earlier assistant reasoning as reasoning_content. Defaults to true for models Alibaba documents as supporting it (such as qwen3.7-max). Other models send the field only when you set it. |
parallelToolCalls |
parallel_tool_calls |
Sent only when the call has tools. |
LanguageModelCallOptions.Reasoningmaps toenable_thinkingandthinking_budget.nonesendsenable_thinking: false. Any other level sendsenable_thinking: truewith a budget.enableThinkingandthinkingBudgetwin overReasoning. Alibaba never receivesreasoning_effort.LanguageModelCallOptions.TopKis sent astop_k.FrequencyPenaltyis not sent, and the call gets anunsupportedwarning forfrequencyPenalty.- Function tools are sent with their
strictsetting when you set one. A provider-defined tool is left out and the call gets anunsupportedwarning,provider-defined tool <id>.ToolChoicemaps toauto,none,required, or a named function. - Reasoning from the current tool-call round (after the last user message) always goes back with the tool results. Earlier reasoning is sent only when
preserveThinkingis on. An assistant message with no text, no tool call, and no reasoning to send is left out. - Models
qwen3.7-plus,qwen3.7-flash,qwen3.7-max,qwen3.8-max, andqwen3.8-flash(and their dated ids) useresponse_format.type = json_schemawhen the call has a JSON schema. Other models usejson_object, inject the schema into the system message, and add acompatibilitywarning. A JSON call with no schema usesjson_objectand the instructionYou MUST answer with JSON.. DoStreamAsyncstreams reasoning, text, and tool calls as separate blocks. A reasoning block ends when text or a tool call starts, and a text block ends when reasoning or a tool call starts. Text uses block id0, and each reasoning block gets a new generated id. Every tool call streamstool-input-start,tool-input-delta, andtool-input-endparts before itstool-callpart. A chunk with an emptytool_callsarray does not end the reasoning block.- Usage reports
cache_creation_input_tokensas cache write tokens. Cache reads and writes are both insideprompt_tokens, soNoCacheInputTokensexcludes both. The raw usage object keeps fields the SDK does not map, such ascache_type.
Prompt caching
Section titled “Prompt caching”Alibaba caches repeated prompts without any setting (implicit caching). For more control, mark content with alibaba.cacheControl (explicit caching). The value, for example { "type": "ephemeral" }, is sent as cache_control and Alibaba validates it. alibaba.cache_control works too.
Set it in the ProviderOptions of a SystemModelMessage, UserModelMessage, AssistantModelMessage, TextContentPart, FileContentPart, or ToolModelMessage. The marked content is sent as a text block with cache_control, or as a part that carries it. A part-level marker wins over a message-level marker. A message-level marker on a user message marks its last part. On a tool message, set ProviderOptions on a result to mark that result, or MessageProviderOptions to mark the last result.
var system = new SystemModelMessage( "You are a helpful assistant. [... long system prompt ...]", new Dictionary<string, JsonElement> { ["alibaba"] = JsonDocument.Parse("{\"cacheControl\":{\"type\":\"ephemeral\"}}").RootElement.Clone(), });var message = new UserModelMessage(new UserContentPart[]{ new TextContentPart("Context: Please analyze this document."), new TextContentPart(longDocument) { ProviderOptions = new Dictionary<string, JsonElement> { ["alibaba"] = JsonDocument.Parse("{\"cacheControl\":{\"type\":\"ephemeral\"}}").RootElement.Clone(), }, },});The minimum content length for a cache block is 1,024 tokens. Alibaba allows 4 cache breakpoints per request. A fifth marker adds an other warning, Max breakpoint limit exceeded. Only the last 4 cache markers will take effect., and is still sent.
HeyGen
Section titled “HeyGen”The HeyGen provider (Vercel.AI.HeyGen) supports heygen-video-1, a video generation model with text-to-video, image-to-video, and reference-to-video modes. Generated clips include audio. It supports video generation only. HeyGen’s avatar, voice, translation, and video editing APIs are not included.
Provider instance
Section titled “Provider instance”using Vercel.AI.HeyGen;using Vercel.AI.OpenAICompatible;
var heygen = HeyGenProvider.Create(new OpenAICompatibleOptions { ApiKey = Environment.GetEnvironmentVariable("HEYGEN_API_KEY") });These settings are optional:
- ApiKey string: The key sent in the
x-api-keyheader. Defaults to theHEYGEN_API_KEYenvironment variable. The key is read when a model is used, so a missing key fails then and not when the provider is created. - BaseUrl string: The API URL prefix. Defaults to
https://api.heygen.com. - Headers IDictionary<string, string>: Additional request headers.
AddHeyGen registers the provider with dependency injection. LanguageModel, EmbeddingModel, ImageModel, SpeechModel, and TranscriptionModel throw an AiSdkException.
Video models
Section titled “Video models”Create a model with heygen.VideoModel("heygen-video-1") or its alias heygen.Video("heygen-video-1"). HeyGenVideoModel implements IVideoCaller, so pass it as the Model of GenerateVideoRequest.
Text to video
Section titled “Text to video”var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)heygen.VideoModel("heygen-video-1"), Prompt = new VideoPrompt("A paper boat floats down a quiet stream. Static camera, soft morning light, the sound of flowing water. No music."), Duration = 5, AspectRatio = "16:9",});Image to video
Section titled “Image to video”Pass an image in the prompt to use it as the first frame. The video follows the image’s aspect ratio. A supplied AspectRatio is ignored with a warning.
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)heygen.VideoModel("heygen-video-1"), Prompt = new VideoPrompt("The subject moves slowly. Preserve the composition and natural lighting.", "https://example.com/first-frame.jpg"), Duration = 5,});A single first_frame in FrameImages is also supported. Last-frame conditioning and multiple first frames are rejected.
Reference to video
Section titled “Reference to video”Use InputReferences to condition a new scene on images or videos. Include a MediaType for URL references so the provider can select the reference kind.
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)heygen.VideoModel("heygen-video-1"), Prompt = new VideoPrompt("The product in <Picture 1> sits on a desk beside a sunlit window."), InputReferences = new[] { new VideoReference("https://example.com/product.jpg", "image/jpeg") }, Duration = 5, AspectRatio = "16:9",});References are grouped by media type, preserving order within each group. Address them in the prompt as <Picture 1>, <Video 1>, and <Audio 1>. References in the heygen provider options are appended after the standard references of the same kind.
Reference-to-video requires at least one image or video. Audio-only conditioning is not supported. HeyGen accepts up to 9 image references, 3 video references, and 3 audio references, with a combined maximum of 12. Only the first 5 seconds of each reference video are used.
Provider options
Section titled “Provider options”Set the options under the heygen key of ProviderOptions:
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)heygen.VideoModel("heygen-video-1"), Prompt = new VideoPrompt("The product in <Picture 1> rotates slowly on a table. No music."), AspectRatio = "16:9", ProviderOptions = OperationJson.Parse(""" { "heygen": { "resolution": "1080p", "promptEnhancement": "disabled", "referenceImages": [{ "type": "asset_id", "assetId": "YOUR_ASSET_ID" }] } } """),});- mode
text_to_video|image_to_video|reference_to_video: Inferred from the inputs when omitted. Explicit modes must match the supplied inputs. First-frame images cannot be combined with references. - resolution
480p|768p|1080p|2k: Output size class. Defaults to768p. Takes precedence over the standardResolution. - promptEnhancement
turbo|quality|disabled: HeyGen’s prompt enhancement setting. The API defaults toturbo. - image asset: First-frame input, including an existing asset ID. Cannot be combined with a standard image or
FrameImages. - referenceImages asset[]: Additional image references.
- referenceVideos asset[]: Additional video references.
- referenceAudio asset[]: Additional audio references.
An asset has one of these shapes:
{ "type": "url", "url": "https://example.com/input.jpg" }{ "type": "asset_id", "assetId": "YOUR_ASSET_ID" }{ "type": "base64", "mediaType": "image/jpeg", "data": "BASE64_DATA" }Asset IDs must already exist in the workspace associated with the API key. This provider does not upload or manage HeyGen assets. Standard file inputs (VideoModelFile.FromFile) are sent inline as base64. HeyGen permits up to 5 MB per inline image and 16 MB per inline video or audio input. URL inputs must use HTTPS and be directly accessible without redirects. HeyGen permits up to 16 MB per image URL and 32 MB per video or audio URL.
Resolution and aspect ratio
Section titled “Resolution and aspect ratio”Prefer providerOptions.heygen.resolution to select a size class. The standard Resolution accepts the exact frame sizes below and infers the corresponding aspect ratio when AspectRatio is omitted:
| Aspect ratio | 480p |
768p |
1080p |
2k |
|---|---|---|---|---|
21:9 |
960x416 |
1536x672 |
— | — |
16:9 |
832x480 |
1344x768 |
1890x1080 |
2688x1536 |
4:3 |
640x480 |
1024x768 |
— | — |
1:1 |
480x480 |
768x768 |
— | — |
3:4 |
480x640 |
768x1024 |
— | — |
9:16 |
480x832 |
768x1344 |
1080x1890 |
1536x2688 |
Text-to-video defaults to 16:9. Reference-to-video defaults to adaptive, inheriting the first reference image or video’s shape. At 1080p or 2k, text-to-video and reference-to-video require 16:9 or 9:16. Image-to-video always inherits the first frame’s shape, even when a standard frame size is supplied. Only its size class is used.
Other constraints
Section titled “Other constraints”- Prompts must contain between 1 and 32,000 characters.
- Duration must be an integer from 5 to 15 seconds. The default is 5.
- The seed must not be negative.
- One video is generated per provider call.
- Output is MP4 at 24 fps with generated audio. An unsupported
FpsorGenerateAudio = falseproduces a warning, as doesNabove 1. - An invalid option is rejected with an
InvalidArgumentExceptionbefore the request.
Asynchronous generation
Section titled “Asynchronous generation”HeyGenVideoModel implements DoStartAsync and DoStatusAsync. The SDK polls for GenerateVideo. Webhook completion is not implemented.
HeyGen jobs continue running after submission. Canceling the CancellationToken stops the local request and does not cancel the remote generation. The result is the signed video URL, and the bytes are not downloaded. Reading the status of the same operation again refreshes the URL.
Pass an Idempotency-Key through the call headers when retrying the same submission. HeyGen retains keys for 24 hours. Concurrent duplicates return an HTTP 409 error.
Provider metadata
Section titled “Provider metadata”providerMetadata.heygen.videos has one entry per completed video. Each entry contains:
videoId: The HeyGen video identifier.mode: The generation mode submitted to HeyGen.resolution: The resolved size class submitted to HeyGen, kept across status calls. This is separate from the actual output dimensions.duration,width,height,aspectRatio, andseed: Values reported by HeyGen on completion, when available.timings.inference: The inference timing reported by HeyGen, when available.
Submission and in-progress operation metadata exposes videoId, mode, and resolution directly under providerMetadata.heygen. Failed or canceled operations also expose failureCode when HeyGen reports it.
The HeyGen video endpoint does not document token counts or credit consumption in its generation response. The provider returns the reported duration and dimensions without estimating those quantities.
Kling AI
Section titled “Kling AI”The Kling AI provider (Vercel.AI.KlingAI) supports Kling AI video generation models: text-to-video, image-to-video, reference-to-video (multi-image), motion control, and multi-shot video generation. It supports video generation only.
Provider instance
Section titled “Provider instance”using Vercel.AI.KlingAI;
var klingai = KlingAIProvider.Create(new KlingAIOptions { ApiKey = Environment.GetEnvironmentVariable("KLINGAI_API_KEY") });These settings are optional:
- ApiKey string: The Kling AI API key, sent as a bearer token. Defaults to the
KLINGAI_API_KEYenvironment variable. Create one in the Kling AI developer console. - AccessKey string: The legacy access key. Defaults to the
KLINGAI_ACCESS_KEYenvironment variable. PreferApiKey. - SecretKey string: The legacy secret key. Defaults to the
KLINGAI_SECRET_KEYenvironment variable. PreferApiKey. - BaseUrl string: The API URL prefix. Defaults to
https://api-singapore.klingai.com. - Headers IDictionary<string, string>: Additional request headers.
AddKlingAI registers the provider with dependency injection. LanguageModel, EmbeddingModel, and ImageModel throw a NoSuchModelError.
Authentication
Section titled “Authentication”Kling AI supports two authentication schemes:
- API key (recommended): a single key sent as a bearer token. Set
KLINGAI_API_KEYor passApiKey. - Access key and secret key (legacy): a key pair the provider uses to sign a short-lived JWT (HS256, valid for 30 minutes) for each request. Set
KLINGAI_ACCESS_KEYandKLINGAI_SECRET_KEY, or passAccessKeyandSecretKey.
Credentials are resolved in this order, and explicit settings win over environment variables:
ApiKeyAccessKeyandSecretKey- The
KLINGAI_API_KEYenvironment variable - The
KLINGAI_ACCESS_KEYandKLINGAI_SECRET_KEYenvironment variables
The credentials are read when a request is sent, so a missing key fails then and not when the provider is created. KlingAIAuth.ResolveAuthToken and KlingAIAuth.GenerateAuthToken expose the same logic.
Video models
Section titled “Video models”Create a model with klingai.VideoModel("kling-v3.0-t2v") or its alias klingai.Video("kling-v3.0-t2v"). KlingAIVideoModel implements IVideoCaller, so pass it as the Model of GenerateVideoRequest. The suffix of the model id selects the mode: -t2v, -i2v, or -motion-control. Any other model id throws a NoSuchModelError when the video starts. Not every option works with every model version and mode. See the Kling AI capability map.
The model creates a task, then polls it until it finishes. The result is the video URL, and the bytes are not downloaded.
Text to video
Section titled “Text to video”var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)klingai.VideoModel("kling-v3.0-t2v"), Prompt = new VideoPrompt("A chicken flying into the sunset in the style of 90s anime."), AspectRatio = "16:9", Duration = 5, ProviderOptions = OperationJson.Parse("""{ "klingai": { "mode": "std" } }"""),});Image to video
Section titled “Image to video”Pass an image in the prompt to use it as the start frame. Set imageTail for start and end frame control, or pass a last_frame in FrameImages.
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)klingai.VideoModel("kling-v3.0-i2v"), Prompt = new VideoPrompt("The cat slowly turns its head and blinks", "https://example.com/start-frame.png"), Duration = 5, ProviderOptions = OperationJson.Parse("""{ "klingai": { "mode": "pro", "imageTail": "https://example.com/end-frame.png" } }"""),});Multi-shot video
Section titled “Multi-shot video”Kling v3.0 can split a video into up to 6 storyboard shots, each with its own prompt and duration:
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)klingai.VideoModel("kling-v3.0-t2v"), Prompt = new VideoPrompt(""), AspectRatio = "16:9", Duration = 10, ProviderOptions = OperationJson.Parse(""" { "klingai": { "mode": "pro", "multiShot": true, "shotType": "customize", "multiPrompt": [ { "index": 1, "prompt": "A sunrise over a calm ocean, warm golden light.", "duration": "4" }, { "index": 2, "prompt": "A flock of seagulls take flight from the beach.", "duration": "3" }, { "index": 3, "prompt": "Waves crash against rocky cliffs at sunset.", "duration": "3" } ], "sound": "on" } } """),});Multi-shot also works with image-to-video.
Reference to video
Section titled “Reference to video”Pass images in InputReferences on an image-to-video model to combine them. The request goes to the multi-image endpoint. Video references are ignored with a warning, and a model that is not image-to-video ignores all references with a warning.
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)klingai.VideoModel("kling-v1.6-i2v"), Prompt = new VideoPrompt("The two characters meet and walk together through a sunny park"), InputReferences = new[] { new VideoReference("https://example.com/character-1.png", "image/png"), new VideoReference("https://example.com/character-2.png", "image/png"), }, AspectRatio = "16:9", Duration = 5,});Motion control
Section titled “Motion control”Transfer the motion of a reference video to a character image. videoUrl, characterOrientation, and mode are required, and a call without them throws an AiSdkException.
var result = await GenerateVideo.GenerateVideoAsync(new GenerateVideoRequest{ Model = (IVideoCaller)klingai.VideoModel("kling-v3.0-motion-control"), Prompt = new VideoPrompt("The character performs a smooth dance move", "https://example.com/character.png"), ProviderOptions = OperationJson.Parse(""" { "klingai": { "videoUrl": "https://example.com/reference-motion.mp4", "characterOrientation": "image", "mode": "std", "elementList": [{ "element_id": 829836802793406551 }] } } """),});Caller-managed callbacks
Section titled “Caller-managed callbacks”Use StartVideo.StartVideoAsync with WebhookUrl to register your own callback receiver. The URL is sent as callback_url and wins over providerOptions.klingai.callback_url. Without WebhookUrl, a raw callback_url option is passed through unchanged.
Kling AI sends progress notifications (submitted and processing) as well as terminal ones (succeed and failed). Your receiver must filter the progress notifications and match each callback to its task. Call GetVideoStatus.GetVideoStatusAsync with the operation from StartVideoAsync to read the result after a terminal notification. The model does not handle webhooks itself (CanWebhook is false), so GenerateVideo with a webhook polls instead.
Provider options
Section titled “Provider options”Set the options under the klingai key of ProviderOptions. An option of the wrong type or with an unknown enum value throws an InvalidArgumentException. Keys that are not listed are sent to the API as is.
Common options:
- mode “std” | “pro”:
stdis cost-effective.prohas higher quality and takes longer. - pollIntervalMs number and pollTimeoutMs number: Accepted and not sent to the API. Pass
PollIntervalMsandPollTimeoutMstoGenerateVideoRequestinstead. - watermarkEnabled boolean: Also generate a watermarked result.
Text-to-video and image-to-video options:
- negativePrompt string: What to avoid (max 2500 characters).
- sound “on” | “off”: Generate audio. V2.6 and later, and needs
mode: "pro". AGenerateAudiovalue on the request wins. - cfgScale number: How closely to follow the prompt, in [0, 1]. V2.x models do not support it.
- cameraControl object: A
typepreset (simple,down_back,forward_up,right_turn_forward,left_turn_forward) and an optionalconfigwithhorizontal,vertical,pan,tilt,roll, andzoom. - multiShot boolean, shotType “customize” | “intelligence”, multiPrompt array of
{ index, prompt, duration }: Multi-shot generation (v3.0+).shotTypeis required withmultiShot.multiPromptis required forcustomize. - voiceList array of
{ voice_id }: Voice references (v3.0+, up to 2), used as<<<voice_1>>>in the prompt. It needssound: "on"and cannot be combined withelementListon image-to-video.
Image-to-video only:
- imageTail string: The end frame, as a URL or raw base64. Most models need
mode: "pro". - staticMask string and dynamicMasks array of
{ mask, trajectories }: Motion brush.
Image-to-video and motion control:
- elementList array of
{ element_id }: Reference elements (v3.0+). Up to 3 for image-to-video and 1 for motion control.
Motion control only:
- videoUrl string (required): The reference motion video (.mp4 or .mov, up to 100 MB, 3 to 30 seconds).
- characterOrientation “image” | “video” (required):
imagefollows the image (reference video up to 10 s).videofollows the video (up to 30 s). - keepOriginalSound “yes” | “no”: Keep the reference video’s sound. Defaults to
yeson the API.
Warnings
Section titled “Warnings”The model reports Unsupported warnings for Resolution, Seed, Fps, and N greater than 1 on every mode. Other warnings depend on the mode: AspectRatio on image-to-video and motion control, Duration on motion control, an image on text-to-video, and video files used as images or references.
Status polls send the credentials to the configured Kling AI origin only. A redirect to another origin is checked with the download URL rules before it is followed, and a private address throws a DownloadError.
Provider metadata
Section titled “Provider metadata”providerMetadata.klingai has the taskId and a videos array. Each entry has the id and url, and the watermarkUrl and duration when Kling AI reports them.
Video model ids
Section titled “Video model ids”Text to video: kling-v3.0-t2v, kling-v2.6-t2v, kling-v2.5-turbo-t2v, kling-v2.1-master-t2v, kling-v2-master-t2v, kling-v1.6-t2v, kling-v1-t2v.
Image to video: kling-v3.0-i2v, kling-v2.6-i2v, kling-v2.5-turbo-i2v, kling-v2.1-master-i2v, kling-v2.1-i2v, kling-v2-master-i2v, kling-v1.6-i2v, kling-v1.5-i2v, kling-v1-i2v.
Motion control: kling-v3.0-motion-control, kling-v2.6-motion-control.
Any other model id with one of the three suffixes is also accepted.
Black Forest Labs
Section titled “Black Forest Labs”The Black Forest Labs provider (Vercel.AI.BlackForestLabs) generates images with ImageModel(id) or Image(id) (provider id black-forest-labs.image) and videos with VideoModel(id) or Video(id) (provider id black-forest-labs.video, see Black Forest Labs video models). It reads BFL_API_KEY and sends it as x-key. LanguageModel and EmbeddingModel throw NoSuchModelError.
The model submits the request, polls polling_url (with ?id= added when missing) until the task is Ready, then downloads the sample. BlackForestLabsProvider.Create(options, handler, pollIntervalMillis, pollTimeoutMillis) sets the poll interval (default 500 ms for images, 2 s for video) and the overall poll timeout (default 60 s for images, 10 minutes for video) for every image and video model. The pollIntervalMillis and pollTimeoutMillis provider options override them for one call. A timeout throws AiSdkException (“Black Forest Labs generation timed out.”). A task that ends as Error or Failed, or is Request Moderated, Content Moderated, or Task not found, throws AiSdkException. An HTTP error throws ApiException with the detail (or message) of the body; an HTTP 503 whose body carries a terminal status or state is not retryable.
Credentials go to the polling URL only when it is on the provider origin or an https host under bfl.ai (for example api.us1.bfl.ai). The download sends no credentials, except that a custom base URL outside bfl.ai receives the provider and call headers when the sample is on the same origin. Every other hop carries only the SDK User-Agent header. Other hosts are checked first (private addresses and non-http(s) schemes throw DownloadException).
BlackForestLabsImageModel.SupportsFileInputs and SupportsMaskInputs report what the model id accepts (null when unknown). The model id picks the API: ids such as flux-pro-1.1, flux-kontext-pro, flux-pro-1.0-fill, flux-1.*, and flux-2-* use the legacy API. flux-3-image uses the FLUX 3 API. Any other id (for example flux-3-image-klein) uses the latest API.
- Legacy:
AspectRatiowins overSize. ASizeadds anunsupportedsizewarning, and its width and height are sent unless thewidthandheightprovider options are set. Files are sent asinput_image,input_image_2, and so on (imageforflux-pro-1.0-fill), and the mask asmask. - FLUX 3 and latest: files are sent in
images(URLs as is, bytes as base64), at most 10 (more throwsAiSdkException). A mask throwsUnsupportedFunctionalityException. The aspect ratio comes fromAspectRatioor fromSize;7:3and3:7are sent as21:9and9:21, and a ratio the endpoint does not take is left out with anunsupportedaspectRatiowarning. FLUX 3 adds anunsupportedseedwarning for aSeed. The latest API sendsSeed, and itsSizebecomeswidthandheight(thewidthandheightprovider options win); with dimensions set, no aspect ratio or resolution is sent. - Provider options are read from
blackForestLabsand validated before the request (InvalidArgumentException). An option the model does not support is dropped with anunsupportedblackForestLabs.<name>warning. Ranges:safetyTolerance0 to 6 (legacy) or 0 to 7,widthandheight256 to 1920 (legacy) or 64 to 6256 together with at most 16,822,272 pixels (latest),steps1 to 16 (latest), andresolutionone of768sq,1k,1.5k,2k,4k.
GenerateImage returns providerMetadata.blackForestLabs.images[0] with seed, start_time, end_time, duration, cost, inputMegapixels, and outputMegapixels when the API sent them, and the response Timestamp, ModelId, and Headers of the download.
Black Forest Labs video models
Section titled “Black Forest Labs video models”BlackForestLabsProvider.VideoModel(id) (also Video(id)) returns a BlackForestLabsVideoModel for FLUX 3 video (flux-3-video). It generates one video per call (MaxVideosPerCall is 1) and supports DoGenerateAsync, DoStartAsync, and DoStatusAsync. The video is returned as a signed MP4 URL (VideoOutput of type url, media type video/mp4) and is not downloaded. The URL expires, so keep the bytes you need. It is also in providerMetadata.blackForestLabs.videos[0].videoUrl, next to id, seed, start_time, end_time, duration, draftCache, cost, inputMegapixels, and outputMegapixels when the API sent them. cost is the settled cost of the poll response when it has one, else the estimate of the submit response. The API has no webhook input, so CanWebhook is false.
DoStartAsync submits the job and returns an operation: a JSON object with requestId, pollingUrl, and the cost, inputMegapixels, and outputMegapixels of the submit response. DoStatusAsync reads it once and returns pending, completed, or error (a terminal status of Error, Failed, Request Moderated, Content Moderated, or Task not found, with the details of the poll in the message). The polling URL gets ?id= when it has none, and the same trust rules as the image model apply to the credentials. DoGenerateAsync starts a job and polls it every PollIntervalMillis (default 2 s). It throws AiSdkException when the job fails (“Black Forest Labs video generation failed with status …”) or when PollTimeoutMillis (default 10 minutes) passes (“Black Forest Labs video generation timed out after …ms”).
- Modes:
modecomes from the inputs. A prompt alone is text to video (t2v). AnImageor afirst_frameframe image is image to video (i2v); alast_framecloses the clip, and alast_framewithout afirst_frameis dropped with a warning. A video inInputReferencescontinues from that video (v2v; only the first is used). Keyframes and a video to continue from exclude each other, and keyframes win. FLUX 3 has no reference-image input, so an image inInputReferencesis ignored with a warning. A reference with no media type is treated as the video to continue from, with acompatibilitywarning. Durationis a whole number of seconds from 5 to 20. A fraction is rounded and an out-of-range value is clamped, each with a warning. Without aDurationthe API picks one.Resolutionishdorfhd, or{width}x{height}, which maps onto a tier by the shorter side (720 or less ishd, elsefhd; acompatibilitywarning when the side is not 720 or 1080). Theresolutionprovider option wins.AspectRatiotakes21:9,2:1,16:9,4:3,1:1,3:4, or9:16; theaspectRatioprovider option also takesauto, and wins. Other values are dropped with a warning.GenerateAudiois sent asgenerate_audioonly when set.FpsandSeedare not supported and add warnings, and so does anNabove 1.- Provider options are read from
blackForestLabsand validated before the request (InvalidArgumentException):resolution,aspectRatio,keyframes,safetyTolerance(0 to 4),draft,draftCache, andversion(onlylatest).keyframesholds 1 to 10 image URLs or base64 strings, or[seconds, image]pairs in chronological order (0 to 20 seconds). It wins overImageandFrameImages, with a warning. Three or more untimed keyframes need aDuration(InvalidArgumentExceptionotherwise). - Draft mode:
draftrenders a fast preview and reportsdraftCachein the provider metadata. Passing that URL or its base64 bundle asdraftCachereplays it at full quality. The bundle pins everything else, so the request sends onlymode,draft_cache, andsafety_tolerance, and every other input is reported as anunsupportedwarning.
fal image models
Section titled “fal image models”FalImageModel.SupportsFileInputs and SupportsMaskInputs report what the model id accepts. SupportsFileInputs is true for the known image-to-image and edit models (for example fal-ai/flux-2/edit and fal-ai/flux-pro/kontext), false for known text-to-image models (for example fal-ai/recraft/v3/text-to-image), and null for any other id. SupportsMaskInputs is true for fal-ai/flux-general/inpainting and fal-ai/flux-lora/inpainting, false for the other known ids, and null when SupportsFileInputs is null.
MiniMax
Section titled “MiniMax”MiniMaxProvider (Vercel.AI.MiniMax) speaks MiniMax’s Anthropic-compatible protocol, so the default base URL is https://api.minimax.io/anthropic/v1, not the OpenAI-compatible https://api.minimax.io/v1. Model calls post to {base}/messages with the provider id minimax.messages, which makes minimax the provider-options namespace. MiniMaxOptions sets ApiKey (default: the MINIMAX_API_KEY environment variable, sent in the x-api-key header), BaseUrl, and Headers. LanguageModel(id) and Chat(id) return a MiniMaxChatLanguageModel. Embedding and image models throw AiSdkException. MiniMax video models are not ported (#650).
using Vercel.AI.MiniMax;
var minimax = MiniMaxProvider.Create(new MiniMaxOptions { ApiKey = Environment.GetEnvironmentVariable("MINIMAX_API_KEY") });var model = minimax.LanguageModel("minimax-m3.1-flash");A model id from M3.1 on gets the extra capabilities below: minimax-m3.1-flash, MiniMax-M3.1-Flash-Preview, minimax-m3.2, minimax-m4, and so on. The version is compared by its numbers, so m3.10 is newer than m3.1. Older and unrecognized ids (minimax-m3, minimax-m2.7, custom-model) keep the plain Anthropic adapter behavior.
Sampling
Section titled “Sampling”For M3.1 and later ids, Temperature accepts 0 to 2 and TopP accepts 0 to 1. Both are kept when adaptive thinking is on or when you set Reasoning. A temperature outside the range is clamped with an unsupported temperature warning that names minimax. Omit them to use MiniMax’s defaults. Older ids clamp the temperature to 1 and drop Temperature and TopP when thinking is on.
Prompt caching
Section titled “Prompt caching”For M3.1 and later ids, mark stable text with minimax.cacheControl. It becomes cache_control on the matching content block. Set it on a system message, a text part, or a user message (which marks its final content block).
var part = new TextContentPart("Long reference material reused across requests..."){ ProviderOptions = new Dictionary<string, JsonElement> { ["minimax"] = JsonDocument.Parse("{\"cacheControl\":{\"type\":\"ephemeral\"}}").RootElement.Clone(), },};Keep the stable prefix unchanged and put the changing question after it. A marker does not guarantee a hit; check the cache-read token usage. anthropic.cacheControl still works. When both namespaces set it on a block, minimax wins. At most four blocks are cached; the rest add a cacheControl breakpoint limit warning. Older ids read only the anthropic namespace.
Image and video input
Section titled “Image and video input”For M3.1 and later ids, user messages accept images and videos, with GenerateText and StreamText. Pass bytes or a public http(s) URL; the URL is sent to MiniMax as is. Set the media options on each file part under minimax:
- fps number (video): the frame sampling rate, from
0.2to5. Omit it for the server default. A value outside the range, or a non-number, throwsInvalidArgumentExceptionbefore the request. - detail string (image):
low,default, orhigh. Higher detail can use more tokens. Any other value throwsInvalidArgumentException.
For a video you already uploaded to MiniMax, use a provider reference. FileContentPart.FromProviderReference("video/mp4", new Dictionary<string, string> { ["minimax"] = "your-uploaded-file-id" }) is sent as mm_file://your-uploaded-file-id; an id that already starts with mm_file:// is kept. A reference without a minimax entry throws NoSuchProviderReferenceError, and an image reference throws UnsupportedFunctionalityException. The provider does not upload the file. MiniMaxChatLanguageModel.SupportedUrls lists image/* and video/* for M3.1 and later ids, and nothing for older ids, which reject video with UnsupportedFunctionalityException.
MiniMax documents JPEG, PNG, GIF, and WebP images (up to 10 MB), and MP4, AVI, MOV, and MKV videos (up to 50 MB inline or by URL, 512 MB uploaded). The request body limit is 64 MB, including base64 overhead. Audio input is not supported.
Reasoning
Section titled “Reasoning”Pass thinking and effort under the minimax key of ProviderOptions:
- thinking object:
{ "type": "adaptive" }lets the model decide when to reason.{ "type": "disabled" }answers directly. Thinking control works onminimax-m3. The M2.x models always think, andMiniMax-M3.1-Flashalways thinks and returns an error fordisabled. - effort string:
low,medium,high,xhigh, ormax, for M3.1 and later ids. It setsoutput_config.effort. When omitted, the server picks its default (maxfor M3.1 Flash).
The output token budget covers the reasoning and the answer, so higher effort may need a larger MaxOutputTokens.
For M3.1 and later ids, LanguageModelCallOptions.Reasoning maps to the effort instead of an Anthropic thinking budget: low, medium, high, xhigh, and max map to the same effort, minimal maps to low with a compatibility warning, and none also maps to low with a compatibility reasoning warning, because Flash always reasons. provider-default or no Reasoning leaves the server default. An explicit minimax.effort or anthropic.effort wins over Reasoning. Older ids keep the Anthropic mapping.
MiniMaxChatLanguageModel.Serialize() returns the model id and config for a workflow boundary (provider id, base URL, and headers). It leaves out the API key and any header whose name contains authorization, key, token, secret, or cookie. MiniMaxChatLanguageModel.Deserialize(serialized, apiKey) restores the model; without apiKey it reads MINIMAX_API_KEY.
xAI Responses
Section titled “xAI Responses”XaiProvider.LanguageModel(modelId) creates a language model for the xAI Responses API (POST {base}/responses), as upstream does. XaiProvider.ResponsesModel(modelId) creates the same model with its concrete type. The provider id is xai.responses. Upstream has no xAI chat model, so XaiProvider.CreateChatModel(modelId), the Chat Completions model, is obsolete and will be removed.
using Vercel.AI.OpenAICompatible;using Vercel.AI.Xai;
var xai = XaiProvider.Create(new OpenAICompatibleOptions { ApiKey = Environment.GetEnvironmentVariable("XAI_API_KEY") });var model = xai.LanguageModel("grok-4.3");The model supports text, image, and file input, streaming, function tools, xAI provider-defined tools, JsonSchema structured output (with JsonSchemaName and JsonSchemaDescription), JsonResponse, reasoning, and URL citations as sources. Reasoning returns as XaiReasoning content, which carries the xai.itemId and xai.reasoningEncryptedContent metadata. Streams send the same metadata on reasoning-start and reasoning-end, and the itemId on each reasoning-delta. To continue a conversation, pass that metadata back on the assistant message (ReasoningProviderOptions). This round trip works only for messages you build by hand. GenerateText and StreamText do not yet carry the metadata into the next step (#344). A request sends topK as top_k and warns about FrequencyPenalty, PresencePenalty, and StopSequences. A file part with a URL becomes input_image for images and input_file for other types. Inline bytes are only accepted for images. For other files, upload them with XaiProvider.Files() and pass a provider reference.
Provider options
Section titled “Provider options”Pass these under the xai key of ProviderOptions.
- reasoningEffort string:
none,low,medium,high, orxhigh(xhighis supported bygrok-4.6). Overrides the portableReasoningoption. - reasoningSummary string: Deprecated. xAI ignores it, so the model accepts it and does not send it.
- logprobs bool and topLogprobs int (0 to 8):
topLogprobsalso turns onlogprobs. - minP number (0 to 1), maxTurns int, parallelToolCalls bool, promptCacheKey string, safetyIdentifier string, and user string.
- serviceTier string:
defaultorpriority. The service tier xAI used comes back inproviderMetadata.xai.serviceTier. - store bool: With
false, the request also includesreasoning.encrypted_contentso reasoning can be sent back. - previousResponseId string.
- include string[]:
file_search_call.results,web_search_call.action.sources,code_interpreter_call.outputs,reasoning.encrypted_content, orno_inline_citations.
An image file part accepts the provider option xai.imageDetail (low, high, or auto) as FileContentPart.ProviderOptions.
providerMetadata.xai holds costInUsdTicks, serviceTier, promptCacheKey, and safetyIdentifier when the response reports them.
Provider-defined tools
Section titled “Provider-defined tools”The Responses API has server-side tools that the model runs on xAI’s servers during its reasoning. Pass them in LanguageModelCallOptions.Tools with LanguageModelTool.ProviderDefined(id, name, args). args is a JSON object with the parameters below. Invalid arguments throw a TypeValidationException.
var result = await model.DoGenerateAsync( new LanguageModelCallOptions { Prompt = new ModelMessage[] { new UserModelMessage("What are the latest developments in AI?") }, Tools = new[] { LanguageModelTool.ProviderDefined( "xai.web_search", "web_search", JsonDocument.Parse("""{"allowedDomains":["arxiv.org","openai.com"],"enableImageUnderstanding":true}""").RootElement), }, }, CancellationToken.None);XaiTools builds the Tool for GenerateText, StreamText, and Agent, like xai.tools upstream: XaiTools.WebSearch(new XaiWebSearchOptions { AllowedDomains = new[] { "arxiv.org" } }), XSearch, CodeExecution, ViewImage, ViewXVideo, ImageGeneration, FileSearch, and McpServer. Each takes an optional tool name as its last argument. It defaults to the wire name, such as web_search.
xai.web_search: real-time web search and page browsing. allowedDomains string[] (max 5) and excludedDomains string[] (max 5) cannot be used together. enableImageSearch bool lets the model run an image search when images help the answer. enableImageUnderstanding bool lets the model view images it finds, and uses more tokens.xai.x_search: search X posts. allowedXHandles string[] (max 20) and excludedXHandles string[] (max 20) cannot be used together. fromDate and toDate string (YYYY-MM-DD). enableImageUnderstanding and enableVideoUnderstanding bool.xai.code_execution: runs Python code. No parameters.xai.view_image: views and analyzes images. No parameters.xai.view_x_video: views and analyzes videos from X posts. No parameters.xai.image_generation: creates and edits images with Grok Imagine. action string:auto(the default),generate, oredit. The tool takes no size or format parameters. Ask for an aspect ratio in the prompt.xai.mcp: connects to a remote MCP server. serverUrl string (required), serverLabel string, serverDescription string, allowedTools string[] (all tools when not set), headers object of strings, and authorization string (for exampleBearer token123).xai.file_search: searches xAI vector stores (collections). vectorStoreIds string[] (required) and maxNumResults number. Addfile_search_call.resultsto theincludeprovider option to get the matching file content and scores. File search needs a grok-4 family model.
Another provider-defined tool id is left out with an unsupported warning (provider-defined tool <name>). xAI can force only function tools, so a ToolChoice.Tool(name) that selects a provider-defined tool is not sent and adds an unsupported warning (toolChoice for server-side tool "<name>").
The result has a GeneratedToolCall with ProviderExecuted set for each server-side call, named after the tool you declared. xAI reports sub-tools such as x_keyword_search or browse_page, and they map back to that name. Web search, file search, and image generation calls also return a GeneratedToolResult:
- web search:
{ action, sources }.actionis{ type: "search", query, queries },{ type: "openPage", url }, or{ type: "findInPage", url, pattern }.sourceslists{ type: "url", url }when xAI sends them (request them with theweb_search_call.action.sourcesinclude). - file search:
{ queries, results }, where each result hasfileId,filename,score, andtext.resultsis null when xAI sends none. - image generation:
{ result, prompt }, whereresultis the base64 image. A call without an image gives an error result (IsError).
Streams send tool-input-start, tool-input-delta, tool-input-end, and tool-call once per server-side call, and a tool-result part when the call is done (ToolResultStreamPart.IsError marks a failed image generation). A prompt replays only the client-side tool calls: assistant tool calls with ProviderExecuted set and assistant tool results are not sent back.
Not included
Section titled “Not included”Tool results whose output is content with images (input_image in function_call_output) are not sent, because ToolModelMessage has no multipart content (#327).
QuiverAI reference images
Section titled “QuiverAI reference images”The SVG edit provider option quiverai.referenceImages takes up to four { "url" } or { "base64" } objects. URL references must use HTTP or HTTPS. Base64 references must decode to PNG, JPEG, WebP, GIF, or SVG data. Use QuiverAIImageReference.Prepare to convert binary, data URL, or URL inputs into this shape. It returns a QuiverAIImageReference with exactly one of Url and Base64 set, and it serializes to the JSON shape above.
using System.Text.Json;using System.Text.Json.Nodes;using Vercel.AI.QuiverAI;
var options = new JsonObject{ ["quiverai"] = new JsonObject { ["operation"] = "edit", ["referenceImages"] = new JsonArray( JsonSerializer.SerializeToNode(QuiverAIImageReference.Prepare(File.ReadAllBytes("blue-reference.png"))), new JsonObject { ["url"] = "https://example.com/second-reference.png" }), },};Uploaded File References
Section titled “Uploaded File References”An OpenAI-compatible chat model accepts a file that you already uploaded to the provider. Build the part with a provider reference: a map from provider name to file id.
var part = FileContentPart.FromProviderReference( "application/pdf", new Dictionary<string, string> { ["my-provider"] = "file-123" }, "document.pdf");- The reference key must match
OpenAICompatibleOptions.ProviderName, including hyphens. A camelCase variant such asmyProvideris never used as the id. A reference without that key throwsNoSuchProviderReferenceError. - The request body carries
{ "type": "file", "file": { "file_id": "file-123" } }. This works forapplication/pdfandimage/png. Properties from the part’sopenaiCompatibleprovider options are copied onto that part. DoStreamAsyncsends the same part.- Inline PDF bytes are unchanged: they are sent as a base64
file_dataURL.
The provider does not upload the file. Which media types are accepted depends on the destination model.
Anthropic and Google read the same reference. Anthropic sends the anthropic entry as a Files API file_id. Google sends the google entry as the fileUri. AnthropicSkills.UploadSkillAsync returns its skill id as ProviderReference["anthropic"].
Anthropic container forwarding
Section titled “Anthropic container forwarding”Code execution runs in a container. To keep the same container across steps, return AnthropicContainer.ForwardFromLastStep from PrepareStep. It searches the steps from the end for providerMetadata.anthropic.container.id and returns an update whose ProviderOptions set anthropic.container.id. It returns null when no step has a container id.
var result = await client.GenerateTextAsync(new GenerateTextOptions{ Model = anthropic.LanguageModel("claude-sonnet-4-5"), StopWhen = StopWhen.IsStepCount(10), Tools = tools, PrepareStep = context => AnthropicContainer.ForwardFromLastStep(context.Steps),});PrepareStepUpdate.ProviderOptions is deep-merged onto the call’s provider options for that step.
PrepareStepContext also exposes Messages, the prompt plus the responses of the earlier steps, and Model, the model the step is about to use. Messages does not hold the instructions. Instructions is what the step is about to send, InitialInstructions and InitialMessages are what the call started with, and ResponseMessages are the messages the earlier steps added. Return PrepareStepUpdate.Instructions to replace the instructions, or PrepareStepUpdate.Messages to replace the messages. Each override carries forward to later steps, and replacing one keeps the other. Like upstream, the results of all tool calls in one step are a single ToolModelMessage; read them from ToolModelMessage.Results (a single-result message lists itself).
Decision state
Section titled “Decision state”DecideRequest.State is a DecisionState: a string, a JSON object, or a list of parts. A string converts to it implicitly. DecisionState.FromJson(element) takes a JSON object, and a raw JSON array is not state. Wrap it in an object or in a DecisionJsonPart. A list of parts is an array of DecisionTextPart, DecisionJsonPart, and DecisionFilePart (media type, FilePartInput data, optional FileName and ProviderOptions). Decide.DecideAsync normalizes the state with DecisionState.PrepareDecisionStateAsync before the model call. A string becomes one text part, an object becomes one json part, and a file part gets a full media type. A URL file is downloaded. A failed download throws DownloadError before any model call. DecisionModelCall.State and DecisionModelEvent.State hold the normalized parts as DecisionModelTextPart, DecisionModelJsonPart, and DecisionModelFilePart. DecideEvent.State keeps the public value. OpenAIDecisionModel sends text and json parts as input_text and inline PNG, JPEG, WebP, or GIF files as input_image. It throws UnsupportedFunctionalityException for other files. GatewayDecisionModel posts { stateParts, questions, providerOptions } to /decision-model. stateParts holds the ordered text, json, and file parts. File bytes go out as base64, and a file with URL, reference, or text data throws UnsupportedFunctionalityException before any request. providerOptions is sent whenever the call has it, even when it is empty.
Decision questions
Section titled “Decision questions”new DecisionQuestion(type, instructions, criteria) takes DecisionInput? instructions: a string (implicit), or a JSON object or array through DecisionInput.FromJson(element) or DecisionInput.Parse(json). Any other JSON value throws InvalidArgumentException. For choice, score, and boolean, the instructions may be null when the provider supports it and the criteria define the judgment. A question without instructions leaves the field out of the request. Set HasInstructions = true on a question with null instructions to send an explicit null, as upstream does for instructions: null. PerplexityDecisionModel and GatewayDecisionModel honor it. A boolean question without instructions must describe at least one of True or False with a non-null value, otherwise Decide.DecideAsync throws InvalidArgumentException. A box always requires instructions. Criteria are typed by question type. A choice takes DecisionChoiceCriteria (a nonempty map of option names to DecisionInput?), a score takes DecisionScoreCriteria (at least one level; a provider can require more), and a boolean takes an optional DecisionBooleanCriteria with True and False. A side that is never set is left out of the request. A side set to null is sent as null. A box takes no criteria. Descriptions may be null in the shared types, but providers can impose stricter limits. OpenAIDecisionModel and GatewayDecisionModel send a question without instructions as is, and DecisionLanguageModel leaves instructions out of the rubric.
Decision language model
Section titled “Decision language model”new DecisionLanguageModel(languageModel, provider?) wraps a V4 ILanguageModel so Decide.DecideAsync can use it for choice, score, and boolean questions. It does not support box questions. The provider id defaults to the wrapped model’s provider plus .decision. It sends one structured-output request with reasoning set to none. Question ids and choice labels are replaced by internal keys (q0, c0, …) in the schema and mapped back in the answers. State parts keep their order. Text and image file parts pass through, and a json part is sent as compact JSON text. Any non-image file part throws UnsupportedFunctionalityException before the model is called. A wrong finish reason, invalid JSON, a missing or extra answer, an unknown choice, a score outside its rubric, or a probability outside 0 to 1 throws InvalidResponseDataException. A choice needs at least one option and a score at least two levels, otherwise InvalidArgumentException is thrown. A file part’s providerOptions are forwarded as the FileContentPart.ProviderOptions of the image. DecisionLanguageModel.Serialize(model) returns a DecisionLanguageModelOptions with the wrapped Model and the Provider id for a workflow boundary, and DecisionLanguageModel.Deserialize(options) restores the model. The wrapped model is not copied, so a workflow runtime must serialize it with its own hooks.
Gateway decision fallbacks
Section titled “Gateway decision fallbacks”providerOptions.gateway.models for a decision request may start with one conditional entry, { "model": "...", "when": { ... } }, followed by plain model ids. when is confidenceBelow, probabilityBetween: [min, max], or an any, all, or atLeast group of conditions, nested at most 5 levels with 1 to 20 conditions per list. GatewayDecisionModel rejects an invalid shape with an InvalidArgumentException before any request. Other gateway options pass through unchanged.
Batch result downloads
Section titled “Batch result downloads”GatewayOptions, OpenAIOptions, AnthropicOptions, and GoogleOptions each take an optional BatchResultDownloads setting. It configures the download of JSON Lines batch results. provider.ExperimentalBatch() returns the batch API whose DoGetResultsAsync reads them.
-
BatchResultDownloads BatchResultDownloads
Settings for downloading JSON Lines batch results. For Google, the setting applies to file-based results.
-
MaxLineBytes int?
Maximum UTF-8 bytes per row, excluding the LF delimiter. Defaults to 64 MiB (67,108,864 bytes). Must be positive. Oversized rows throw a
DownloadErrorand cancel the download.
-
var anthropic = AnthropicProvider.Create(new AnthropicOptions{ BatchResultDownloads = new BatchResultDownloads { MaxLineBytes = 16 * 1024 * 1024 },});JsonStreams.ReadJsonLinesAsync takes the same limit as maxLineBytes, plus the url reported by the DownloadError.
OpenAI prompt caching
Section titled “OpenAI prompt caching”For GPT-5.6 and later models, providerOptions.openai.promptCacheOptions configures prompt caching on the Responses API. mode is implicit or explicit. ttl sets the minimum cache lifetime: 30m (the default) or 12h. 12-hour writes are billed at a higher rate than 30-minute writes. Any other ttl throws InvalidArgumentException. Set prewarm to true to write the prompt to the cache without generating output; the result then has no content. Chat Completions accepts 30m only.
var options = new LanguageModelCallOptions{ Prompt = prompt, ProviderOptions = new Dictionary<string, JsonElement> { ["openai"] = JsonDocument.Parse("{\"promptCacheOptions\":{\"mode\":\"implicit\",\"ttl\":\"12h\",\"prewarm\":true}}").RootElement.Clone(), },};In explicit mode, only content marked with promptCacheBreakpoint is cached. Set {"mode":"explicit","ttl":"12h"} under openai in the ProviderOptions of a system message, a TextContentPart, or a FileContentPart; the Responses request carries it as prompt_cache_breakpoint. Usage.CacheWriteTokens reports the tokens written, and Usage.Raw holds input_tokens_details.cache_write_tokens_details with ttl_30m_tokens and ttl_12h_tokens.
OpenAI batch
Section titled “OpenAI batch”Batch support is experimental and the API may change in patch releases.
The OpenAI provider supports asynchronous text generation through the Batch API. Pass the provider to Batch.StartBatchAsync, Batch.GetBatchStatusAsync, Batch.GetBatchResults, Batch.CancelBatchAsync, and Batch.ListBatchesAsync for the complete workflow.
Batch.GetBatchResults returns an IAsyncEnumerable<BatchItemResult> of terminal results. Read it with await foreach. Each result is downloaded and converted only when you ask for it. To read every result into a list, call Batch.GetAllBatchResultsAsync instead.
DoStartAsyncuploads the requests as abatch.jsonlinput file and creates a/v1/responsesbatch with a24hcompletion window. Each request body is the Responses request body for its model. OpenAI requires every request in a batch to be a text request for the same model. A different type throwsUnsupportedFunctionalityExceptionand a different model throwsInvalidArgumentException, both before any upload. Only function tools are supported in batch requests for now.providerOptions.openai.inputFileExpiresAftersets the input file TTL in seconds, from 3600 (1 hour) to 2592000 (30 days). It defaults to 48 hours. The start result’sProviderMetadata.openaiholdsinputFileIdand, when the upload reports one,inputFileExpiresAt.- The OpenAI Batch API does not support per-batch webhooks. When you set
WebhookUrl, the start result has anunsupportedwarning and the batch starts without a webhook. Statusiscompletedforcompleted,failedforfailed,expired, andcancelled, andpendingfor every other status.RawStatusholds the OpenAI status.RequestCounts,CreatedAt,ExpiresAt, and the first batch error (ErrorMessage,ErrorCode) come from the batch object. A batch error without a message readsOpenAI batch failed.DoListAsyncsendsLimitaslimitandCursorasafter.NextCursoris the page’slast_idwhenhas_moreis true.
OpenAI batch results
Section titled “OpenAI batch results”OpenAIBatchApi.DoGetResultsAsync reads the batch first and throws InvalidArgumentException while its status is pending. It then returns an IAsyncEnumerable<BatchItem>. Reading it downloads the output file and then the error file, and converts each row as it arrives. A row that fails never stops the later rows. A malformed JSON Lines row throws JsonParseException from the stream, after the rows before it.
- A row with
error.codebatch_cancelledorbatch_expiredhas statuscancelledorexpired. Any other row error has statusfailed. - A row with a non-2xx
status_codefails withErrorMessage,ErrorType,ErrorCode, andErrorStatusCodefromresponse.body.error. When the body has no error message, the message isOpenAI batch request failed with status code <n>. - A 2xx row whose body does not match the Responses response schema fails with
invalid_response. This includes an unknownoutputitemtypeand anoutputofnull. A body with nooutputkey also fails withinvalid_response. - A row that does not match the result line schema (
custom_idstring,response.status_codenumber,error.codeanderror.messagestrings) throws aTypeValidationExceptionand stops the download. - A completed batch with no output or error file throws
InvalidResponseDataException. - A Responses
outputconverts toBatchTextGeneration.Content:reasoning(withitemIdandreasoningEncryptedContentprovider metadata),text, andtool-callparts forfunction_callandcustom_tool_call.web_search_call,file_search_call, andcode_interpreter_callbecome a provider-executed dynamictool-callplus atool-result. A failed or incomplete web search setsIsErroron its result. Any other output item fails the row withunsupported_content. FinishReason,RawFinishReason,Usage,ResponseId,Timestamp, andModelIdcome from the body.ProviderMetadata.openaiholdsresponseIdand, when present,logprobs,serviceTier, andreasoningContext.
Response handlers
Section titled “Response handlers”ResponseHandlers in Vercel.AI.ProviderUtils holds the closest .NET shape of the upstream response handler factories. Each one takes the HttpResponseMessage, the request url, and the request body values, and returns the value with the response headers (ResponseHandlerResult<T>) or, for the error handlers, an ApiException.
| Method | Upstream | Result |
|---|---|---|
CreateBinaryResponseAsync |
createBinaryResponseHandler |
The body bytes. An empty body throws an ApiException with “Response body is empty”. |
CreateBinaryStreamResponseAsync |
createBinaryStreamResponseHandler |
The body stream. An empty body throws “Empty response body”. |
CreateEventSourceResponseAsync |
createEventSourceResponseHandler |
A stream of parsed events. A read failure (IOException, HttpRequestException) on the successful response throws a retryable ApiException “Failed to process successful response” that keeps the status code, URL, headers, and the original error as InnerException. |
CreateJsonErrorResponseAsync |
createJsonErrorResponseHandler |
An ApiException whose message comes from errorToMessage, or the status text when the body does not match the error schema. |
CreateStatusCodeErrorResponseAsync |
createStatusCodeErrorResponseHandler |
An ApiException whose message is the status text. |
The binary and error handlers read at most Download.DefaultMaxBytes. A larger Content-Length throws a DownloadError (“exceeded maximum size”) before the body is read, and the body is released.
ApiException now also carries Url, RequestBodyValues, ResponseHeaders, and IsRetryable (408, 429, and 500 and above by default) and ErrorData, the provider error parsed from the response body (upstream APICallError.data). It is a JsonElement when CreateJsonErrorResponseAsync parsed the body, and null otherwise. It is not called Data because Exception.Data already exists.
Embedding dimensions
Section titled “Embedding dimensions”Embed.EmbedAsync, EmbedMany.EmbedManyAsync, and AiClient.EmbedAsync / EmbedManyAsync take an optional Dimensions setting. It requests an output vector size and must be a positive integer. Any other value throws an InvalidArgumentException for parameter dimensions before the model runs. EmbedMany sends the same value to every chunk and every retry, and the start events and the OnEmbedStart telemetry callback carry it. AiClient calls the model once and does not retry or raise those events. Provider options are not changed.
A provider option for the same setting wins over Dimensions. When both are unset, the field is left out of the request.
| Model | Provider option | Request field |
|---|---|---|
| OpenAI | openai.dimensions |
dimensions |
| OpenAI-compatible | dimensions on the compatible options |
dimensions |
| Cohere | cohere.outputDimension |
output_dimension |
| Google (non-Vertex) | google.outputDimensionality |
outputDimensionality |
| Perplexity | perplexity.dimensions |
dimensions |
| Voyage | voyage.outputDimension |
output_dimension |
| Alibaba | alibaba.dimension |
parameters.dimension |
| Amazon Bedrock Titan | amazonBedrock.dimensions |
dimensions |
| Amazon Bedrock Cohere | amazonBedrock.outputDimension |
output_dimension |
| Amazon Bedrock Nova | amazonBedrock.embeddingDimension |
singleEmbeddingParams.embeddingDimension (1024 when unset) |
| Google Vertex (predict) | googleVertex.outputDimensionality |
parameters.outputDimensionality |
| Google Vertex (embedContent) | googleVertex.outputDimensionality |
embedContentConfig.outputDimensionality |
| Mistral | mistral.outputDimension |
output_dimension |
AmazonBedrockProvider.EmbeddingModel creates an InvokeModel embedding model for Titan, Cohere (up to 96 values per call), and Nova model ids. The bedrock options key is accepted as well as amazonBedrock. Cohere token usage comes from the x-amzn-bedrock-input-token-count response header. When the header is missing, Tokens is null.
Application inference profile ARNs do not identify their underlying model. Pass the model family when you create the embedding model, so the provider can select the correct request format and batch size:
var profileArn = "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy";var model = provider.EmbeddingModel(profileArn, new AmazonBedrockEmbeddingModelSettings { ModelFamily = AmazonBedrockEmbeddingModelFamily.Cohere });AmazonBedrockOptions.Headers are sent with every embedding and reranking request. AmazonBedrockEmbeddingModel.Headers are sent with the next call and override provider headers with the same name. Both are signed with the request.
EmbeddingResult.Response holds the raw headers and body of the call, and EmbeddingResult.Warnings its warnings. To send per-call headers, dimensions, or a user to an OpenAI-compatible embedding model, pass an EmbeddingCallOptions to OpenAICompatibleEmbeddingModel.DoEmbedAsync. The model keeps no per-call state, so concurrent calls do not see each other.
ImageCallOptions carries Seed, Files, Mask, ProviderOptions, and Headers for each call. ImageGenerationResult returns CallWarnings, Response (timestamp, model id, headers), and Usage. Image models keep no per-call state, so concurrent calls on one model do not see each other.
Gateway sends dimensions next to values and keeps providerOptions as its own object. It does not merge them.
The Gateway image model sends seed, files, mask, providerOptions, and call headers to /image-model. Byte files are encoded as base64, base64 files pass unchanged, and URL files stay URLs. Its ImageModelResult carries the response Images as the base64 strings the server returned, Warnings (unsupported, compatibility, deprecated, and other), ProviderMetadata, IsRetryable, Response (model id, timestamp, headers), and Usage (InputTokens, OutputTokens, TotalTokens, each null when the server omitted it). MaxImagesPerCall is int.MaxValue, so requests are never split client-side.
IEmbeddingModel.DoEmbedAsync takes int? dimensions = null and IReadOnlyDictionary<string, string?>? headers = null after the cancellation token, and EmbeddingModelCall carries Dimensions. IRerankingModel.DoRerankAsync takes the same headers parameter. Call headers override the provider headers. EmbedOptions.Headers and RerankOptions.Headers pass them from AiClient. Custom models must add the parameters.
ImageCallOptions.Files and Mask are ImageModelFile values, the same file-or-URL union as upstream’s ImageModelV4File. Create one with ImageModelFile.FromFile(bytes, mediaType), ImageModelFile.FromBase64(base64, mediaType) (base64 text that the Gateway passes unchanged), or ImageModelFile.FromUrl(url), and set ProviderOptions on it when a provider reads per-file options. OpenAICompatibleImageFile is gone. Use ImageModelFile instead.
ImageGenerationResult also carries ProviderMetadata and IsRetryable (false when the provider filtered the content, so an empty result is not retried).
Google image models
Section titled “Google image models”GoogleImageModel runs Gemini image generation (gemini-* ids only) through the Gemini language model with the IMAGE response modality. The google entry of ImageCallOptions.ProviderOptions passes to that call, so safety settings, thinking, and the other Google language model options apply. Seed, AspectRatio (merged into imageConfig), and call Headers are sent too. Size adds an unsupported warning. Use AspectRatio instead.
Pass { "google": { "googleSearch": { } } } to ground the image with Google Search. The value is the args of the google.google_search tool, for example { "searchTypes": { "imageSearch": {} } }.
Files are sent as inline images for editing. A URL file throws because its media type image/* is not passed as inline bytes. A Mask throws AiSdkException. SupportsFileInputs is true and SupportsMaskInputs is false for the Gemini image models that take input images, and both are null otherwise.
The result carries Usage, Response (timestamp, model id, headers), and ProviderMetadata under google: the language model metadata (promptFeedback, groundingMetadata, usageMetadata, and so on) plus finishReason (the raw candidate finish reason) and images (one object per image). A blocked prompt returns no images with promptFeedback in the metadata, and IsRetryable is false when the finish reason is a content filter. GoogleImageResult is gone. Use ImageGenerationResult.CallWarnings.
Decide and the deprecated Evaluate names
Section titled “Decide and the deprecated Evaluate names”Decide.DecideAsync(DecideRequest) is experimental_decide. It answers typed questions against one shared state. A decision model implements IDecisionCaller.DoDecideAsync(DecisionModelCall) and returns a DecisionModelResult. Operation ids are ai.decide and ai.decide.doDecide. Telemetry callbacks on DecideTelemetry are OnDecideStart, OnDecideEnd, OnDecisionModelCallStart, and OnDecisionModelCallEnd. Lifecycle events are DecideEvent and DecisionModelEvent. The refused-question and unsupported-type errors are DecisionRefusalError, DecisionUnsupportedQuestionTypeError (marker vercel.ai.error.AI_DecisionUnsupportedQuestionTypeError), and DecisionUnsupportedQuestionTypeException.
DecideRequest.MaxOutputTokens (an integer of at least 1, otherwise InvalidArgumentError is thrown before any model call) and DecideRequest.Reasoning (provider-default, none, minimal, low, medium, high, xhigh, or max) reach the model as DecisionModelCall.MaxOutputTokens and DecisionModelCall.Reasoning, and DecideEvent reports both in OnStart and OnEnd. DecisionLanguageModel forwards them to the wrapped language model and uses reasoning none when Reasoning is null. Set Reasoning = "provider-default" to use the wrapped model’s own default effort instead. GatewayDecisionModel adds maxOutputTokens and reasoning to the request body when set. OpenAIDecisionModel, PerplexityDecisionModel, and TypeSafeAiDecisionModel do not send either setting and report an unsupported warning for maxOutputTokens or reasoning when it is set.
The old names still compile and are marked [Obsolete]: Evaluate.EvaluateAsync, EvaluateRequest, EvaluateResult, EvaluateEvent, EvaluateModelEvent, EvaluateTelemetry, IEvaluationCaller.DoEvaluateAsync, EvaluationQuestion, EvaluationAnswer, EvaluationRounding, EvaluationModelCall, EvaluationModelResult, and the two EvaluationUnsupportedQuestionType* types. C# cannot make two names one type, so each alias derives from its decision type, and Evaluate.EvaluateAsync forwards to Decide.DecideAsync. A model that implements only IEvaluationCaller still runs through Evaluate.EvaluateAsync. To pass it to Decide.DecideAsync, wrap it with DecisionCaller.FromLegacy(model). Both Decide.DecideAsync and DecisionLanguageModel throw the unsupported-type error as EvaluationUnsupportedQuestionTypeException, so a catch of the old or the new type matches, and DecisionUnsupportedQuestionTypeError.IsInstance recognizes it. Deprecated telemetry callbacks run once and receive the decision operation ids. When both a decision callback and its deprecated alias are set, only the decision callback runs. The rubric API is a different feature. See “Rubric models” below.
Decision models in registries and custom providers
Section titled “Decision models in registries and custom providers”A provider exposes decision models by implementing IDecisionProvider.DecisionModel(id). GatewayProvider does. ProviderRegistry.DecisionModel("providerId:modelId") returns the decision model of a registered provider. Custom separators and colons inside the model id work as they do for other model types. Language model middleware does not wrap decision models. CustomProvider.AddDecisionModel(alias, model) registers an alias, and CustomProvider.DecisionModel(alias) returns it. An unknown alias goes to the fallback provider’s decision factory when it has one. A provider without a decision factory, or one that returns null, throws NoSuchModelError with ModelType decisionModel. An unknown registry provider throws NoSuchProviderError. A model whose SpecificationVersion is not v4 throws UnsupportedModelVersionException.
The deprecated factory IEvaluationProvider.EvaluationModel(id) and the deprecated alias map CustomProvider.AddEvaluationModel(alias, IEvaluationCaller) still resolve through DecisionModel. A provider that implements both factories is asked through IDecisionProvider only. The deprecated alias map is ignored once any alias is added with AddDecisionModel. ProviderRegistry.EvaluationModel(id) and CustomProvider.EvaluationModel(id) are deprecated aliases of DecisionModel(id). They return the same decision model as an IEvaluationDecisionCaller, which also has the deprecated DoEvaluateAsync. A model that already implements IEvaluationDecisionCaller is returned unchanged. Any other model is wrapped. The wrapper’s DoEvaluateAsync calls the model’s own IEvaluationCaller.DoEvaluateAsync when it has one, and DoDecideAsync otherwise.
String model ids and the default provider
Section titled “String model ids and the default provider”DecideRequest takes the model as Model (an IDecisionCaller) or as ModelId (a string). Model wins when both are set, and AiSdkException (“Model or ModelId is required.”) is thrown when neither is. A model id is resolved through DecisionModels.DefaultProvider (AI_SDK_DEFAULT_PROVIDER upstream), which must implement IDecisionProvider or the deprecated IEvaluationProvider. Otherwise DecisionModels.Resolve throws InvalidOperationException (“The default provider does not support decision models.”). When DefaultProvider is null, the id goes through GatewayProvider and its AI_GATEWAY_API_KEY key. DecisionModels.GatewayFactory replaces that provider. A provider that returns no model throws NoSuchModelError (decisionModel), and errors thrown by the provider pass through. The v4 check applies whether the model comes from an instance, an alias, a registry, or the default provider. CustomProvider.AddDecisionModel(alias, "provider-model-id") registers a string alias. CustomProvider.DecisionModel(alias) resolves it through the default provider.
Rubric models
Section titled “Rubric models”AiClient.EvaluateAsync(EvaluateOptions) scores a candidate against a rubric and returns a RubricResult. Upstream has no such API, so the port names it after the rubric. A model that scores directly implements IRubricModel and is set on EvaluateOptions.RubricModel. Without one, a language model scores the candidate. ProviderBase.RubricModel(id), ProviderRegistry.RubricModel("providerId:modelId"), CustomProvider.RubricModel(id), and CustomProvider.AddRubricModel(id, model) return and register rubric models. AnthropicProvider.RubricModel(id) returns an AnthropicEvaluationModel.
These names replace the old rubric names, because EvaluationModel now means the deprecated decision accessor. IEvaluationModel, EvaluationResult, EvaluateOptions.EvaluationModel, and CustomProvider.AddEvaluationModel(id, IEvaluationModel) still compile and are marked [Obsolete]. RubricModels.FromLegacy(model) wraps an IEvaluationModel as an IRubricModel. ProviderBase.EvaluationModel is gone; override RubricModel instead. AnthropicProvider.EvaluationModel(id) now returns the decision model; call RubricModel(id) for the rubric model.
Apache-2.0 · Copyright 2023 Vercel, Inc. · unofficial community port, not an official Vercel product