diff --git a/.changeset/nearai-provider.md b/.changeset/nearai-provider.md new file mode 100644 index 000000000..c888032d8 --- /dev/null +++ b/.changeset/nearai-provider.md @@ -0,0 +1,5 @@ +--- +"@voltagent/core": patch +--- + +Add NEAR AI Cloud to the model provider registry. diff --git a/packages/core/src/registries/embedding-model-router-types.generated.ts b/packages/core/src/registries/embedding-model-router-types.generated.ts index 8d9f570fd..a7c3dd188 100644 --- a/packages/core/src/registries/embedding-model-router-types.generated.ts +++ b/packages/core/src/registries/embedding-model-router-types.generated.ts @@ -29,6 +29,7 @@ export type EmbeddingModelsMap = { readonly huggingface: readonly ["Qwen/Qwen3-Embedding-4B", "Qwen/Qwen3-Embedding-8B"]; readonly inference: readonly ["qwen/qwen3-embedding-4b"]; readonly mistral: readonly ["mistral-embed"]; + readonly nearai: readonly ["Qwen/Qwen3-Embedding-0.6B"]; readonly nvidia: readonly ["nvidia/llama-embed-nemotron-8b"]; readonly openai: readonly [ "text-embedding-3-large", diff --git a/packages/core/src/registries/model-provider-registry-nearai.spec.ts b/packages/core/src/registries/model-provider-registry-nearai.spec.ts new file mode 100644 index 000000000..807bfa5ef --- /dev/null +++ b/packages/core/src/registries/model-provider-registry-nearai.spec.ts @@ -0,0 +1,139 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +let createOpenAICompatibleCalls: unknown[][] = []; + +vi.mock("@voltagent/internal", async () => { + const actual = await vi.importActual("@voltagent/internal"); + return { + ...actual, + safeStringify: actual.safeStringify, + }; +}); + +vi.mock("@ai-sdk/openai-compatible", () => ({ + createOpenAICompatible: (...args: unknown[]) => { + createOpenAICompatibleCalls.push(args); + const mockModel = { + modelId: "mock-model", + specificationVersion: "v1", + provider: "nearai", + }; + return { + languageModel: () => mockModel, + chatModel: () => mockModel, + textEmbeddingModel: () => mockModel, + }; + }, +})); + +describe("NEAR AI provider registry", () => { + const originalEnv = { ...process.env }; + + beforeEach(() => { + vi.resetModules(); + createOpenAICompatibleCalls = []; + (globalThis as Record).___voltagent_model_provider_registry = undefined; + process.env = { ...originalEnv, NODE_ENV: "production" }; + }); + + afterEach(() => { + process.env = originalEnv; + (globalThis as Record).___voltagent_model_provider_registry = undefined; + }); + + it("should list nearai as a registered provider", async () => { + const { ModelProviderRegistry } = await import("./model-provider-registry"); + const registry = ModelProviderRegistry.getInstance(); + + expect(registry.listProviders()).toContain("nearai"); + }); + + it("should load nearai through the OpenAI-compatible adapter", async () => { + process.env.NEARAI_API_KEY = "test-nearai-key"; + + const { ModelProviderRegistry } = await import("./model-provider-registry"); + const registry = ModelProviderRegistry.getInstance(); + const model = await registry.resolveLanguageModel("nearai/zai-org/GLM-5.1-FP8"); + + expect(model).toBeDefined(); + expect(createOpenAICompatibleCalls.length).toBeGreaterThan(0); + + const lastCall = createOpenAICompatibleCalls[createOpenAICompatibleCalls.length - 1]; + const config = lastCall[0] as Record; + expect(config.name).toBe("nearai"); + expect(config.baseURL).toBe("https://cloud-api.near.ai/v1"); + expect(config.apiKey).toBe("test-nearai-key"); + expect(config.supportsStructuredOutputs).toBe(false); + expect(config.transformRequestBody).toBeTypeOf("function"); + }); + + it("should support NEARAI_BASE_URL override", async () => { + process.env.NEARAI_API_KEY = "test-nearai-key"; + process.env.NEARAI_BASE_URL = "https://custom.near.ai/v1"; + + const { ModelProviderRegistry } = await import("./model-provider-registry"); + const registry = ModelProviderRegistry.getInstance(); + await registry.resolveLanguageModel("nearai/zai-org/GLM-5.1-FP8"); + + const lastCall = createOpenAICompatibleCalls[createOpenAICompatibleCalls.length - 1]; + const config = lastCall[0] as Record; + expect(config.baseURL).toBe("https://custom.near.ai/v1"); + }); + + it("should sanitize unsupported NEAR AI request fields", async () => { + process.env.NEARAI_API_KEY = "test-nearai-key"; + + const { ModelProviderRegistry } = await import("./model-provider-registry"); + const registry = ModelProviderRegistry.getInstance(); + await registry.resolveLanguageModel("nearai/zai-org/GLM-5.1-FP8"); + + const lastCall = createOpenAICompatibleCalls[createOpenAICompatibleCalls.length - 1]; + const config = lastCall[0] as Record; + const transformRequestBody = config.transformRequestBody as ( + args: Record, + ) => Record; + + const transformed = transformRequestBody({ + messages: [{ role: "developer", content: "Use concise answers." }], + reasoning_effort: "medium", + store: true, + response_format: { + type: "json_schema", + json_schema: { name: "result", strict: true }, + }, + tools: [ + { + type: "function", + function: { name: "lookup", strict: true }, + }, + ], + }); + + expect(transformed).not.toHaveProperty("reasoning_effort"); + expect(transformed).not.toHaveProperty("store"); + expect(transformed.messages).toEqual([{ role: "system", content: "Use concise answers." }]); + expect(transformed.response_format).toEqual({ + type: "json_schema", + json_schema: { name: "result" }, + }); + expect(transformed.tools).toEqual([ + { + type: "function", + function: { name: "lookup" }, + }, + ]); + }); + + it("should throw if NEARAI_API_KEY is not set", async () => { + process.env = Object.fromEntries( + Object.entries(process.env).filter(([key]) => key !== "NEARAI_API_KEY"), + ); + + const { ModelProviderRegistry } = await import("./model-provider-registry"); + const registry = ModelProviderRegistry.getInstance(); + + await expect(registry.resolveLanguageModel("nearai/zai-org/GLM-5.1-FP8")).rejects.toThrow( + /NEARAI_API_KEY/, + ); + }); +}); diff --git a/packages/core/src/registries/model-provider-registry.generated.ts b/packages/core/src/registries/model-provider-registry.generated.ts index e24c73af0..a7ce39ab8 100644 --- a/packages/core/src/registries/model-provider-registry.generated.ts +++ b/packages/core/src/registries/model-provider-registry.generated.ts @@ -374,6 +374,14 @@ export const MODEL_PROVIDER_REGISTRY: Record env: ["NANO_GPT_API_KEY"], doc: "https://docs.nano-gpt.com", }, + nearai: { + id: "nearai", + name: "NEAR AI Cloud", + npm: "@ai-sdk/openai-compatible", + api: "https://cloud-api.near.ai/v1", + env: ["NEARAI_API_KEY"], + doc: "https://docs.near.ai/", + }, nebius: { id: "nebius", name: "Nebius Token Factory", diff --git a/packages/core/src/registries/model-provider-registry.ts b/packages/core/src/registries/model-provider-registry.ts index 0c1f5fd02..b35c67e76 100644 --- a/packages/core/src/registries/model-provider-registry.ts +++ b/packages/core/src/registries/model-provider-registry.ts @@ -523,6 +523,51 @@ const requireApiKey = (config: ModelProviderRegistryEntry): ProviderEnvMatch => return match; }; +const NEAR_AI_PROVIDER_ID = "nearai"; + +const sanitizeNearAIRequestBody = (args: Record): Record => { + const { reasoning_effort: _reasoningEffort, store: _store, ...sanitized } = args; + + if (Array.isArray(sanitized.messages)) { + sanitized.messages = sanitized.messages.map((message) => { + if (!isRecord(message) || message.role !== "developer") { + return message; + } + return { ...message, role: "system" }; + }); + } + + if (Array.isArray(sanitized.tools)) { + sanitized.tools = sanitized.tools.map((tool) => { + if (!isRecord(tool)) { + return tool; + } + + const toolFunction = tool.function; + if (!isRecord(toolFunction) || !("strict" in toolFunction)) { + return tool; + } + + const { strict: _strict, ...sanitizedFunction } = toolFunction; + return { ...tool, function: sanitizedFunction }; + }); + } + + const responseFormat = sanitized.response_format; + if (isRecord(responseFormat)) { + const jsonSchema = responseFormat.json_schema; + if (isRecord(jsonSchema) && "strict" in jsonSchema) { + const { strict: _strict, ...sanitizedJsonSchema } = jsonSchema; + sanitized.response_format = { + ...responseFormat, + json_schema: sanitizedJsonSchema, + }; + } + } + + return sanitized; +}; + const isModelProvider = (value: unknown): value is ModelProvider => Boolean( value && @@ -608,11 +653,13 @@ const buildOpenAICompatibleProvider: ProviderAdapter = (config, moduleExports) = throw new Error(`Missing base URL for "${config.id}". Set ${formatEnvList(config.env ?? [])}.`); } + const isNearAI = normalizeProviderId(config.id) === NEAR_AI_PROVIDER_ID; const provider = createFn({ name: config.id, baseURL, apiKey: apiKeyMatch.value, - supportsStructuredOutputs: true, + supportsStructuredOutputs: !isNearAI, + ...(isNearAI ? { transformRequestBody: sanitizeNearAIRequestBody } : {}), }); return provider as ModelProviderEntry; diff --git a/packages/core/src/registries/model-provider-types.generated.ts b/packages/core/src/registries/model-provider-types.generated.ts index 87f4da8bd..f5b9e72c3 100644 --- a/packages/core/src/registries/model-provider-types.generated.ts +++ b/packages/core/src/registries/model-provider-types.generated.ts @@ -1180,6 +1180,41 @@ export type ProviderModelsMap = { "zai-org/glm-4.7", "zai-org/glm-4.7:thinking", ]; + readonly nearai: readonly [ + "Qwen/Qwen3-30B-A3B-Instruct-2507", + "Qwen/Qwen3-Embedding-0.6B", + "Qwen/Qwen3-Reranker-0.6B", + "Qwen/Qwen3-VL-30B-A3B-Instruct", + "Qwen/Qwen3.5-122B-A10B", + "anthropic/claude-haiku-4-5", + "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", + "anthropic/claude-sonnet-4-5", + "anthropic/claude-sonnet-4-6", + "black-forest-labs/FLUX.2-klein-4B", + "google/gemini-2.5-flash", + "google/gemini-2.5-flash-lite", + "google/gemini-2.5-pro", + "google/gemini-3.1-flash-lite", + "openai/gpt-4.1", + "openai/gpt-4.1-mini", + "openai/gpt-4.1-nano", + "openai/gpt-5", + "openai/gpt-5-mini", + "openai/gpt-5-nano", + "openai/gpt-5.1", + "openai/gpt-5.2", + "openai/gpt-5.4", + "openai/gpt-5.4-mini", + "openai/gpt-5.4-nano", + "openai/gpt-5.5", + "openai/gpt-oss-120b", + "openai/o3", + "openai/o3-mini", + "openai/o4-mini", + "openai/whisper-large-v3", + "zai-org/GLM-5.1-FP8", + ]; readonly nebius: readonly [ "NousResearch/hermes-4-405b", "NousResearch/hermes-4-70b", diff --git a/website/docs/getting-started/providers-models.md b/website/docs/getting-started/providers-models.md index fcc6bf06a..b2176d44a 100644 --- a/website/docs/getting-started/providers-models.md +++ b/website/docs/getting-started/providers-models.md @@ -235,6 +235,7 @@ For providers that follow the OpenAI API specification: | ----------------------------- | ------------------------------------------------------------------------- | ------------------------------ | | **LM Studio** | [Docs](https://ai-sdk.dev/providers/openai-compatible-providers/lmstudio) | Local model execution with GUI | | **Baseten** | [Docs](https://ai-sdk.dev/providers/openai-compatible-providers/baseten) | Model deployment platform | +| **NEAR AI Cloud** | [Docs](https://docs.near.ai/) | TEE-backed inference | | **Any OpenAI-compatible API** | [Docs](https://ai-sdk.dev/providers/openai-compatible-providers) | Custom endpoints | ## Model Capabilities @@ -340,6 +341,9 @@ GOOGLE_GENERATIVE_AI_API_KEY=your-key # Groq GROQ_API_KEY=your-key +# NEAR AI Cloud +NEARAI_API_KEY=your-key + # And so on... ``` diff --git a/website/models-docs/overview.md b/website/models-docs/overview.md index 6d076591d..4493c1876 100644 --- a/website/models-docs/overview.md +++ b/website/models-docs/overview.md @@ -2,7 +2,7 @@ title: Models slug: / sidebar_position: 1 -description: Explore 80+ providers and 2193+ models supported by VoltAgent's model registry. +description: Explore 81+ providers and 2226+ models supported by VoltAgent's model registry. --- @@ -12,7 +12,7 @@ import TabItem from '@theme/TabItem'; # Models -Explore 80+ providers and 2193+ models using VoltAgent's built-in model registry. Use `provider/model` strings for fast routing, or pass an ai-sdk `LanguageModel` when you need provider-specific control. +Explore 81+ providers and 2226+ models using VoltAgent's built-in model registry. Use `provider/model` strings for fast routing, or pass an ai-sdk `LanguageModel` when you need provider-specific control. The registry is generated from [models.dev](https://models.dev) and bundled with VoltAgent. At runtime, VoltAgent checks required environment variables and reports the exact one that's missing. diff --git a/website/models-docs/providers/nearai.md b/website/models-docs/providers/nearai.md new file mode 100644 index 000000000..2a21df1d3 --- /dev/null +++ b/website/models-docs/providers/nearai.md @@ -0,0 +1,82 @@ +--- +title: NEAR AI Cloud +--- + +# NEAR AI Cloud + +Use `nearai/` with VoltAgent's model router. + +NEAR AI Cloud provides OpenAI-compatible TEE inference at `https://cloud-api.near.ai/v1`. + +## Quick start + +```ts +import { Agent } from "@voltagent/core"; + +const agent = new Agent({ + name: "nearai-agent", + instructions: "You are a helpful assistant", + model: "nearai/zai-org/GLM-5.1-FP8", +}); +``` + +## Environment variables + +- `NEARAI_API_KEY` + +## Provider package + +`@ai-sdk/openai-compatible` + +This provider uses the OpenAI-compatible adapter. + +## Default base URL + +`https://cloud-api.near.ai/v1` + +You can override the base URL by setting `NEARAI_BASE_URL`. + +## Provider docs + +- https://docs.near.ai/ + +## Models + +
+ Show models (33) + +- Qwen/Qwen3-30B-A3B-Instruct-2507 +- Qwen/Qwen3-Embedding-0.6B +- Qwen/Qwen3-Reranker-0.6B +- Qwen/Qwen3-VL-30B-A3B-Instruct +- Qwen/Qwen3.5-122B-A10B +- anthropic/claude-haiku-4-5 +- anthropic/claude-opus-4-6 +- anthropic/claude-opus-4-7 +- anthropic/claude-sonnet-4-5 +- anthropic/claude-sonnet-4-6 +- black-forest-labs/FLUX.2-klein-4B +- google/gemini-2.5-flash +- google/gemini-2.5-flash-lite +- google/gemini-2.5-pro +- google/gemini-3.1-flash-lite +- openai/gpt-4.1 +- openai/gpt-4.1-mini +- openai/gpt-4.1-nano +- openai/gpt-5 +- openai/gpt-5-mini +- openai/gpt-5-nano +- openai/gpt-5.1 +- openai/gpt-5.2 +- openai/gpt-5.4 +- openai/gpt-5.4-mini +- openai/gpt-5.4-nano +- openai/gpt-5.5 +- openai/gpt-oss-120b +- openai/o3 +- openai/o3-mini +- openai/o4-mini +- openai/whisper-large-v3 +- zai-org/GLM-5.1-FP8 + +
diff --git a/website/models-docs/providers/overview.md b/website/models-docs/providers/overview.md index e41650993..ce1c9d3bf 100644 --- a/website/models-docs/providers/overview.md +++ b/website/models-docs/providers/overview.md @@ -67,6 +67,7 @@ Each provider page includes: | Moonshot AI (China) | `moonshotai-cn` | `MOONSHOT_API_KEY` | [Docs](https://platform.moonshot.cn/docs/api/chat) | | Morph | `morph` | `MORPH_API_KEY` | [Docs](https://docs.morphllm.com/api-reference/introduction) | | NanoGPT | `nano-gpt` | `NANO_GPT_API_KEY` | [Docs](https://docs.nano-gpt.com) | +| NEAR AI Cloud | `nearai` | `NEARAI_API_KEY` | [Docs](https://docs.near.ai/) | | Nebius Token Factory | `nebius` | `NEBIUS_API_KEY` | [Docs](https://docs.tokenfactory.nebius.com/) | | NovitaAI | `novita-ai` | `NOVITA_API_KEY` | [Docs](https://novita.ai/docs/guides/introduction) | | Nvidia | `nvidia` | `NVIDIA_API_KEY` | [Docs](https://docs.api.nvidia.com/nim/) |