From a70ece7245b3cea07380ec679e97c3f4a9ad2859 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 29 Oct 2026 10:12:18 +0000 Subject: [PATCH] feat: add CloudGPU provider to the embedded catalog Register cloudgpu as an OpenAI-compatible catalog provider at https://cloudgpu.app/v1, discover CLOUDGPU_API_KEY, and default to deepseek-v4.1-flash. Co-authored-by: CloudGPU --- agent-schema.json | 2 +- docs/configuration/models/index.md | 6 +- docs/data/nav.yml | 2 + docs/providers/cloudgpu/index.md | 114 ++++++++++++++++++ docs/providers/overview/index.md | 1 + examples/README.md | 1 + examples/cloudgpu.yaml | 17 +++ pkg/config/auto_test.go | 21 +++- pkg/config/cloudgpu_test.go | 28 +++++ pkg/config/examples_test.go | 1 + pkg/model/provider/catalog/providers.json | 10 ++ pkg/model/provider/catalog_test.go | 1 + .../openai/system_message_merge_test.go | 1 + .../provider/openai_alias_providers_test.go | 8 ++ 14 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 docs/providers/cloudgpu/index.md create mode 100644 examples/cloudgpu.yaml create mode 100644 pkg/config/cloudgpu_test.go diff --git a/agent-schema.json b/agent-schema.json index d75c99b4d..b591b8967 100644 --- a/agent-schema.json +++ b/agent-schema.json @@ -224,7 +224,7 @@ "properties": { "provider": { "type": "string", - "description": "The underlying provider type. Defaults to \"openai\" when not set. Supported values: openai, anthropic, google, amazon-bedrock, dmr, and any built-in alias (requesty, openrouter, opper, runcrate, api-route, lunaroute, orcarouter, atlascloud, azure, xai, ollama, mistral, baseten, daoxe, ovhcloud, groq, fireworks-ai, deepseek, cerebras, togetherai, huggingface, moonshotai, vercel, cloudflare-workers-ai, cloudflare-ai-gateway, nvidia, github-copilot, chatgpt, opencode, opencode-go, etc.).", + "description": "The underlying provider type. Defaults to \"openai\" when not set. Supported values: openai, anthropic, google, amazon-bedrock, dmr, and any built-in alias (requesty, openrouter, opper, runcrate, api-route, lunaroute, orcarouter, atlascloud, cloudgpu, azure, xai, ollama, mistral, baseten, daoxe, ovhcloud, groq, fireworks-ai, deepseek, cerebras, togetherai, huggingface, moonshotai, vercel, cloudflare-workers-ai, cloudflare-ai-gateway, nvidia, github-copilot, chatgpt, opencode, opencode-go, etc.).", "examples": [ "openai", "anthropic", diff --git a/docs/configuration/models/index.md b/docs/configuration/models/index.md index b06c92c92..75b778710 100644 --- a/docs/configuration/models/index.md +++ b/docs/configuration/models/index.md @@ -18,7 +18,7 @@ models: first_available: [list] # Optional: candidate model refs, tried in order by available credentials. # Mutually exclusive with other model settings. provider: string # Required unless using first_available. One of: openai, anthropic, google, amazon-bedrock, - # dmr, mistral, xai, nebius, nvidia, minimax, baseten, daoxe, ovhcloud, groq, fireworks-ai, deepseek, cerebras, togetherai, huggingface, moonshotai, vercel, cloudflare-workers-ai, cloudflare-ai-gateway, requesty, openrouter, opper, runcrate, api-route, lunaroute, orcarouter, atlascloud, + # dmr, mistral, xai, nebius, nvidia, minimax, baseten, daoxe, ovhcloud, groq, fireworks-ai, deepseek, cerebras, togetherai, huggingface, moonshotai, vercel, cloudflare-workers-ai, cloudflare-ai-gateway, requesty, openrouter, opper, runcrate, api-route, lunaroute, orcarouter, atlascloud, cloudgpu, # azure, ollama, github-copilot, or a named provider defined # under the top-level `providers:` section. model: string # Required: model identifier @@ -60,7 +60,7 @@ models: | Property | Type | Required | Description | | --------------------- | ---------- | -------- | ------------------------------------------------------------------------------------- | | `first_available` | array | ✗ | Candidate model references tried in order; selects the first whose credentials are configured. Mutually exclusive with other model settings. | -| `provider` | string | ✓/✗ | Required for regular model definitions; omitted for `first_available` selectors. Provider: `openai`, `anthropic`, `google`, `amazon-bedrock`, `dmr`, `mistral`, `xai`, `nebius`, `nvidia`, `minimax`, `baseten`, `daoxe`, `ovhcloud`, `groq`, `fireworks-ai`, `deepseek`, `cerebras`, `togetherai`, `huggingface`, `moonshotai`, `vercel`, `cloudflare-workers-ai`, `cloudflare-ai-gateway`, `requesty`, `openrouter`, `opper`, `runcrate`, `api-route`, `lunaroute`, `orcarouter`, `atlascloud`, `azure`, `ollama`, `github-copilot`, `chatgpt`, `opencode`, `opencode-go`, or any [named provider](../../providers/custom/index.md). | +| `provider` | string | ✓/✗ | Required for regular model definitions; omitted for `first_available` selectors. Provider: `openai`, `anthropic`, `google`, `amazon-bedrock`, `dmr`, `mistral`, `xai`, `nebius`, `nvidia`, `minimax`, `baseten`, `daoxe`, `ovhcloud`, `groq`, `fireworks-ai`, `deepseek`, `cerebras`, `togetherai`, `huggingface`, `moonshotai`, `vercel`, `cloudflare-workers-ai`, `cloudflare-ai-gateway`, `requesty`, `openrouter`, `opper`, `runcrate`, `api-route`, `lunaroute`, `orcarouter`, `atlascloud`, `cloudgpu`, `azure`, `ollama`, `github-copilot`, `chatgpt`, `opencode`, `opencode-go`, or any [named provider](../../providers/custom/index.md). | | `model` | string | ✓/✗ | Required for regular model definitions; omitted for `first_available` selectors. Model name (e.g., `gpt-4o`, `claude-sonnet-4-5`, `gemini-3.5-flash`) | | `description` | string | ✗ | Informational, human-readable summary of the model's purpose or strengths (e.g., "fast and cheap, good for summaries"). Not sent to the model. Can be combined with `first_available` (a selector's description is kept when it resolves). | | `temperature` | float | ✗ | Sampling randomness. Range is provider-dependent — typically `0.0–2.0` (Anthropic caps at `1.0`). `0.0` is deterministic. | @@ -558,7 +558,7 @@ for a complete local-server configuration. ## Custom HTTP Headers For OpenAI-compatible providers (`openai`, `github-copilot`, `mistral`, `xai`, -`nebius`, `nvidia`, `minimax`, `baseten`, `daoxe`, `ovhcloud`, `groq`, `fireworks-ai`, `deepseek`, `cerebras`, `togetherai`, `huggingface`, `moonshotai`, `vercel`, `cloudflare-workers-ai`, `cloudflare-ai-gateway`, `requesty`, `openrouter`, `opper`, `runcrate`, `lunaroute`, `orcarouter`, `atlascloud`, `ollama`, and any custom provider using the OpenAI API), +`nebius`, `nvidia`, `minimax`, `baseten`, `daoxe`, `ovhcloud`, `groq`, `fireworks-ai`, `deepseek`, `cerebras`, `togetherai`, `huggingface`, `moonshotai`, `vercel`, `cloudflare-workers-ai`, `cloudflare-ai-gateway`, `requesty`, `openrouter`, `opper`, `runcrate`, `lunaroute`, `orcarouter`, `atlascloud`, `cloudgpu`, `ollama`, and any custom provider using the OpenAI API), `provider_opts.http_headers` adds arbitrary HTTP headers to every outgoing request: diff --git a/docs/data/nav.yml b/docs/data/nav.yml index 89d4e527a..b33e8bc6e 100644 --- a/docs/data/nav.yml +++ b/docs/data/nav.yml @@ -191,6 +191,8 @@ url: /providers/cloudflare-workers-ai/ - title: ChatGPT url: /providers/chatgpt/ + - title: CloudGPU + url: /providers/cloudgpu/ - title: DaoXE url: /providers/daoxe/ - title: DeepSeek diff --git a/docs/providers/cloudgpu/index.md b/docs/providers/cloudgpu/index.md new file mode 100644 index 000000000..5e9563e4f --- /dev/null +++ b/docs/providers/cloudgpu/index.md @@ -0,0 +1,114 @@ +--- +title: "CloudGPU" +description: "Use CloudGPU models with Docker Agent." +keywords: docker agent, ai agents, model providers, llm, cloudgpu +weight: 26 +canonical: https://docs.docker.com/ai/docker-agent/providers/cloudgpu/ +--- + +_Use CloudGPU models with Docker Agent._ + +## Overview + +[CloudGPU](https://cloudgpu.app/api/docs) serves DeepSeek, Kimi, GLM and MiniMax +models through an OpenAI-compatible API. Docker Agent includes built-in support +for CloudGPU as an alias provider. Current model IDs are on the live +[model list](https://cloudgpu.app/v1/models). + +## Setup + +1. Create an API key in the [CloudGPU console](https://cloudgpu.app/) +2. Set the environment variable: + + ```bash + export CLOUDGPU_API_KEY=your-api-key + ``` + +## Usage + +### Inline Syntax + +Use a CloudGPU model by prefixing its model ID with `cloudgpu/`: + +```yaml +agents: + root: + model: cloudgpu/deepseek-v4.1-flash + description: Assistant using CloudGPU + instruction: You are a helpful assistant. +``` + +### Named Model + +For more control over parameters: + +```yaml +models: + cloudgpu_kimi: + provider: cloudgpu + model: kimi-k2.6 + temperature: 0.7 + max_tokens: 8192 + +agents: + root: + model: cloudgpu_kimi + description: Assistant using CloudGPU + instruction: You are a helpful assistant. +``` + +When CloudGPU is selected automatically from available credentials, the default +model is `deepseek-v4.1-flash`. + +## Available Models + +Some chat model IDs CloudGPU serves: + +| Model ID | Model | +| --- | --- | +| `deepseek-v4.1-flash` | DeepSeek V4.1 Flash (default) | +| `deepseek-v4-pro` | DeepSeek V4 Pro | +| `kimi-k2.6` | Kimi K2.6 | +| `glm-5.2` | GLM 5.2 | +| `minimax-m3` | MiniMax M3 | + +Model IDs are case-sensitive. The live [`/v1/models` endpoint](https://cloudgpu.app/v1/models) +lists current IDs, and the [API docs](https://cloudgpu.app/api/docs) describe +the endpoints. A listing does not guarantee that inference is currently available. + +CloudGPU is absent from the embedded models.dev catalog. Docker Agent does not +import context limits or attachment capabilities from CloudGPU's model list. +Attachment capabilities fall back to text-only, so image and PDF attachments +are dropped, with a warning logged once per model. + +If you have verified that your chosen model accepts a particular attachment +type, set `models..capabilities` to override detection. See +[Attachment Capability Overrides](../../configuration/models/index.md#attachment-capability-overrides) +for all supported flags. + +## How It Works + +CloudGPU is a built-in alias in Docker Agent: + +- **API type:** OpenAI-compatible Chat Completions (`openai_chatcompletions`) +- **Base URL:** `https://cloudgpu.app/v1` +- **Token variable:** `CLOUDGPU_API_KEY` + +Some models CloudGPU serves require a single leading system message, so Docker +Agent coalesces consecutive system messages into one. + +## Example: Code Assistant + +```yaml +agents: + coder: + model: cloudgpu/deepseek-v4.1-flash + description: Code assistant using CloudGPU + instruction: | + You are an expert programmer. + Write clean code and follow language best practices. + toolsets: + - type: filesystem + - type: shell + - type: think +``` diff --git a/docs/providers/overview/index.md b/docs/providers/overview/index.md index 50e2c15e9..f112c30dc 100644 --- a/docs/providers/overview/index.md +++ b/docs/providers/overview/index.md @@ -73,6 +73,7 @@ Use this table to find a built-in provider's config key and authentication metho | OrcaRouter | `orcarouter` | `ORCAROUTER_API_KEY` | | Atlas Cloud | [`atlascloud`](../atlascloud/index.md) | `ATLASCLOUD_API_KEY` | | Runcrate | [`runcrate`](../runcrate/index.md) | `RUNCRATE_API_KEY` | +| CloudGPU | [`cloudgpu`](../cloudgpu/index.md) | `CLOUDGPU_API_KEY` | | Azure OpenAI | `azure` | `AZURE_API_KEY` + `base_url` (override the key with `token_key`) | | [Ollama](../local/index.md) | `ollama` | None (local; optional `base_url`) | | GitHub Copilot | `github-copilot` | `GITHUB_TOKEN` or `GH_TOKEN` (PAT with `copilot` scope) | diff --git a/examples/README.md b/examples/README.md index d130042a9..8bc5e420b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -223,6 +223,7 @@ remote MCP endpoints. | [`huggingface.yaml`](huggingface.yaml) | Hugging Face Inference Providers open-model router. | | [`moonshot.yaml`](moonshot.yaml) | Moonshot AI Kimi provider (`moonshotai`). | | [`vercel.yaml`](vercel.yaml) | Vercel AI Gateway multi-provider router. | +| [`cloudgpu.yaml`](cloudgpu.yaml) | CloudGPU OpenAI-compatible provider (`cloudgpu`). | | [`cloudflare-workers-ai.yaml`](cloudflare-workers-ai.yaml) | Cloudflare Workers AI edge-hosted open models. | | [`cloudflare-ai-gateway.yaml`](cloudflare-ai-gateway.yaml) | Cloudflare AI Gateway multi-provider router. | | [`grok.yaml`](grok.yaml) | xAI Grok model. | diff --git a/examples/cloudgpu.yaml b/examples/cloudgpu.yaml new file mode 100644 index 000000000..dcced7c05 --- /dev/null +++ b/examples/cloudgpu.yaml @@ -0,0 +1,17 @@ +# yaml-language-server: $schema=../agent-schema.json + +models: + cloudgpu_model: + provider: cloudgpu + model: deepseek-v4.1-flash + +agents: + root: + model: cloudgpu_model + description: Assistant using CloudGPU + instruction: | + You are a helpful assistant. + toolsets: + - type: filesystem + - type: shell + - type: think diff --git a/pkg/config/auto_test.go b/pkg/config/auto_test.go index 2b6a95657..a19cc8f24 100644 --- a/pkg/config/auto_test.go +++ b/pkg/config/auto_test.go @@ -113,6 +113,13 @@ func TestAvailableProviders_NoGateway(t *testing.T) { }, expectedProvider: "runcrate", }, + { + name: "cloudgpu api key present", + envVars: map[string]string{ + "CLOUDGPU_API_KEY": "test-key", + }, + expectedProvider: "cloudgpu", + }, { name: "baseten api key present", envVars: map[string]string{ @@ -379,6 +386,15 @@ func TestAutoModelConfig(t *testing.T) { expectedModel: "deepseek-ai/DeepSeek-V4.1-Flash", expectedMaxTokens: 32000, }, + { + name: "cloudgpu provider", + envVars: map[string]string{ + "CLOUDGPU_API_KEY": "test-key", + }, + expectedProvider: "cloudgpu", + expectedModel: "deepseek-v4.1-flash", + expectedMaxTokens: 32000, + }, { name: "lunaroute provider", envVars: map[string]string{"LUNAROUTE_API_KEY": "lr_test"}, @@ -596,6 +612,7 @@ func TestDefaultModels(t *testing.T) { assert.Equal(t, "meta-llama/llama-4-maverick", DefaultModels["openrouter"]) assert.Equal(t, "orcarouter/auto", DefaultModels["orcarouter"]) assert.Equal(t, "glm-5.3", DefaultModels["lunaroute"]) + assert.Equal(t, "deepseek-v4.1-flash", DefaultModels["cloudgpu"]) assert.Equal(t, "qwen/qwen3.8-max", DefaultModels["atlascloud"]) assert.Equal(t, "deepseek-ai/DeepSeek-V4-Pro-0813", DefaultModels["baseten"]) assert.Equal(t, "Qwen3.5-397B-A17B", DefaultModels["ovhcloud"]) @@ -616,7 +633,7 @@ func TestAutoModelConfig_IntegrationWithDefaultModels(t *testing.T) { t.Parallel() // Verify that AutoModelConfig always returns a model from DefaultModels - providers := []string{"openai", "anthropic", "google", "mistral", "openrouter", "api-route", "orcarouter", "atlascloud", "baseten", "ovhcloud", "groq", "fireworks-ai", "deepseek", "cerebras", "togetherai", "huggingface", "moonshotai", "vercel", "opencode", "github-copilot"} + providers := []string{"openai", "anthropic", "google", "mistral", "openrouter", "api-route", "orcarouter", "atlascloud", "cloudgpu", "baseten", "ovhcloud", "groq", "fireworks-ai", "deepseek", "cerebras", "togetherai", "huggingface", "moonshotai", "vercel", "opencode", "github-copilot"} for _, provider := range providers { t.Run(provider, func(t *testing.T) { @@ -644,6 +661,8 @@ func TestAutoModelConfig_IntegrationWithDefaultModels(t *testing.T) { envVars["ORCAROUTER_API_KEY"] = "test-key" case "atlascloud": envVars["ATLASCLOUD_API_KEY"] = "test-key" + case "cloudgpu": + envVars["CLOUDGPU_API_KEY"] = "test-key" case "baseten": envVars["BASETEN_API_KEY"] = "test-key" case "ovhcloud": diff --git a/pkg/config/cloudgpu_test.go b/pkg/config/cloudgpu_test.go new file mode 100644 index 000000000..c764231fa --- /dev/null +++ b/pkg/config/cloudgpu_test.go @@ -0,0 +1,28 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/docker/docker-agent/pkg/config/latest" + "github.com/docker/docker-agent/pkg/environment" +) + +func TestCloudGPURequiredCredentials(t *testing.T) { + t.Parallel() + + cfg := &latest.Config{ + Agents: []latest.AgentConfig{{Name: "root", Model: "cloudgpu/deepseek-v4.1-flash"}}, + Models: map[string]latest.ModelConfig{ + "cloudgpu/deepseek-v4.1-flash": {Provider: "cloudgpu", Model: "deepseek-v4.1-flash"}, + }, + } + err := CheckRequiredEnvVars(t.Context(), cfg, "", environment.NewNoEnvProvider()) + var required *environment.RequiredEnvError + require.ErrorAs(t, err, &required) + assert.Equal(t, []string{"CLOUDGPU_API_KEY"}, required.Missing) + assert.True(t, required.MissingModelCredentials) + require.NoError(t, CheckRequiredEnvVars(t.Context(), cfg, "", environment.NewMapEnvProvider(map[string]string{"CLOUDGPU_API_KEY": "test-key"}))) +} diff --git a/pkg/config/examples_test.go b/pkg/config/examples_test.go index a495107e6..b1040b5a1 100644 --- a/pkg/config/examples_test.go +++ b/pkg/config/examples_test.go @@ -32,6 +32,7 @@ var modelsDevAbsentProviders = map[string]bool{ "api-route": true, // API Route model IDs come from its own /v1/models endpoint, not models.dev. "lunaroute": true, // Lunaroute model IDs come from its own /v1/models endpoint, not models.dev. "atlascloud": true, // Atlas Cloud model IDs come from its own /v1/models endpoint, not models.dev. + "cloudgpu": true, // CloudGPU model IDs come from its own /v1/models endpoint, not models.dev. "runcrate": true, // Runcrate model IDs come from its own /v1/models endpoint, not models.dev. "dmr": true, // Docker Model Runner (local, not in catalog) "cloudflare-workers-ai": true, // example uses an @cf/... model id not present in the models.dev snapshot (only variant ids like -fp8 are listed) diff --git a/pkg/model/provider/catalog/providers.json b/pkg/model/provider/catalog/providers.json index 27c2ba2b9..c84efa1e8 100644 --- a/pkg/model/provider/catalog/providers.json +++ b/pkg/model/provider/catalog/providers.json @@ -134,6 +134,16 @@ "RUNCRATE_API_KEY" ] }, + { + "name": "cloudgpu", + "merge_consecutive_messages": true, + "api_type": "openai", + "base_url": "https://cloudgpu.app/v1", + "default_model": "deepseek-v4.1-flash", + "token_env_vars": [ + "CLOUDGPU_API_KEY" + ] + }, { "name": "baseten", "merge_consecutive_messages": true, diff --git a/pkg/model/provider/catalog_test.go b/pkg/model/provider/catalog_test.go index 79cfcc400..fe6442236 100644 --- a/pkg/model/provider/catalog_test.go +++ b/pkg/model/provider/catalog_test.go @@ -74,6 +74,7 @@ func TestProviderIDsMatchEmbeddedCatalog(t *testing.T) { absent := map[string]string{ "api-route": "model IDs come from API Route's /v1/models endpoint, not models.dev", "atlascloud": "model IDs come from Atlas Cloud's /v1/models endpoint, not models.dev", + "cloudgpu": "model IDs come from CloudGPU's /v1/models endpoint, not models.dev", "chatgpt": "subscription backend, distinct from OpenAI", "lunaroute": "model IDs come from Lunaroute’s /v1/models endpoint, not models.dev", "runcrate": "model IDs come from Runcrate's /v1/models endpoint, not models.dev", diff --git a/pkg/model/provider/openai/system_message_merge_test.go b/pkg/model/provider/openai/system_message_merge_test.go index bfeca7e7d..2d2e103cb 100644 --- a/pkg/model/provider/openai/system_message_merge_test.go +++ b/pkg/model/provider/openai/system_message_merge_test.go @@ -164,6 +164,7 @@ func TestShouldMergeConsecutiveMessages_Gating(t *testing.T) { {"daoxe, no base_url", &latest.ModelConfig{Provider: "daoxe", Model: "claude-sonnet-4-6"}, true}, {"opper, no base_url", &latest.ModelConfig{Provider: "opper", Model: "claude-sonnet-4-6"}, true}, {"runcrate, no base_url", &latest.ModelConfig{Provider: "runcrate", Model: "deepseek-ai/DeepSeek-V4.1-Flash"}, true}, + {"cloudgpu, no base_url", &latest.ModelConfig{Provider: "cloudgpu", Model: "deepseek-v4.1-flash"}, true}, {"ovhcloud", &latest.ModelConfig{Provider: "ovhcloud", Model: "Qwen3.5-397B-A17B"}, true}, {"open-model host alias cerebras", &latest.ModelConfig{Provider: "cerebras", Model: "qwen-3-coder-480b"}, true}, {"open-model host fireworks", &latest.ModelConfig{Provider: "fireworks", Model: "accounts/fireworks/models/kimi-k3"}, true}, diff --git a/pkg/model/provider/openai_alias_providers_test.go b/pkg/model/provider/openai_alias_providers_test.go index cb01693f7..d54424527 100644 --- a/pkg/model/provider/openai_alias_providers_test.go +++ b/pkg/model/provider/openai_alias_providers_test.go @@ -97,6 +97,14 @@ var openAIAliasProviders = []openAIAliasProvider{ greeting: "Hello from Runcrate", mergesSystemMessages: true, }, + { + provider: "cloudgpu", + envVar: "CLOUDGPU_API_KEY", + testKey: "test-cloudgpu-key", + model: "deepseek-v4.1-flash", + greeting: "Hello from CloudGPU", + mergesSystemMessages: true, + }, { provider: "deepseek", envVar: "DEEPSEEK_API_KEY",