From f7965c7d294e7663b719f5b1d4b390f7a40930c1 Mon Sep 17 00:00:00 2001 From: Vaughn Date: Sun, 1 Feb 2026 00:06:36 +0000 Subject: [PATCH] feat(gatewayz): add Gatewayz AI inference provider with extensive model support - Introduce Gatewayz provider plugin enabling unified access to 10,000+ AI models via a single API. - Add Gatewayz models configuration with default models including GPT, Claude, Gemini, LLaMA, DeepSeek, Qwen, Mistral. - Implement API key authentication flow for Gatewayz. - Add detailed documentation for Gatewayz provider usage. - Update labeler and docs routing to include Gatewayz. - Add tests verifying Gatewayz plugin registration and auth. - Extend model auth resolution to support GATEWAYZ_API_KEY environment variable. - Integrate Gatewayz provider into models-config for agent use. This feature enables users to leverage a broad range of AI models through the standard OpenClaw platform interface. Co-authored-by: gatewayz-ai-inbox[bot] --- .github/labeler.yml | 5 + docs/docs.json | 9 ++ docs/providers/gatewayz.md | 74 +++++++++ extensions/gatewayz/index.test.ts | 80 ++++++++++ extensions/gatewayz/index.ts | 209 ++++++++++++++++++++++++++ extensions/gatewayz/package.json | 14 ++ pnpm-lock.yaml | 6 + src/agents/model-auth.test.ts | 24 +++ src/agents/model-auth.ts | 1 + src/agents/models-config.providers.ts | 98 ++++++++++++ 10 files changed, 520 insertions(+) create mode 100644 docs/providers/gatewayz.md create mode 100644 extensions/gatewayz/index.test.ts create mode 100644 extensions/gatewayz/index.ts create mode 100644 extensions/gatewayz/package.json diff --git a/.github/labeler.yml b/.github/labeler.yml index 5c19fa418707f..1f06c1f1a40c1 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -196,6 +196,11 @@ - changed-files: - any-glob-to-any-file: - "extensions/google-gemini-cli-auth/**" +"extensions: gatewayz": + - changed-files: + - any-glob-to-any-file: + - "extensions/gatewayz/**" + - "docs/providers/gatewayz.md" "extensions: llm-task": - changed-files: - any-glob-to-any-file: diff --git a/docs/docs.json b/docs/docs.json index e1bedd8a4330c..986962b7a2552 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -149,6 +149,14 @@ "source": "/zai/", "destination": "/providers/zai" }, + { + "source": "/gatewayz", + "destination": "/providers/gatewayz" + }, + { + "source": "/gatewayz/", + "destination": "/providers/gatewayz" + }, { "source": "/message", "destination": "/cli/message" @@ -1027,6 +1035,7 @@ "providers/minimax", "providers/vercel-ai-gateway", "providers/openrouter", + "providers/gatewayz", "providers/synthetic", "providers/opencode", "providers/glm", diff --git a/docs/providers/gatewayz.md b/docs/providers/gatewayz.md new file mode 100644 index 0000000000000..ad83b34c775df --- /dev/null +++ b/docs/providers/gatewayz.md @@ -0,0 +1,74 @@ +--- +summary: "Use Gatewayz's universal AI inference API to access 10,000+ models in OpenClaw" +read_when: + - You want a single API key for many LLMs + - You want to run models via Gatewayz in OpenClaw +title: "Gatewayz" +--- + +# Gatewayz + +Gatewayz provides a **universal AI inference API** with unified access to over 10,000 AI models +through a single endpoint. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL. + +Supported models include GPT, Claude, Gemini, LLaMA, DeepSeek, Qwen, Mistral, and many more. + +## Quick start + +```bash +openclaw login gatewayz +``` + +This will prompt you for your API key and configure the provider. + +## CLI setup (non-interactive) + +```bash +openclaw onboard --auth-choice apiKey --token-provider gatewayz --token "$GATEWAYZ_API_KEY" +``` + +## Environment variable + +You can also set the API key via environment variable: + +```bash +export GATEWAYZ_API_KEY="your-api-key" +``` + +## Config snippet + +```json5 +{ + env: { GATEWAYZ_API_KEY: "your-api-key" }, + agents: { + defaults: { + model: { primary: "gatewayz/gpt-4o" }, + }, + }, +} +``` + +## Available models + +Gatewayz provides access to 10,000+ models. Some popular ones: + +- `gatewayz/gpt-4o` - OpenAI GPT-4o +- `gatewayz/gpt-4o-mini` - OpenAI GPT-4o Mini +- `gatewayz/claude-3-5-sonnet-20241022` - Anthropic Claude 3.5 Sonnet +- `gatewayz/claude-3-5-haiku-20241022` - Anthropic Claude 3.5 Haiku +- `gatewayz/gemini-1.5-pro` - Google Gemini 1.5 Pro +- `gatewayz/gemini-1.5-flash` - Google Gemini 1.5 Flash +- `gatewayz/deepseek-chat` - DeepSeek Chat +- `gatewayz/deepseek-reasoner` - DeepSeek Reasoner +- `gatewayz/llama-3.1-70b-instruct` - Meta LLaMA 3.1 70B +- `gatewayz/qwen-2.5-72b-instruct` - Qwen 2.5 72B +- `gatewayz/mistral-large-latest` - Mistral Large + +For the full model catalog, see [docs.gatewayz.ai](https://docs.gatewayz.ai). + +## Notes + +- Model refs are `gatewayz/`. +- Get your API key from [gatewayz.ai](https://gatewayz.ai). +- Gatewayz uses a Bearer token with your API key under the hood. +- For more model/provider options, see [/concepts/model-providers](/concepts/model-providers). diff --git a/extensions/gatewayz/index.test.ts b/extensions/gatewayz/index.test.ts new file mode 100644 index 0000000000000..ec8e1d6616056 --- /dev/null +++ b/extensions/gatewayz/index.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vitest"; + +import gatewayzPlugin from "./index.js"; + +describe("gatewayz plugin", () => { + it("has correct plugin metadata", () => { + expect(gatewayzPlugin.id).toBe("gatewayz"); + expect(gatewayzPlugin.name).toBe("Gatewayz"); + expect(gatewayzPlugin.description).toContain("Universal AI inference"); + }); + + it("registers provider with correct id and label", () => { + let registeredProvider: unknown = null; + + const mockApi = { + registerProvider: (provider: unknown) => { + registeredProvider = provider; + }, + }; + + gatewayzPlugin.register(mockApi as never); + + expect(registeredProvider).not.toBeNull(); + const provider = registeredProvider as { + id: string; + label: string; + aliases: string[]; + envVars: string[]; + models: { baseUrl: string; api: string; models: unknown[] }; + auth: { id: string; kind: string }[]; + }; + expect(provider.id).toBe("gatewayz"); + expect(provider.label).toBe("Gatewayz"); + expect(provider.aliases).toContain("gateway"); + expect(provider.envVars).toContain("GATEWAYZ_API_KEY"); + }); + + it("registers provider with api_key auth method", () => { + let registeredProvider: unknown = null; + + const mockApi = { + registerProvider: (provider: unknown) => { + registeredProvider = provider; + }, + }; + + gatewayzPlugin.register(mockApi as never); + + const provider = registeredProvider as { + auth: { id: string; kind: string; label: string }[]; + }; + expect(provider.auth).toHaveLength(1); + expect(provider.auth[0].id).toBe("api_key"); + expect(provider.auth[0].kind).toBe("api_key"); + }); + + it("registers provider with OpenAI-compatible models config", () => { + let registeredProvider: unknown = null; + + const mockApi = { + registerProvider: (provider: unknown) => { + registeredProvider = provider; + }, + }; + + gatewayzPlugin.register(mockApi as never); + + const provider = registeredProvider as { + models: { + baseUrl: string; + api: string; + models: { id: string; name: string }[]; + }; + }; + expect(provider.models.baseUrl).toBe("https://api.gatewayz.ai/v1"); + expect(provider.models.api).toBe("openai-completions"); + expect(provider.models.models.length).toBeGreaterThan(0); + expect(provider.models.models.some((m) => m.id === "gpt-4o")).toBe(true); + }); +}); diff --git a/extensions/gatewayz/index.ts b/extensions/gatewayz/index.ts new file mode 100644 index 0000000000000..00c150eac7e7a --- /dev/null +++ b/extensions/gatewayz/index.ts @@ -0,0 +1,209 @@ +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; + +const PROVIDER_ID = "gatewayz"; +const PROVIDER_LABEL = "Gatewayz"; +const DEFAULT_BASE_URL = "https://api.gatewayz.ai/v1"; +const DEFAULT_CONTEXT_WINDOW = 128000; +const DEFAULT_MAX_TOKENS = 8192; +const DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +function buildModelDefinition(params: { + id: string; + name: string; + reasoning?: boolean; + input?: Array<"text" | "image">; + contextWindow?: number; + maxTokens?: number; +}) { + return { + id: params.id, + name: params.name, + reasoning: params.reasoning ?? false, + input: params.input ?? ["text"], + cost: DEFAULT_COST, + contextWindow: params.contextWindow ?? DEFAULT_CONTEXT_WINDOW, + maxTokens: params.maxTokens ?? DEFAULT_MAX_TOKENS, + }; +} + +// Default models available through Gatewayz +// Users can access 10,000+ models via Gatewayz's unified API +const DEFAULT_MODELS = [ + buildModelDefinition({ + id: "gpt-4o", + name: "GPT-4o", + input: ["text", "image"], + }), + buildModelDefinition({ + id: "gpt-4o-mini", + name: "GPT-4o Mini", + input: ["text", "image"], + }), + buildModelDefinition({ + id: "claude-3-5-sonnet-20241022", + name: "Claude 3.5 Sonnet", + input: ["text", "image"], + contextWindow: 200000, + }), + buildModelDefinition({ + id: "claude-3-5-haiku-20241022", + name: "Claude 3.5 Haiku", + input: ["text", "image"], + contextWindow: 200000, + }), + buildModelDefinition({ + id: "gemini-1.5-pro", + name: "Gemini 1.5 Pro", + input: ["text", "image"], + contextWindow: 1000000, + }), + buildModelDefinition({ + id: "gemini-1.5-flash", + name: "Gemini 1.5 Flash", + input: ["text", "image"], + contextWindow: 1000000, + }), + buildModelDefinition({ + id: "llama-3.1-70b-instruct", + name: "Llama 3.1 70B Instruct", + }), + buildModelDefinition({ + id: "llama-3.1-8b-instruct", + name: "Llama 3.1 8B Instruct", + }), + buildModelDefinition({ + id: "deepseek-chat", + name: "DeepSeek Chat", + contextWindow: 64000, + }), + buildModelDefinition({ + id: "deepseek-reasoner", + name: "DeepSeek Reasoner", + reasoning: true, + contextWindow: 64000, + }), + buildModelDefinition({ + id: "qwen-2.5-72b-instruct", + name: "Qwen 2.5 72B Instruct", + contextWindow: 131072, + }), + buildModelDefinition({ + id: "mistral-large-latest", + name: "Mistral Large", + contextWindow: 128000, + }), +]; + +const gatewayzPlugin = { + id: "gatewayz", + name: "Gatewayz", + description: "Universal AI inference provider with access to 10,000+ models", + configSchema: emptyPluginConfigSchema(), + register(api) { + api.registerProvider({ + id: PROVIDER_ID, + label: PROVIDER_LABEL, + docsPath: "/providers/gatewayz", + aliases: ["gateway", "gatewayz.ai"], + envVars: ["GATEWAYZ_API_KEY"], + models: { + baseUrl: DEFAULT_BASE_URL, + api: "openai-completions", + models: DEFAULT_MODELS, + }, + auth: [ + { + id: "api_key", + label: "API Key", + hint: "Get your API key from gatewayz.ai", + kind: "api_key", + run: async (ctx) => { + const progress = ctx.prompter.progress("Setting up Gatewayz..."); + try { + await ctx.prompter.note( + [ + "Gatewayz provides unified access to 10,000+ AI models.", + "", + "Get your API key from: https://gatewayz.ai", + "", + "Your API key will be stored securely in your auth profile.", + ].join("\n"), + "Gatewayz Setup", + ); + + const apiKey = await ctx.prompter.text({ + message: "Enter your Gatewayz API key:", + validate: (value) => { + if (!value?.trim()) { + return "API key is required"; + } + return undefined; + }, + }); + + if (!apiKey || typeof apiKey !== "string") { + throw new Error("API key is required"); + } + + const trimmedKey = apiKey.trim(); + const profileId = `${PROVIDER_ID}:default`; + + progress.stop("Gatewayz setup complete"); + + return { + profiles: [ + { + profileId, + credential: { + type: "api_key", + provider: PROVIDER_ID, + key: trimmedKey, + }, + }, + ], + configPatch: { + models: { + providers: { + [PROVIDER_ID]: { + baseUrl: DEFAULT_BASE_URL, + apiKey: trimmedKey, + api: "openai-completions", + models: DEFAULT_MODELS, + }, + }, + }, + agents: { + defaults: { + models: { + "gatewayz/gpt-4o": { alias: "gatewayz" }, + "gatewayz/claude-3-5-sonnet-20241022": { alias: "gatewayz-claude" }, + "gatewayz/gemini-1.5-pro": { alias: "gatewayz-gemini" }, + }, + }, + }, + }, + defaultModel: "gatewayz/gpt-4o", + notes: [ + "Gatewayz provides access to 10,000+ AI models through a unified API.", + "You can use any model supported by Gatewayz by specifying its ID.", + "See https://docs.gatewayz.ai for the full model catalog.", + `Models are accessed as: gatewayz/ (e.g., gatewayz/gpt-4o)`, + ], + }; + } catch (err) { + progress.stop("Gatewayz setup failed"); + throw err; + } + }, + }, + ], + }); + }, +}; + +export default gatewayzPlugin; diff --git a/extensions/gatewayz/package.json b/extensions/gatewayz/package.json new file mode 100644 index 0000000000000..d4152c0068b64 --- /dev/null +++ b/extensions/gatewayz/package.json @@ -0,0 +1,14 @@ +{ + "name": "@openclaw/gatewayz", + "version": "2026.1.30", + "description": "OpenClaw Gatewayz provider plugin for universal AI inference", + "type": "module", + "devDependencies": { + "openclaw": "workspace:*" + }, + "openclaw": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d97cbb1174ff2..56a43cd7fd78e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -288,6 +288,12 @@ importers: specifier: workspace:* version: link:../.. + extensions/gatewayz: + devDependencies: + openclaw: + specifier: workspace:* + version: link:../.. + extensions/google-antigravity-auth: devDependencies: openclaw: diff --git a/src/agents/model-auth.test.ts b/src/agents/model-auth.test.ts index d5ce433490a0c..42b539b17ff42 100644 --- a/src/agents/model-auth.test.ts +++ b/src/agents/model-auth.test.ts @@ -281,6 +281,30 @@ describe("getApiKeyForModel", () => { } }); + it("resolves Gatewayz API key from env", async () => { + const previousGatewayzKey = process.env.GATEWAYZ_API_KEY; + + try { + process.env.GATEWAYZ_API_KEY = "gatewayz-test-key"; + + vi.resetModules(); + const { resolveApiKeyForProvider } = await import("./model-auth.js"); + + const resolved = await resolveApiKeyForProvider({ + provider: "gatewayz", + store: { version: 1, profiles: {} }, + }); + expect(resolved.apiKey).toBe("gatewayz-test-key"); + expect(resolved.source).toContain("GATEWAYZ_API_KEY"); + } finally { + if (previousGatewayzKey === undefined) { + delete process.env.GATEWAYZ_API_KEY; + } else { + process.env.GATEWAYZ_API_KEY = previousGatewayzKey; + } + } + }); + it("prefers Bedrock bearer token over access keys and profile", async () => { const previous = { bearer: process.env.AWS_BEARER_TOKEN_BEDROCK, diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index e55cdd127ffd6..09b8f2f714697 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -301,6 +301,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", + gatewayz: "GATEWAYZ_API_KEY", }; const envVar = envMap[normalized]; if (!envVar) { diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 471c477652842..1322ad543bc31 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -76,6 +76,16 @@ const OLLAMA_DEFAULT_COST = { cacheWrite: 0, }; +const GATEWAYZ_BASE_URL = "https://api.gatewayz.ai/v1"; +const GATEWAYZ_DEFAULT_CONTEXT_WINDOW = 128000; +const GATEWAYZ_DEFAULT_MAX_TOKENS = 8192; +const GATEWAYZ_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + interface OllamaModel { name: string; modified_at: string; @@ -394,6 +404,87 @@ async function buildOllamaProvider(): Promise { }; } +function buildGatewayzProvider(): ProviderConfig { + return { + baseUrl: GATEWAYZ_BASE_URL, + api: "openai-completions", + models: [ + { + id: "gpt-4o", + name: "GPT-4o", + reasoning: false, + input: ["text", "image"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: GATEWAYZ_DEFAULT_CONTEXT_WINDOW, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "gpt-4o-mini", + name: "GPT-4o Mini", + reasoning: false, + input: ["text", "image"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: GATEWAYZ_DEFAULT_CONTEXT_WINDOW, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "claude-3-5-sonnet-20241022", + name: "Claude 3.5 Sonnet", + reasoning: false, + input: ["text", "image"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: 200000, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "claude-3-5-haiku-20241022", + name: "Claude 3.5 Haiku", + reasoning: false, + input: ["text", "image"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: 200000, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "gemini-1.5-pro", + name: "Gemini 1.5 Pro", + reasoning: false, + input: ["text", "image"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: 1000000, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "gemini-1.5-flash", + name: "Gemini 1.5 Flash", + reasoning: false, + input: ["text", "image"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: 1000000, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "deepseek-chat", + name: "DeepSeek Chat", + reasoning: false, + input: ["text"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: 64000, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + { + id: "deepseek-reasoner", + name: "DeepSeek Reasoner", + reasoning: true, + input: ["text"], + cost: GATEWAYZ_DEFAULT_COST, + contextWindow: 64000, + maxTokens: GATEWAYZ_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + export async function resolveImplicitProviders(params: { agentDir: string; }): Promise { @@ -461,6 +552,13 @@ export async function resolveImplicitProviders(params: { providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey }; } + const gatewayzKey = + resolveEnvApiKeyVarName("gatewayz") ?? + resolveApiKeyFromProfiles({ provider: "gatewayz", store: authStore }); + if (gatewayzKey) { + providers.gatewayz = { ...buildGatewayzProvider(), apiKey: gatewayzKey }; + } + return providers; }