Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
135 changes: 135 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,135 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

let createOpenAICompatibleCalls: unknown[][] = [];

vi.mock("@voltagent/internal", () => ({
safeStringify: (value: unknown) => JSON.stringify(value),
}));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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