/** * Language Model Well-Known Binding * * Defines the interface for AI model providers. * Any MCP that implements this binding can provide AI models and streaming endpoints. * * This binding includes: * - LLM operations (metadata, stream, generate) * - Collection bindings for LIST and GET operations (read-only) * - Streaming endpoint information is included directly in the model entity schema. */ import { z } from "zod"; import { bindingClient, type ToolBinder } from "../core/binder"; import { BaseCollectionEntitySchema, createCollectionBindings, } from "./collections"; /** * JSON Value Schema * Represents any valid JSON value: null, string, number, boolean, object, or array */ const JSONValueSchema: z.ZodType = z.lazy(() => z.union([ z.null(), z.string(), z.number(), z.boolean(), z.record(z.string(), JSONValueSchema), z.array(JSONValueSchema), ]), ); /** * Provider Options Schema * Additional provider-specific options passed through to the provider */ const ProviderOptionsSchema = z .record(z.string(), z.record(z.string(), JSONValueSchema)) .optional() .describe( "Additional provider-specific options. Outer record keyed by provider name, inner by option key", ); /** * Text Part Schema * Text content part of a prompt */ const TextPartSchema = z.object({ type: z.literal("text"), text: z.string().describe("The text content"), providerOptions: ProviderOptionsSchema, }); /** * Text Output Part Schema * Text content generated by the model (uses providerMetadata instead of providerOptions) */ const TextOutputPartSchema = z.object({ type: z.literal("text"), text: z.string().describe("The text content"), providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), }); /** * Data Content Schema * File data can be a URL string or a base64 encoded string */ const DataContentSchema = z.string().describe("File data as URL string"); /** * File Part Schema * File content part of a prompt */ const FilePartSchema = z.object({ type: z.literal("file"), filename: z.string().optional().describe("Optional filename of the file"), data: DataContentSchema, mediaType: z .string() .describe("IANA media type of the file (e.g., image/png, audio/mp3)"), providerOptions: ProviderOptionsSchema, }); /** * File Output Part Schema * File content generated by the model */ const FileOutputPartSchema = z.object({ type: z.literal("file"), mediaType: z .string() .describe("IANA media type of the file (e.g., image/png, audio/mp3)"), data: z.string().describe("Generated file data as base64 encoded string"), }); /** * Reasoning Part Schema * Reasoning content part of a prompt */ const ReasoningPartSchema = z.object({ type: z.literal("reasoning"), text: z.string().describe("The reasoning text"), providerOptions: ProviderOptionsSchema, }); /** * Reasoning Output Part Schema * Reasoning content generated by the model */ const ReasoningOutputPartSchema = z.object({ type: z.literal("reasoning"), text: z.string().describe("The reasoning text"), providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), }); /** * Tool Call Part Schema * Tool call content part of a prompt (usually generated by the AI model) */ const ToolCallPartSchema = z.object({ type: z.literal("tool-call"), toolCallId: z .string() .describe("ID of the tool call, used to match with tool result"), toolName: z.string().describe("Name of the tool being called"), input: z .string() .describe( "Arguments of the tool call (JSON-serializable object matching tool input schema)", ), providerExecuted: z .boolean() .optional() .describe("Whether the tool call will be executed by the provider"), providerOptions: ProviderOptionsSchema, }); /** * Tool Call Output Part Schema * Tool call content generated by the model */ const ToolCallOutputPartSchema = z.object({ type: z.literal("tool-call"), toolCallId: z.string().describe("ID of the tool call"), toolName: z.string().describe("Name of the tool being called"), input: z .string() .describe("Stringified JSON object with the tool call arguments"), providerExecuted: z .boolean() .optional() .describe("Whether the tool call will be executed by the provider"), providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), }); /** * Tool Result Output Schema * The output of a tool result. * Accepts typed objects (text, json, error-text, error-json, content), * execution-denied (from AI SDK tool approval flow), and raw strings * (JSON-serialized outputs from mapToolResultOutput). */ const ToolResultOutputSchema = z.union([ z.object({ type: z.literal("text"), value: z.string(), }), z.object({ type: z.literal("json"), value: JSONValueSchema, }), z.object({ type: z.literal("error-text"), value: z.string(), }), z.object({ type: z.literal("error-json"), value: JSONValueSchema, }), z.object({ type: z.literal("content"), value: z.array( z.union([ z.object({ type: z.literal("text"), text: z.string().describe("Text content"), }), z.object({ type: z.literal("media"), data: z.string().describe("Base-64 encoded media data"), mediaType: z.string().describe("IANA media type"), }), ]), ), }), z.object({ type: z.literal("execution-denied"), reason: z.string().optional(), }), z .string() .describe( "Raw or JSON-serialized output (e.g. from AI SDK mapToolResultOutput)", ), ]); /** * Tool Result Part Schema * Tool result content part of a prompt */ const ToolResultPartSchema = z.object({ type: z.literal("tool-result"), toolCallId: z .string() .describe("ID of the tool call that this result is associated with"), toolName: z.string().describe("Name of the tool that generated this result"), output: ToolResultOutputSchema.describe("Result of the tool call"), result: z.unknown().describe("Unknown result of the tool call"), providerOptions: ProviderOptionsSchema, }); /** * Tool Result Output Part Schema * Tool result content generated by the model (provider-executed tools) */ const ToolResultOutputPartSchema = z.object({ type: z.literal("tool-result"), toolCallId: z .string() .describe("ID of the tool call that this result is associated with"), toolName: z.string().describe("Name of the tool that generated this result"), result: z.any().describe("Result of the tool call (JSON-serializable)"), isError: z .boolean() .optional() .describe("Whether the result is an error or error message"), providerExecuted: z .boolean() .optional() .describe("Whether the tool result was generated by the provider"), providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), }); /** * Source Part Schema * Source content generated by the model (references to web or document sources) */ const SourcePartSchema = z.union([ z.object({ type: z.literal("source"), sourceType: z.literal("url"), id: z.string().describe("The ID of the source"), url: z.string().describe("The URL of the source"), title: z.string().optional().describe("The title of the source"), providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), }), z.object({ type: z.literal("source"), sourceType: z.literal("document"), id: z.string().describe("The ID of the source"), mediaType: z .string() .describe("IANA media type of the document (e.g., application/pdf)"), title: z.string().describe("The title of the document"), filename: z .string() .optional() .describe("Optional filename of the document"), providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), }), ]); /** * System Message Schema */ const SystemMessageSchema = z.object({ role: z.literal("system"), content: z.string().describe("System message content"), providerOptions: ProviderOptionsSchema, }); /** * User Message Schema */ const UserMessageSchema = z.object({ role: z.literal("user"), content: z .array(z.union([TextPartSchema, FilePartSchema])) .describe("User message content parts (text or file)"), providerOptions: ProviderOptionsSchema, }); /** * Assistant Message Schema */ const AssistantMessageSchema = z.object({ role: z.literal("assistant"), content: z .array( z.union([ TextPartSchema, FilePartSchema, ReasoningPartSchema, ToolCallPartSchema, ToolResultPartSchema, ]), ) .describe( "Assistant message content parts (text, file, reasoning, tool-call, or tool-result)", ), providerOptions: ProviderOptionsSchema, }); /** * Tool Message Schema */ const ToolMessageSchema = z.object({ role: z.literal("tool"), content: z .array(ToolResultPartSchema) .describe("Tool message content (tool results)"), providerOptions: ProviderOptionsSchema, }); /** * Language Model Message Schema * A message in a language model prompt */ export const LanguageModelMessageSchema = z.union([ SystemMessageSchema, UserMessageSchema, AssistantMessageSchema, ToolMessageSchema, ]); /** * Language Model Prompt Schema * A prompt is a list of messages based on LanguageModelV2Prompt from @ai-sdk/provider */ export const LanguageModelPromptSchema = z .array(LanguageModelMessageSchema) .describe("A list of messages forming the prompt"); /** * Language Model Call Options Schema * Based on LanguageModelV2CallOptions from @ai-sdk/provider */ export const LanguageModelCallOptionsSchema = z.object({ // Core parameters prompt: LanguageModelPromptSchema.describe( "A language model prompt is a standardized prompt type (array of messages with roles: system, user, assistant, tool)", ), // Generation parameters maxOutputTokens: z .number() .optional() .describe("Maximum number of tokens to generate"), temperature: z .number() .optional() .describe( "Temperature setting. The range depends on the provider and model", ), topP: z.number().optional().describe("Nucleus sampling parameter"), topK: z .number() .optional() .describe( "Only sample from the top K options for each subsequent token. Used to remove long tail low probability responses", ), presencePenalty: z .number() .optional() .describe( "Presence penalty setting. It affects the likelihood of the model to repeat information that is already in the prompt", ), frequencyPenalty: z .number() .optional() .describe( "Frequency penalty setting. It affects the likelihood of the model to repeatedly use the same words or phrases", ), seed: z .number() .optional() .describe( "The seed (integer) to use for random sampling. If set and supported by the model, calls will generate deterministic results", ), // Stop sequences stopSequences: z .array(z.string()) .optional() .describe( "Stop sequences. If set, the model will stop generating text when one of the stop sequences is generated", ), // Response format responseFormat: z .union([ z.object({ type: z.literal("text") }), z.object({ type: z.literal("json"), schema: z .any() .optional() .describe("JSON schema that the generated output should conform to"), name: z .string() .optional() .describe("Name of output that should be generated"), description: z .string() .optional() .describe("Description of the output that should be generated"), }), ]) .optional() .describe( "Response format. The output can either be text or JSON. Default is text", ), // Tools tools: z .array(z.any()) .optional() .describe("The tools that are available for the model"), toolChoice: z .any() .optional() .describe("Specifies how the tool should be selected. Defaults to 'auto'"), // Stream options includeRawChunks: z .boolean() .optional() .describe( "Include raw chunks in the stream. Only applicable for streaming calls", ), // Abort signal abortSignal: z .any() .optional() .describe("Abort signal for cancelling the operation"), // Additional options // headers: z // .record(z.string(), z.union([z.string(), z.undefined()])) // .optional() // .describe("Additional HTTP headers to be sent with the request"), providerOptions: z .any() .optional() .describe("Additional provider-specific options"), }); /** * Language Model Generate Output Schema * Based on the return type of LanguageModelV2.doGenerate from @ai-sdk/provider */ export const LanguageModelGenerateOutputSchema = z.object({ // Ordered content that the model has generated content: z .array( z.union([ TextOutputPartSchema, FileOutputPartSchema, ReasoningOutputPartSchema, ToolCallOutputPartSchema, ToolResultOutputPartSchema, SourcePartSchema, ]), ) .describe( "Ordered content that the model has generated (text, tool-calls, reasoning, files, sources)", ), // Finish reason (required) finishReason: z .enum([ "stop", "length", "content-filter", "tool-calls", "error", "other", "unknown", ]) .describe("Reason why generation stopped"), // Usage information (required) usage: z .looseObject({ inputTokens: z.number().optional(), outputTokens: z.number().optional(), totalTokens: z.number().optional(), reasoningTokens: z.number().optional(), }) .describe("Usage information for the language model call"), // Provider metadata providerMetadata: z .any() .optional() .describe("Additional provider-specific metadata"), // Request information for telemetry and debugging request: z .object({ body: z .any() .optional() .describe("Request HTTP body sent to the provider API"), }) .optional() .describe("Optional request information for telemetry and debugging"), // Response information for telemetry and debugging response: z .object({ id: z.string().optional().describe("ID for the generated response"), timestamp: z.iso .datetime() .optional() .describe("Timestamp for the start of the generated response"), modelId: z .string() .optional() .describe("The ID of the response model that was used"), headers: z .record(z.string(), z.string()) .optional() .describe("Response headers"), body: z.any().optional().describe("Response HTTP body"), }) .optional() .describe("Optional response information for telemetry and debugging"), // Warnings for the call (required) warnings: z .array(z.any()) .describe("Warnings for the call, e.g. unsupported settings"), }); /** * Language Model Stream Output Schema * Based on the return type of LanguageModelV2.doStream from @ai-sdk/provider */ export const LanguageModelStreamOutputSchema = z.object({ // Stream of language model output parts stream: z.any().describe("ReadableStream of LanguageModelV2StreamPart"), // Request information for telemetry and debugging request: z .object({ body: z .any() .optional() .describe("Request HTTP body sent to the provider API"), }) .optional() .describe("Optional request information for telemetry and debugging"), // Response information response: z .object({ headers: z .record(z.string(), z.string()) .optional() .describe("Response headers"), }) .optional() .describe("Optional response data"), }); export const LanguageModelMetadataSchema = z.object({ supportedUrls: z .record(z.string(), z.array(z.string())) .describe("Supported URL patterns by media type for the provider"), }); /** * Simple Model schema for LLM operations */ export const ModelSchema = z.object({ modelId: z.string().describe("The ID of the model"), // Model-specific fields logo: z.string().nullable(), description: z.string().nullable(), capabilities: z.array(z.string()), limits: z .object({ contextWindow: z.number(), maxOutputTokens: z.number(), }) .nullable(), costs: z .object({ input: z.number(), output: z.number(), }) .nullable(), // Provider information provider: z .enum([ "openai", "anthropic", "google", "x-ai", "deepseek", "openai-compatible", "openrouter", ]) .nullable(), }); export const LanguageModelInputSchema = z.object({ modelId: z.string().describe("The ID of the model"), callOptions: LanguageModelCallOptionsSchema, }); export const ModelCollectionEntitySchema = BaseCollectionEntitySchema.extend({ // Model-specific fields logo: z.string().nullable(), description: z.string().nullable(), capabilities: z.array(z.string()), limits: z .object({ contextWindow: z.number(), maxOutputTokens: z.number(), }) .nullable(), costs: z .object({ input: z.number(), output: z.number(), }) .nullable(), // Provider information provider: z .enum([ "openai", "anthropic", "google", "xai", "deepseek", "openai-compatible", "openrouter", ]) .nullable(), }); /** * LLM Collection Binding (internal) * * Collection bindings for language models (read-only). * Provides LIST and GET operations for AI models. */ const LLM_COLLECTION_BINDING = createCollectionBindings( "llm", ModelCollectionEntitySchema, { readOnly: true }, ); /** * Language Model Binding * * Defines the interface for AI model providers. * Any MCP that implements this binding can provide AI models. * * Required tools: * - LLM_METADATA: Get metadata for a specific model * - LLM_DO_STREAM: Stream a language model response * - LLM_DO_GENERATE: Generate a language model response * - COLLECTION_LLM_LIST: List available AI models with their capabilities * - COLLECTION_LLM_GET: Get a single model by ID * * @deprecated Use native AI SDK provider adapters instead. The llm-binding * abstraction wraps MCP connections as AI SDK v2 language models, but the * AI SDK now expects v3. The decopilot stream path already uses native * providers. This binding will be removed in a future release. */ export const LANGUAGE_MODEL_BINDING = [ { name: "LLM_METADATA" as const, inputSchema: z.object({ modelId: z.string().describe("The ID of the model"), }), outputSchema: LanguageModelMetadataSchema, }, { name: "LLM_DO_STREAM" as const, inputSchema: LanguageModelInputSchema, }, { name: "LLM_DO_GENERATE" as const, inputSchema: LanguageModelInputSchema, outputSchema: LanguageModelGenerateOutputSchema, }, ...LLM_COLLECTION_BINDING, ] satisfies ToolBinder[]; /** * @deprecated Use native AI SDK provider adapters instead. See {@link LANGUAGE_MODEL_BINDING}. */ export const LanguageModelBinding = bindingClient(LANGUAGE_MODEL_BINDING);