sarvam-ai-sdk
Version:
Sarvam AI Provider support for AI-SDK
1 lines • 102 kB
Source Map (JSON)
{"version":3,"sources":["../src/sarvam-provider.ts","../src/sarvam-chat-language-model.ts","../src/convert-to-sarvam-chat-messages.ts","../src/get-response-metadata.ts","../src/map-sarvam-finish-reason.ts","../src/sarvam-error.ts","../src/sarvam-prepare-tools.ts","../src/sarvam-speech-model.ts","../src/sarvam-speech-settings.ts","../src/sarvam-transcription-model.ts","../src/sarvam-transcription-settings.ts","../src/sarvam-translation-model.ts","../src/sarvam-config.ts","../src/sarvam-transliterate-model.ts","../src/sarvam-lid-model.ts","../src/sarvam-speech-translation-model.ts"],"sourcesContent":["import {\n LanguageModelV1,\n SpeechModelV1,\n TranscriptionModelV1\n} from \"@ai-sdk/provider\";\nimport {\n FetchFunction,\n loadApiKey,\n withoutTrailingSlash,\n} from \"@ai-sdk/provider-utils\";\nimport { SarvamChatLanguageModel } from \"./sarvam-chat-language-model\";\nimport { SarvamChatModelId, SarvamChatSettings } from \"./sarvam-chat-settings\";\nimport { SarvamLanguageCode } from \"./sarvam-config\";\nimport {\n SarvamSpeechModel,\n} from \"./sarvam-speech-model\";\nimport { SarvamSpeechModelId, SarvamSpeechSettings } from \"./sarvam-speech-settings\";\nimport {\n SarvamTranscriptionModel,\n} from \"./sarvam-transcription-model\";\nimport { SarvamSpeechTranslationModelId, SarvamTranscriptionCallOptions, SarvamTranscriptionModelId } from \"./sarvam-transcription-settings\";\nimport { SarvamTranslationModel } from \"./sarvam-translation-model\";\nimport { SarvamTranslationSettings } from \"./sarvam-translation-settings\";\nimport { SarvamTransliterateModel } from \"./sarvam-transliterate-model\";\nimport { SarvamTransliterateSettings } from \"./sarvam-transliterate-settings\";\nimport { SarvamLidModel } from \"./sarvam-lid-model\";\nimport { SarvamSpeechTranslationModel } from \"./sarvam-speech-translation-model\";\n\nexport interface SarvamProvider {\n /**\n * Creates a model for text generation.\n */\n (modelId: SarvamChatModelId, settings?: SarvamChatSettings): LanguageModelV1;\n\n /**\n * Creates an Sarvam chat model for text generation.\n */\n languageModel(\n modelId: SarvamChatModelId,\n settings?: SarvamChatSettings,\n ): LanguageModelV1;\n\n /**\n * Creates a Sarvam model for transcription.\n */\n transcription(\n modelId: SarvamTranscriptionModelId,\n /**\n * Audio source language code\n *\n * @default unknown\n */\n languageCode?: SarvamLanguageCode | \"unknown\",\n settings?: SarvamTranscriptionCallOptions,\n ): TranscriptionModelV1;\n\n /**\n * Creates a Sarvam model for Speech translation.\n */\n speechTranslation(\n modelId: SarvamSpeechTranslationModelId,\n ): TranscriptionModelV1;\n\n /**\n * Creates a Sarvam model for speech.\n */\n speech(\n modelId: SarvamSpeechModelId,\n languageCode: SarvamLanguageCode,\n settings?: SarvamSpeechSettings,\n ): SpeechModelV1;\n\n /**\n * Creates an Sarvam model for transliterate.\n */\n transliterate(settings: SarvamTransliterateSettings): LanguageModelV1;\n\n /**\n * Creates an Sarvam model for translation.\n */\n translation(settings: SarvamTranslationSettings): LanguageModelV1;\n\n /**\n * Creates an Sarvam model for language identification.\n */\n languageIdentification(): LanguageModelV1;\n}\n\nexport interface SarvamProviderSettings {\n /**\n * URL for the Sarvam API calls.\n * @default https://api.sarvam.ai\n */\n baseURL?: string;\n\n /**\n * API key for authenticating requests.\n * @default process.env.SARVAM_API_KEY\n */\n apiKey?: string;\n\n /**\n * Custom headers to include in the requests.\n * @default\n Authorization: `Bearer ${process.env.SARVAM_API_KEY}`,\n \"api-subscription-key\": process.env.SARVAM_API_KEY\n */\n headers?: Record<string, string>;\n\n /**\n * Custom fetch implementation. You can use it as a middleware to intercept requests,\n * or to provide a custom fetch implementation for e.g. testing.\n */\n fetch?: FetchFunction;\n}\n\n/**\n* Create an Sarvam provider instance.\n*/\nexport function createSarvam(\n options: SarvamProviderSettings = {},\n): SarvamProvider {\n const baseURL =\n withoutTrailingSlash(options.baseURL) ?? \"https://api.sarvam.ai\";\n\n const getApiKey = () => loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: \"SARVAM_API_KEY\",\n description: \"Sarvam\",\n });\n\n const getHeaders = () => ({\n Authorization: `Bearer ${getApiKey()}`,\n \"api-subscription-key\": getApiKey(),\n ...options.headers,\n });\n\n const createChatModel = (\n modelId: SarvamChatModelId,\n settings: SarvamChatSettings = {},\n ) =>\n new SarvamChatLanguageModel(modelId, settings, {\n provider: \"sarvam.chat\",\n url: ({ path }) => `${baseURL}/v1${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n });\n\n const createLanguageModel = (\n modelId: SarvamChatModelId,\n settings?: SarvamChatSettings,\n ) => {\n if (new.target) {\n throw new Error(\n \"The Sarvam model function cannot be called with the new keyword.\",\n );\n }\n\n return createChatModel(modelId, settings);\n };\n\n const createTranscriptionModel = (\n modelId: SarvamTranscriptionModelId,\n languageCode: SarvamLanguageCode | \"unknown\" = \"unknown\",\n settings?: SarvamTranscriptionCallOptions,\n ) => new SarvamTranscriptionModel(modelId, languageCode, {\n provider: \"sarvam.transcription\",\n url: ({ path }) => `${baseURL}${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n transcription: settings,\n });\n\n const createSpeechTranslation = (\n modelId: SarvamTranscriptionModelId\n ) => new SarvamSpeechTranslationModel(modelId, {\n provider: \"sarvam.transcription\",\n url: ({ path }) => `${baseURL}${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n });\n\n const createSpeechModel = (\n modelId: SarvamSpeechModelId,\n languageCode: SarvamLanguageCode,\n settings?: SarvamSpeechSettings,\n ) =>\n new SarvamSpeechModel(modelId, languageCode, {\n provider: \"sarvam.speech\",\n url: ({ path }) => `${baseURL}${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n speech: settings,\n });\n\n const createTransliterateModel = (settings: SarvamTransliterateSettings) =>\n new SarvamTransliterateModel(\n settings,\n {\n provider: \"sarvam.transliterate\",\n url: ({ path }) => `${baseURL}${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n },\n );\n\n const createTranslationModel = (settings: SarvamTranslationSettings) =>\n new SarvamTranslationModel(\n settings,\n {\n provider: \"sarvam.translation\",\n url: ({ path }) => `${baseURL}${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n },\n );\n\n const createLidModel = () =>\n new SarvamLidModel(\n {\n provider: \"sarvam.lid\",\n url: ({ path }) => `${baseURL}${path}`,\n headers: getHeaders,\n fetch: options.fetch,\n },\n );\n\n const provider = (\n modelId: SarvamChatModelId,\n settings?: SarvamChatSettings,\n ) => createLanguageModel(modelId, settings);\n\n provider.languageModel = createLanguageModel;\n provider.chat = createChatModel;\n provider.transcription = createTranscriptionModel;\n provider.speechTranslation = createSpeechTranslation;\n provider.speech = createSpeechModel;\n provider.transliterate = createTransliterateModel;\n provider.translation = createTranslationModel;\n provider.languageIdentification = createLidModel;\n\n return provider;\n}\n\n/**\n* Default Sarvam provider instance.\n*/\nexport const sarvam = createSarvam();\n","import {\n InvalidResponseDataError,\n LanguageModelV1,\n LanguageModelV1CallWarning,\n LanguageModelV1FinishReason,\n LanguageModelV1FunctionToolCall,\n LanguageModelV1Prompt,\n LanguageModelV1ProviderMetadata,\n LanguageModelV1StreamPart,\n} from \"@ai-sdk/provider\";\nimport {\n FetchFunction,\n ParseResult,\n combineHeaders,\n createEventSourceResponseHandler,\n createJsonResponseHandler,\n generateId,\n isParsableJson,\n parseProviderOptions,\n postJsonToApi,\n} from \"@ai-sdk/provider-utils\";\nimport { z } from \"zod\";\nimport { convertToSarvamChatMessages } from \"./convert-to-sarvam-chat-messages\";\nimport { getResponseMetadata } from \"./get-response-metadata\";\nimport { mapSarvamFinishReason } from \"./map-sarvam-finish-reason\";\nimport { SarvamChatModelId, SarvamChatSettings } from \"./sarvam-chat-settings\";\nimport {\n sarvamErrorDataSchema,\n sarvamFailedResponseHandler,\n} from \"./sarvam-error\";\nimport {\n extractToolCallData,\n parseJSON,\n prepareTools,\n simulateJsonSchema,\n simulateToolCalling,\n} from \"./sarvam-prepare-tools\";\n\ntype SarvamChatConfig = {\n provider: string;\n headers: () => Record<string, string | undefined>;\n url: (options: { modelId: string; path: string }) => string;\n fetch?: FetchFunction;\n};\n\nexport class SarvamChatLanguageModel implements LanguageModelV1 {\n readonly specificationVersion = \"v1\";\n\n readonly supportsStructuredOutputs = false;\n readonly defaultObjectGenerationMode = \"json\";\n\n readonly modelId: SarvamChatModelId;\n readonly settings: SarvamChatSettings;\n\n private readonly config: SarvamChatConfig;\n\n constructor(\n modelId: SarvamChatModelId,\n settings: SarvamChatSettings,\n config: SarvamChatConfig,\n ) {\n this.modelId = modelId;\n this.settings = settings;\n this.config = config;\n }\n\n get provider(): string {\n return this.config.provider;\n }\n\n get supportsImageUrls(): boolean {\n // image urls can be sent if downloadImages is disabled (default):\n return !this.settings.downloadImages;\n }\n\n private async getArgs({\n mode,\n prompt,\n maxTokens,\n temperature,\n topP,\n topK,\n frequencyPenalty,\n presencePenalty,\n stopSequences,\n responseFormat,\n seed,\n stream,\n providerMetadata,\n }: Parameters<LanguageModelV1[\"doGenerate\"]>[0] & {\n stream: boolean;\n }) {\n const type = mode.type;\n const simulate = this.settings.simulate\n\n if (type === \"object-json\" && simulate === \"tool-calling\")\n throw new Error('Use { simulate: \"json-object\" } with generateObject()')\n\n if (type === \"regular\" && simulate === \"json-object\")\n throw new Error('Use { simulate: \"tool-calling\" } with generateText()')\n\n const warnings: LanguageModelV1CallWarning[] = [];\n\n if (stream) {\n warnings.push({\n type: \"other\",\n message: \"Streaming is still experimental for Sarvam\",\n });\n }\n\n if (topK != null) {\n warnings.push({\n type: \"unsupported-setting\",\n setting: \"topK\",\n });\n }\n\n if (\n responseFormat != null &&\n responseFormat.type === \"json\" &&\n responseFormat.schema != null\n ) {\n warnings.push({\n type: \"unsupported-setting\",\n setting: \"responseFormat\",\n details: \"JSON response format schema is not supported\",\n });\n }\n\n const sarvamOptions = parseProviderOptions({\n provider: \"sarvam\",\n providerOptions: providerMetadata,\n schema: z.object({\n reasoningFormat: z.enum([\"parsed\", \"raw\", \"hidden\"]).nullish(),\n }),\n });\n\n const baseArgs = (\n prompt: LanguageModelV1Prompt,\n extraSystemPrompt?: string,\n ) => ({\n // model id:\n model: this.modelId,\n\n // model specific settings:\n user: this.settings.user,\n parallel_tool_calls: this.settings.parallelToolCalls,\n\n // standardized settings:\n max_tokens: maxTokens,\n temperature,\n top_p: topP,\n frequency_penalty: frequencyPenalty,\n presence_penalty: presencePenalty,\n stop: stopSequences,\n seed,\n\n // response format:\n response_format:\n // json object response format is not supported for streaming:\n stream === false && responseFormat?.type === \"json\"\n ? { type: \"json_object\" }\n : undefined,\n\n // provider options:\n reasoning_format: sarvamOptions?.reasoningFormat,\n\n // messages:\n messages: convertToSarvamChatMessages(prompt, extraSystemPrompt),\n });\n\n switch (type) {\n case \"regular\": {\n const { tools, tool_choice, toolWarnings } = prepareTools({\n mode,\n });\n\n const extraSystemPrompt =\n tools && simulate === \"tool-calling\"\n ? await simulateToolCalling(tools)\n : undefined;\n\n return {\n args: {\n ...baseArgs(prompt, extraSystemPrompt),\n tools,\n tool_choice,\n },\n warnings: [...warnings, ...toolWarnings],\n };\n }\n\n case \"object-json\": {\n const extraSystemPrompt = simulate === \"json-object\"\n ? simulateJsonSchema()\n : undefined;\n\n return {\n args: {\n ...baseArgs(prompt, extraSystemPrompt),\n response_format:\n // json object response format is not supported for streaming:\n stream === false ? { type: \"json_object\" } : undefined,\n },\n warnings,\n };\n }\n\n case \"object-tool\": {\n return {\n args: {\n ...baseArgs(prompt),\n tool_choice: {\n type: \"function\",\n function: { name: mode.tool.name },\n },\n tools: [\n {\n type: \"function\",\n function: {\n name: mode.tool.name,\n description: mode.tool.description,\n parameters: mode.tool.parameters,\n },\n },\n ],\n },\n warnings,\n };\n }\n\n default: {\n const _exhaustiveCheck: never = type;\n throw new Error(`Unsupported type: ${_exhaustiveCheck}`);\n }\n }\n }\n\n async doGenerate(\n options: Parameters<LanguageModelV1[\"doGenerate\"]>[0],\n ): Promise<Awaited<ReturnType<LanguageModelV1[\"doGenerate\"]>>> {\n const { args, warnings } = await this.getArgs({\n ...options,\n stream: false,\n });\n\n const body = JSON.stringify(args);\n\n const {\n responseHeaders,\n value: response,\n rawValue: rawResponse,\n } = await postJsonToApi({\n url: this.config.url({\n path: \"/chat/completions\",\n modelId: this.modelId,\n }),\n headers: combineHeaders(this.config.headers(), options.headers),\n body: args,\n failedResponseHandler: sarvamFailedResponseHandler,\n successfulResponseHandler: createJsonResponseHandler(\n sarvamChatResponseSchema,\n ),\n abortSignal: options.abortSignal,\n fetch: this.config.fetch,\n });\n\n const { messages: rawPrompt, ...rawSettings } = args;\n const choice = response.choices[0];\n\n let text = choice.message.content ?? undefined;\n\n let toolCalls = choice.message.tool_calls?.map((toolCall) => ({\n toolCallType: \"function\",\n toolCallId: toolCall.id ?? generateId(),\n toolName: toolCall.function.name,\n args: toolCall.function.arguments!,\n })) as LanguageModelV1FunctionToolCall[] | undefined;\n\n // simulate tool calling through prompt engineering\n if (this.settings.simulate === \"tool-calling\") {\n if (text && text.length !== 0) {\n const jsonObject = parseJSON(text);\n if (jsonObject) {\n const newTools = extractToolCallData(jsonObject);\n if (newTools) {\n toolCalls = [newTools];\n text = undefined;\n }\n }\n }\n }\n\n // simulate JSON object generation through prompt engineering\n if (this.settings.simulate === \"json-object\") {\n if (text && text.length !== 0) {\n const jsonObject = parseJSON(text);\n if (jsonObject) {\n const newTools = extractToolCallData(jsonObject);\n text = JSON.stringify(jsonObject);\n }\n }\n }\n\n return {\n text,\n toolCalls,\n reasoning: choice.message.reasoning ?? undefined,\n finishReason: mapSarvamFinishReason(choice.finish_reason),\n usage: {\n promptTokens: response.usage?.prompt_tokens ?? NaN,\n completionTokens: response.usage?.completion_tokens ?? NaN,\n },\n rawCall: { rawPrompt, rawSettings },\n rawResponse: { headers: responseHeaders, body: rawResponse },\n response: getResponseMetadata(response),\n warnings,\n request: { body },\n };\n }\n\n async doStream(\n options: Parameters<LanguageModelV1[\"doStream\"]>[0],\n ): Promise<Awaited<ReturnType<LanguageModelV1[\"doStream\"]>>> {\n const { args, warnings } = await this.getArgs({ ...options, stream: true });\n\n const body = JSON.stringify({ ...args, stream: true });\n\n const { responseHeaders, value: response } = await postJsonToApi({\n url: this.config.url({\n path: \"/chat/completions\",\n modelId: this.modelId,\n }),\n headers: combineHeaders(this.config.headers(), options.headers),\n body: {\n ...args,\n stream: true,\n },\n failedResponseHandler: sarvamFailedResponseHandler,\n successfulResponseHandler: createEventSourceResponseHandler(\n sarvamChatChunkSchema,\n ),\n abortSignal: options.abortSignal,\n fetch: this.config.fetch,\n });\n\n const { messages: rawPrompt, ...rawSettings } = args;\n\n const toolCalls: Array<{\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string;\n };\n hasFinished: boolean;\n }> = [];\n\n let finishReason: LanguageModelV1FinishReason = \"unknown\";\n let usage: {\n promptTokens: number | undefined;\n completionTokens: number | undefined;\n } = {\n promptTokens: undefined,\n completionTokens: undefined,\n };\n let isFirstChunk = true;\n\n let providerMetadata: LanguageModelV1ProviderMetadata | undefined;\n return {\n stream: response.pipeThrough(\n new TransformStream<\n ParseResult<z.infer<typeof sarvamChatChunkSchema>>,\n LanguageModelV1StreamPart\n >({\n transform(chunk, controller) {\n // handle failed chunk parsing / validation:\n if (!chunk.success) {\n finishReason = \"error\";\n controller.enqueue({\n type: \"error\",\n error: chunk.error,\n });\n return;\n }\n\n const value = chunk.value;\n\n // handle error chunks:\n if (\"error\" in value) {\n finishReason = \"error\";\n controller.enqueue({\n type: \"error\",\n error: value.error,\n });\n return;\n }\n\n if (isFirstChunk) {\n isFirstChunk = false;\n\n controller.enqueue({\n type: \"response-metadata\",\n ...getResponseMetadata(value),\n });\n }\n\n if (value.x_sarvam?.usage != null) {\n usage = {\n promptTokens: value.x_sarvam.usage.prompt_tokens ?? undefined,\n completionTokens:\n value.x_sarvam.usage.completion_tokens ?? undefined,\n };\n }\n\n const choice = value.choices[0];\n\n if (choice?.finish_reason != null) {\n finishReason = mapSarvamFinishReason(choice.finish_reason);\n }\n\n if (choice?.delta == null) {\n return;\n }\n\n const delta = choice.delta;\n\n if (delta.reasoning != null && delta.reasoning.length > 0) {\n controller.enqueue({\n type: \"reasoning\",\n textDelta: delta.reasoning,\n });\n }\n\n if (delta.content != null && delta.content.length > 0) {\n controller.enqueue({\n type: \"text-delta\",\n textDelta: delta.content,\n });\n }\n\n if (delta.tool_calls != null) {\n for (const toolCallDelta of delta.tool_calls) {\n const index = toolCallDelta.index;\n\n if (toolCalls[index] == null) {\n if (toolCallDelta.type !== \"function\") {\n throw new InvalidResponseDataError({\n data: toolCallDelta,\n message: `Expected 'function' type.`,\n });\n }\n\n if (toolCallDelta.id == null) {\n throw new InvalidResponseDataError({\n data: toolCallDelta,\n message: `Expected 'id' to be a string.`,\n });\n }\n\n if (toolCallDelta.function?.name == null) {\n throw new InvalidResponseDataError({\n data: toolCallDelta,\n message: `Expected 'function.name' to be a string.`,\n });\n }\n\n toolCalls[index] = {\n id: toolCallDelta.id,\n type: \"function\",\n function: {\n name: toolCallDelta.function.name,\n arguments: toolCallDelta.function.arguments ?? \"\",\n },\n hasFinished: false,\n };\n\n const toolCall = toolCalls[index];\n\n if (\n toolCall.function?.name != null &&\n toolCall.function?.arguments != null\n ) {\n // send delta if the argument text has already started:\n if (toolCall.function.arguments.length > 0) {\n controller.enqueue({\n type: \"tool-call-delta\",\n toolCallType: \"function\",\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n argsTextDelta: toolCall.function.arguments,\n });\n }\n\n // check if tool call is complete\n // (some providers send the full tool call in one chunk):\n if (isParsableJson(toolCall.function.arguments)) {\n controller.enqueue({\n type: \"tool-call\",\n toolCallType: \"function\",\n toolCallId: toolCall.id ?? generateId(),\n toolName: toolCall.function.name,\n args: toolCall.function.arguments,\n });\n toolCall.hasFinished = true;\n }\n }\n\n continue;\n }\n\n // existing tool call, merge if not finished\n const toolCall = toolCalls[index];\n\n if (toolCall.hasFinished) {\n continue;\n }\n\n if (toolCallDelta.function?.arguments != null) {\n toolCall.function!.arguments +=\n toolCallDelta.function?.arguments ?? \"\";\n }\n\n // send delta\n controller.enqueue({\n type: \"tool-call-delta\",\n toolCallType: \"function\",\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n argsTextDelta: toolCallDelta.function.arguments ?? \"\",\n });\n\n // check if tool call is complete\n if (\n toolCall.function?.name != null &&\n toolCall.function?.arguments != null &&\n isParsableJson(toolCall.function.arguments)\n ) {\n controller.enqueue({\n type: \"tool-call\",\n toolCallType: \"function\",\n toolCallId: toolCall.id ?? generateId(),\n toolName: toolCall.function.name,\n args: toolCall.function.arguments,\n });\n toolCall.hasFinished = true;\n }\n }\n }\n },\n\n flush(controller) {\n controller.enqueue({\n type: \"finish\",\n finishReason,\n usage: {\n promptTokens: usage.promptTokens ?? NaN,\n completionTokens: usage.completionTokens ?? NaN,\n },\n ...(providerMetadata != null ? { providerMetadata } : {}),\n });\n },\n }),\n ),\n rawCall: { rawPrompt, rawSettings },\n rawResponse: { headers: responseHeaders },\n warnings,\n request: { body },\n };\n }\n}\n\n// limited version of the schema, focussed on what is needed for the implementation\n// this approach limits breakages when the API changes and increases efficiency\nconst sarvamChatResponseSchema = z.object({\n id: z.string().nullish(),\n created: z.number().nullish(),\n model: z.string().nullish(),\n choices: z.array(\n z.object({\n message: z.object({\n content: z.string().nullish(),\n reasoning: z.string().nullish(),\n tool_calls: z\n .array(\n z.object({\n id: z.string().nullish(),\n type: z.literal(\"function\"),\n function: z.object({\n name: z.string(),\n arguments: z.string(),\n }),\n }),\n )\n .nullish(),\n }),\n index: z.number(),\n finish_reason: z.string().nullish(),\n }),\n ),\n usage: z\n .object({\n prompt_tokens: z.number().nullish(),\n completion_tokens: z.number().nullish(),\n })\n .nullish(),\n});\n\n// limited version of the schema, focussed on what is needed for the implementation\n// this approach limits breakages when the API changes and increases efficiency\nconst sarvamChatChunkSchema = z.union([\n z.object({\n id: z.string().nullish(),\n created: z.number().nullish(),\n model: z.string().nullish(),\n choices: z.array(\n z.object({\n delta: z\n .object({\n content: z.string().nullish(),\n reasoning: z.string().nullish(),\n tool_calls: z\n .array(\n z.object({\n index: z.number(),\n id: z.string().nullish(),\n type: z.literal(\"function\").optional(),\n function: z.object({\n name: z.string().nullish(),\n arguments: z.string().nullish(),\n }),\n }),\n )\n .nullish(),\n })\n .nullish(),\n finish_reason: z.string().nullable().optional(),\n index: z.number(),\n }),\n ),\n x_sarvam: z\n .object({\n usage: z\n .object({\n prompt_tokens: z.number().nullish(),\n completion_tokens: z.number().nullish(),\n })\n .nullish(),\n })\n .nullish(),\n }),\n sarvamErrorDataSchema,\n]);\n","import {\n LanguageModelV1Prompt,\n UnsupportedFunctionalityError,\n} from \"@ai-sdk/provider\";\nimport { convertUint8ArrayToBase64 } from \"@ai-sdk/provider-utils\";\nimport { SarvamChatPrompt } from \"./sarvam-api-types\";\n\nexport function convertToSarvamChatMessages(\n prompt: LanguageModelV1Prompt,\n fakeToolSystemPrompt?: string,\n): SarvamChatPrompt {\n const messages: SarvamChatPrompt = [];\n\n for (const { role, content } of prompt) {\n switch (role) {\n case \"system\": {\n const contentData = fakeToolSystemPrompt\n ? `${content}\\n\\n${fakeToolSystemPrompt}`\n : content;\n messages.push({ role: \"system\", content: contentData });\n break;\n }\n\n case \"user\": {\n if (content.length === 1 && content[0].type === \"text\") {\n messages.push({ role: \"user\", content: content[0].text });\n break;\n }\n\n messages.push({\n role: \"user\",\n content: content.map((part) => {\n switch (part.type) {\n case \"text\": {\n return { type: \"text\", text: part.text };\n }\n case \"image\": {\n return {\n type: \"image_url\",\n image_url: {\n url:\n part.image instanceof URL\n ? part.image.toString()\n : `data:${\n part.mimeType ?? \"image/jpeg\"\n };base64,${convertUint8ArrayToBase64(part.image)}`,\n },\n };\n }\n case \"file\": {\n throw new UnsupportedFunctionalityError({\n functionality: \"File content parts in user messages\",\n });\n }\n }\n }),\n });\n\n break;\n }\n\n case \"assistant\": {\n let text = \"\";\n const toolCalls: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }> = [];\n\n for (const part of content) {\n switch (part.type) {\n case \"text\": {\n text += part.text;\n break;\n }\n case \"tool-call\": {\n toolCalls.push({\n id: part.toolCallId,\n type: \"function\",\n function: {\n name: part.toolName,\n arguments: JSON.stringify(part.args),\n },\n });\n break;\n }\n }\n }\n\n messages.push({\n role: \"assistant\",\n content: text,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n });\n\n break;\n }\n\n case \"tool\": {\n for (const toolResponse of content) {\n messages.push({\n role: \"tool\",\n tool_call_id: toolResponse.toolCallId,\n content: JSON.stringify(toolResponse.result),\n });\n }\n break;\n }\n\n default: {\n const _exhaustiveCheck: never = role;\n throw new Error(`Unsupported role: ${_exhaustiveCheck}`);\n }\n }\n }\n\n return messages;\n}\n","export function getResponseMetadata({\n id,\n model,\n created,\n}: {\n id?: string | undefined | null;\n created?: number | undefined | null;\n model?: string | undefined | null;\n}) {\n return {\n id: id ?? undefined,\n modelId: model ?? undefined,\n timestamp: created != null ? new Date(created * 1000) : undefined,\n };\n}\n","import { LanguageModelV1FinishReason } from \"@ai-sdk/provider\";\n\nexport function mapSarvamFinishReason(\n finishReason: string | null | undefined,\n): LanguageModelV1FinishReason {\n switch (finishReason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"content_filter\":\n return \"content-filter\";\n case \"function_call\":\n case \"tool_calls\":\n return \"tool-calls\";\n default:\n return \"unknown\";\n }\n}\n","import { z } from \"zod\";\nimport { createJsonErrorResponseHandler } from \"@ai-sdk/provider-utils\";\n\nexport const sarvamErrorDataSchema = z.object({\n error: z.object({\n message: z.string(),\n type: z.string(),\n }),\n});\n\nexport type SarvamErrorData = z.infer<typeof sarvamErrorDataSchema>;\n\nexport const sarvamFailedResponseHandler = createJsonErrorResponseHandler({\n errorSchema: sarvamErrorDataSchema,\n errorToMessage: (data) => data.error.message,\n});\n","import {\n LanguageModelV1,\n LanguageModelV1CallWarning,\n LanguageModelV1FunctionToolCall,\n UnsupportedFunctionalityError,\n} from \"@ai-sdk/provider\";\nimport { generateId } from \"@ai-sdk/provider-utils\";\n\ntype SarvamTools = Array<{\n type: \"function\";\n function: {\n name: string;\n description: string | undefined;\n parameters: unknown;\n };\n}>;\n\nexport function prepareTools({\n mode,\n}: {\n mode: Parameters<LanguageModelV1[\"doGenerate\"]>[0][\"mode\"] & {\n type: \"regular\";\n };\n}): {\n tools:\n | undefined\n | Array<{\n type: \"function\";\n function: {\n name: string;\n description: string | undefined;\n parameters: unknown;\n };\n }>;\n tool_choice:\n | { type: \"function\"; function: { name: string } }\n | \"auto\"\n | \"none\"\n | \"required\"\n | undefined;\n toolWarnings: LanguageModelV1CallWarning[];\n fakeTools?: string;\n} {\n // when the tools array is empty, change it to undefined to prevent errors:\n const tools = mode.tools?.length ? mode.tools : undefined;\n const toolWarnings: LanguageModelV1CallWarning[] = [];\n\n if (tools == null) {\n return { tools: undefined, tool_choice: undefined, toolWarnings };\n }\n\n const toolChoice = mode.toolChoice;\n\n const sarvamTools: SarvamTools = [];\n\n for (const tool of tools) {\n if (tool.type === \"provider-defined\") {\n toolWarnings.push({ type: \"unsupported-tool\", tool });\n } else {\n sarvamTools.push({\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n });\n }\n }\n\n if (toolChoice == null) {\n return { tools: sarvamTools, tool_choice: undefined, toolWarnings };\n }\n\n const type = toolChoice.type;\n\n switch (type) {\n case \"auto\":\n case \"none\":\n case \"required\":\n return { tools: sarvamTools, tool_choice: type, toolWarnings };\n case \"tool\":\n return {\n tools: sarvamTools,\n tool_choice: {\n type: \"function\",\n function: {\n name: toolChoice.toolName,\n },\n },\n toolWarnings,\n };\n default: {\n const _exhaustiveCheck: never = type;\n throw new UnsupportedFunctionalityError({\n functionality: `Unsupported tool choice type: ${_exhaustiveCheck}`,\n });\n }\n }\n}\n\nimport { compile } from \"json-schema-to-typescript\";\n\nexport const simulateToolCalling = async (\n tools: SarvamTools,\n): Promise<string> => {\n const context = [];\n const names = [];\n\n for (const tool of tools) {\n names.push(tool.function.name);\n\n const tsType = await compile(\n tool.function.parameters as object,\n tool.function.name,\n {\n bannerComment: \"\",\n format: false,\n declareExternallyReferenced: true,\n enableConstEnums: true,\n unreachableDefinitions: false,\n strictIndexSignatures: false,\n },\n );\n const toolContext = tsType\n .replace(\n /export interface (\\w+) \\{/,\n (_: any, name: string) => `type ${tool.function.name} = {`,\n )\n .replace(/\\/\\*\\*\\s*\\n\\s*\\*\\s*(.*?)\\s*\\n\\s*\\*\\//, \"// $1\");\n context.push(`// ${tool.function.description}\\n${toolContext}`);\n }\n\n const text = `These are the available tool you can execute.\n\n${context.join(\"\\n\")}\n\ntype YourToolChoices = {\n 'toolName': '${names.join(\"' | '\")}',\n 'toolData': ${names.join(\" | \")}\n}\n\nRespond normally.\nIf user request to execute any tool, respond with pure JSON format\nMake sure to translate toolData to English.\n\neg:\nconst myChoice: YourToolChoices = {\n \"toolName\": <name>,\n \"toolData\": <data>\n}`;\n\n return text;\n};\n\nexport const extractToolCallData = (\n jsonObject: object,\n): LanguageModelV1FunctionToolCall | void => {\n\n type ToolFunction = {\n toolName: string;\n toolData: any;\n };\n const toolFunction = jsonObject as ToolFunction\n\n if (!(\"toolName\" in toolFunction)) return;\n if (!(\"toolData\" in toolFunction)) return;\n\n return {\n args: JSON.stringify(toolFunction.toolData),\n toolCallId: generateId(),\n toolCallType: \"function\",\n toolName: toolFunction.toolName,\n };\n\n};\n\nexport const parseJSON = <T extends object>(\n text: string,\n):T | void => {\n const jsonRegex = /\\{(?:[^{}]*|\\{[^{}]*\\})*\\}/g;\n const jsonMatches = text.match(jsonRegex);\n\n if (jsonMatches && jsonMatches[0]) {\n try {\n const jsonObject = JSON.parse(jsonMatches[0])\n return jsonObject\n }\n catch (error) {}\n }\n}\n\nexport const simulateJsonSchema = () => \"If user doen't specify, make sure to translate json data content into pure English.\"\n","import type { SpeechModelV1, SpeechModelV1CallWarning } from \"@ai-sdk/provider\";\nimport {\n combineHeaders,\n createBinaryResponseHandler,\n createJsonResponseHandler,\n parseProviderOptions,\n postJsonToApi,\n} from \"@ai-sdk/provider-utils\";\nimport type { SarvamConfig, SarvamLanguageCode } from \"./sarvam-config\";\nimport { sarvamFailedResponseHandler } from \"./sarvam-error\";\nimport {\n SarvamProviderOptionsSchema,\n SarvamSpeechSettings,\n SpeakerSchema,\n type SarvamSpeechModelId,\n} from \"./sarvam-speech-settings\";\nimport type { SarvamSpeechAPITypes } from \"./sarvam-api-types\";\nimport { z } from \"zod\";\n\ninterface SarvamSpeechModelConfig extends SarvamConfig {\n _internal?: {\n currentDate?: () => Date;\n };\n speech?: SarvamSpeechSettings;\n}\n\nexport class SarvamSpeechModel implements SpeechModelV1 {\n readonly specificationVersion = \"v1\";\n\n get provider(): string {\n return this.config.provider;\n }\n\n constructor(\n readonly modelId: SarvamSpeechModelId,\n readonly languageCode: SarvamLanguageCode,\n private readonly config: SarvamSpeechModelConfig,\n ) {}\n\n private getArgs({\n text,\n voice,\n outputFormat = \"wav\",\n // speed,\n // instructions,\n providerOptions,\n }: Parameters<SpeechModelV1[\"doGenerate\"]>[0]) {\n const warnings: SpeechModelV1CallWarning[] = [];\n\n // Parse provider options\n const sarvamOptions = parseProviderOptions({\n provider: \"sarvam\",\n providerOptions: {\n sarvam: {\n ...providerOptions?.sarvam,\n ...this.config.speech,\n },\n },\n schema: SarvamProviderOptionsSchema,\n });\n\n const getSpeaker = (): SarvamSpeechCallOptions[\"speaker\"] => {\n if (sarvamOptions?.speaker) return sarvamOptions.speaker;\n if (voice) {\n return SpeakerSchema.parse(voice);\n }\n\n switch (this.modelId) {\n case \"bulbul:v1\":\n return \"meera\";\n case \"bulbul:v2\":\n return \"manisha\";\n }\n\n return \"meera\";\n };\n\n // Create request body\n const requestBody: Record<string, unknown> = {\n model: this.modelId,\n text: text,\n target_language_code: this.languageCode,\n speaker: getSpeaker(),\n // response_format: \"wav\",\n // speed,\n // instructions,\n };\n\n if (outputFormat) {\n if (\n [\"mp3\", \"opus\", \"aac\", \"flac\", \"wav\", \"pcm\"].includes(\n outputFormat,\n )\n ) {\n requestBody.response_format = outputFormat;\n } else {\n warnings.push({\n type: \"unsupported-setting\",\n setting: \"outputFormat\",\n details: `Unsupported output format: ${outputFormat}. Using mp3 instead.`,\n });\n }\n }\n\n // Add provider-specific options\n if (sarvamOptions) {\n const speechModelOptions: SarvamSpeechAPITypes = {};\n\n for (const key in speechModelOptions) {\n const value =\n speechModelOptions[key as keyof SarvamSpeechAPITypes];\n if (value !== undefined) {\n requestBody[key] = value;\n }\n }\n }\n\n return {\n requestBody,\n warnings,\n };\n }\n\n async doGenerate(\n options: Parameters<SpeechModelV1[\"doGenerate\"]>[0],\n ): Promise<Awaited<ReturnType<SpeechModelV1[\"doGenerate\"]>>> {\n const currentDate =\n this.config._internal?.currentDate?.() ?? new Date();\n const { requestBody, warnings } = this.getArgs(options);\n\n const {\n value,\n responseHeaders,\n rawValue: rawResponse,\n } = await postJsonToApi({\n url: this.config.url({\n path: \"/text-to-speech\",\n modelId: this.modelId,\n }),\n headers: combineHeaders(this.config.headers(), options.headers),\n body: requestBody,\n failedResponseHandler: sarvamFailedResponseHandler,\n successfulResponseHandler: createJsonResponseHandler(\n z.object({\n request_id: z.string(),\n audios: z.array(z.string()),\n }),\n ),\n abortSignal: options.abortSignal,\n fetch: this.config.fetch,\n });\n\n const audio = value.audios[0];\n\n return {\n audio,\n warnings,\n request: {\n body: JSON.stringify(requestBody),\n },\n response: {\n timestamp: currentDate,\n modelId: this.modelId,\n headers: responseHeaders,\n body: rawResponse,\n },\n };\n }\n}\n","import { z } from \"zod\";\n\nexport type SarvamSpeechModelId = \"bulbul:v1\" | \"bulbul:v2\" | (string & {});\n\nexport type SarvamSpeechVoices = z.infer<typeof SpeakerSchema>;\n\nexport const SpeakerSchema = z\n .enum([\n \"meera\",\n \"pavithra\",\n \"maitreyi\",\n \"arvind\",\n \"amol\",\n \"amartya\",\n \"diya\",\n \"neel\",\n \"misha\",\n \"vian\",\n \"arjun\",\n \"maya\",\n \"anushka\",\n \"abhilash\",\n \"manisha\",\n \"vidya\",\n \"arya\",\n \"karun\",\n \"hitesh\",\n ])\n .default(\"meera\");\n\n// https://docs.sarvam.ai/api-reference-docs/text-to-speech/convert\nexport const SarvamProviderOptionsSchema = z\n .object({\n speaker: SpeakerSchema,\n pitch: z.number().min(-0.75).max(0.75).default(0.0),\n pace: z.number().min(0.5).max(2.0).default(1.0),\n loudness: z.number().min(0.3).max(3.0).default(1.0),\n speech_sample_rate: z\n .union([\n z.literal(8000),\n z.literal(16000),\n z.literal(22050),\n z.literal(24000),\n ])\n .default(22050),\n enable_preprocessing: z.boolean().default(false),\n })\n .partial();\n\n/**\n * Configuration settings for Sarvam Text-to-Speech API.\n *\n * This type defines the customizable options for generating speech audio\n * using the Sarvam Text-to-Speech API. Each property corresponds to a specific\n * feature or parameter supported by the API.\n */\nexport type SarvamSpeechSettings = {\n /**\n * The speaker voice to be used for the output audio.\n *\n * @default \"meera\"\n * @example \"meera\" (Default female voice for bulbul:v1)\n * @example \"arvind\" (Male voice for bulbul:v1)\n * @example \"anushka\" (Female voice for bulbul:v2)\n */\n speaker?: SarvamSpeechVoices;\n\n /**\n * Controls the pitch of the audio.\n *\n * @default 0.0\n * @example -0.5 (Deeper voice)\n * @example 0.5 (Sharper voice)\n */\n pitch?: number;\n\n /**\n * Controls the speed of the audio.\n *\n * @default 1.0\n * @example 0.5 (Slower speech)\n * @example 2.0 (Faster speech)\n */\n pace?: number;\n\n /**\n * Controls the loudness of the audio.\n *\n * @default 1.0\n * @example 0.3 (Quieter audio)\n * @example 2.5 (Louder audio)\n */\n loudness?: number;\n\n /**\n * Specifies the sample rate of the output audio.\n *\n * @default 22050\n * @example 8000 (Low-quality audio)\n * @example 24000 (High-quality audio)\n */\n speech_sample_rate?: 8000 | 16000 | 22050 | 24000;\n\n /**\n * Enables preprocessing for normalization of English words and numeric entities\n * (e.g., numbers, dates) in the input text.\n *\n * @default false\n * @example true (Enable preprocessing)\n * @example false (Disable preprocessing)\n */\n enable_preprocessing?: boolean;\n};\n","import {\n TranscriptionModelV1,\n TranscriptionModelV1CallWarning,\n} from \"@ai-sdk/provider\";\nimport {\n combineHeaders,\n createJsonResponseHandler,\n parseProviderOptions,\n postFormDataToApi,\n} from \"@ai-sdk/provider-utils\";\nimport { z } from \"zod\";\nimport { SarvamConfig, SarvamLanguageCode } from \"./sarvam-config\";\nimport { sarvamFailedResponseHandler } from \"./sarvam-error\";\nimport {\n SarvamProviderOptionsSchema,\n SarvamTranscriptionCallOptions,\n SarvamTranscriptionModelId,\n} from \"./sarvam-transcription-settings\";\n\n// https://docs.sarvam.ai/api-reference-docs/speech-to-text/transcribe\ninterface SarvamTranscriptionModelConfig extends SarvamConfig {\n _internal?: {\n currentDate?: () => Date;\n };\n transcription?: SarvamTranscriptionCallOptions;\n}\n\nexport class SarvamTranscriptionModel implements TranscriptionModelV1 {\n readonly specificationVersion = \"v1\";\n\n constructor(\n readonly modelId: SarvamTranscriptionModelId,\n readonly languageCode: SarvamLanguageCode | \"unknown\",\n private readonly config: SarvamTranscriptionModelConfig,\n ) {}\n\n get provider(): string {\n return this.config.provider;\n }\n\n private getArgs({\n audio,\n mediaType,\n providerOptions,\n }: Parameters<TranscriptionModelV1[\"doGenerate\"]>[0]) {\n const warnings: TranscriptionModelV1CallWarning[] = [];\n\n if (this.modelId === \"saarika:v1\" && this.languageCode === \"unknown\")\n throw new Error(\n \"Language code unknown is not supported for model saarika:v1\",\n );\n\n const sarvamOptions = parseProviderOptions({\n provider: \"sarvam\",\n providerOptions: {\n sarvam: {\n ...providerOptions?.sarvam,\n ...this.config.transcription,\n },\n },\n schema: SarvamProviderOptionsSchema,\n });\n\n const formData = new FormData();\n const blob =\n audio instanceof Blob\n ? audio\n : new Blob([audio], { type: mediaType });\n\n formData.append(\"file\", blob);\n formData.append(\"model\", this.modelId);\n if (sarvamOptions) {\n formData.append(\"language_code\", this.languageCode);\n formData.append(\n \"with_timestamps\",\n sarvamOptions.with_timestamps ? \"true\" : \"false\",\n );\n formData.append(\n \"with_diarization\",\n sarvamOptions.with_diarization ? \"true\" : \"false\",\n );\n if (\n sarvamOptions.num_speakers !== null &&\n sarvamOptions.num_speakers !== undefined\n ) {\n formData.append(\n \"num_speakers\",\n sarvamOptions.num_speakers.toString(),\n );\n }\n }\n\n return {\n formData,\n warnings,\n };\n }\n\n async doGenerate(\n options: Parameters<TranscriptionModelV1[\"doGenerate\"]>[0],\n ): Promise<Awaited<ReturnType<TranscriptionModelV1[\"doGenerate\"]>>> {\n const currentDate =\n this.config._internal?.currentDate?.() ?? new Date();\n const { formData, warnings } = this.getArgs(options);\n\n const {\n value: response,\n responseHeaders,\n rawValue: rawResponse,\n } = await postFormDataToApi({\n url: this.config.url({\n path: \"/speech-to-text\",\n modelId: this.modelId,\n }),\n headers: combineHeaders(this.config.headers(), options.headers),\n formData,\n failedResponseHandler: sarvamFailedResponseHandler,\n successfulResponseHandler: