Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nearai-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@voltagent/core": patch
---

Add NEAR AI Cloud to the model provider registry.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
139 changes: 139 additions & 0 deletions packages/core/src/registries/model-provider-registry-nearai.spec.ts
Original file line number Diff line number Diff line change
@@ -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<typeof import("@voltagent/internal")>("@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<string, unknown>).___voltagent_model_provider_registry = undefined;
process.env = { ...originalEnv, NODE_ENV: "production" };
});

afterEach(() => {
process.env = originalEnv;
(globalThis as Record<string, unknown>).___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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
const transformRequestBody = config.transformRequestBody as (
args: Record<string, unknown>,
) => Record<string, unknown>;

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/,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ export const MODEL_PROVIDER_REGISTRY: Record<string, ModelProviderRegistryEntry>
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",
Expand Down
49 changes: 48 additions & 1 deletion packages/core/src/registries/model-provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,51 @@ const requireApiKey = (config: ModelProviderRegistryEntry): ProviderEnvMatch =>
return match;
};

const NEAR_AI_PROVIDER_ID = "nearai";

const sanitizeNearAIRequestBody = (args: Record<string, unknown>): Record<string, unknown> => {
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 &&
Expand Down Expand Up @@ -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;
Expand Down
35 changes: 35 additions & 0 deletions packages/core/src/registries/model-provider-types.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions website/docs/getting-started/providers-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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...
```

Expand Down
4 changes: 2 additions & 2 deletions website/models-docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
---

<!-- THIS FILE IS AUTO-GENERATED BY website/scripts/generate-model-docs.js. DO NOT EDIT MANUALLY. -->
Expand All @@ -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.

Expand Down
82 changes: 82 additions & 0 deletions website/models-docs/providers/nearai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: NEAR AI Cloud
---

# NEAR AI Cloud

Use `nearai/<model>` 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

<details>
<summary>Show models (33)</summary>

- 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

</details>
Loading