diff --git a/agent-schema.json b/agent-schema.json index eab152f6ab..161f1aa13f 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, api-route, 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, 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.).", "examples": [ "openai", "anthropic", diff --git a/docs/configuration/models/index.md b/docs/configuration/models/index.md index 6b30c46a5e..1efabfbeb0 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, api-route, 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, atlascloud, # 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`, `api-route`, `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`, `atlascloud`, `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`, `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`, `atlascloud`, `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 7a7f1ab145..4ffc6e78fe 100644 --- a/docs/data/nav.yml +++ b/docs/data/nav.yml @@ -233,6 +233,8 @@ url: /providers/openrouter/ - title: OVHcloud url: /providers/ovhcloud/ + - title: Runcrate + url: /providers/runcrate/ - title: Together AI url: /providers/together/ - title: Vercel AI Gateway diff --git a/docs/providers/overview/index.md b/docs/providers/overview/index.md index b9347bf9b7..0ca9d921b6 100644 --- a/docs/providers/overview/index.md +++ b/docs/providers/overview/index.md @@ -70,6 +70,7 @@ Use this table to find a built-in provider's config key and authentication metho | Opper | `opper` | `OPPER_API_KEY` | | API Route | `api-route` | `API_ROUTE_API_KEY` | | Atlas Cloud | [`atlascloud`](../atlascloud/index.md) | `ATLASCLOUD_API_KEY` | +| Runcrate | [`runcrate`](../runcrate/index.md) | `RUNCRATE_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/docs/providers/runcrate/index.md b/docs/providers/runcrate/index.md new file mode 100644 index 0000000000..1431dafd7f --- /dev/null +++ b/docs/providers/runcrate/index.md @@ -0,0 +1,120 @@ +--- +title: "Runcrate" +description: "Use Runcrate models with Docker Agent." +keywords: docker agent, ai agents, model providers, llm, runcrate +weight: 25 +canonical: https://docs.docker.com/ai/docker-agent/providers/runcrate/ +--- + +_Use Runcrate models with Docker Agent._ + +## Overview + +[Runcrate](https://www.runcrate.ai/?utm_source=docker-agent&utm_medium=directory&utm_campaign=gpu_pricing) +is a GPU cloud that also serves open models (DeepSeek, GLM, Qwen, gpt-oss and +others) through an OpenAI-compatible API. Docker Agent includes built-in +support for Runcrate as an alias provider. + +Some open models come with a limited free allowance for GitHub accounts at +least 30 days old. Once the allowance is used up, or when free capacity is +busy, requests from a workspace with credit are billed to that credit. See +[Runcrate's free inference terms](https://www.runcrate.ai/inference/free?utm_source=docker-agent&utm_medium=directory&utm_campaign=gpu_pricing) +for the current models, limits and prices. + +## Setup + +1. [Create a Runcrate API key](https://www.runcrate.ai/dashboard/api-keys) +2. Set the environment variable: + + ```bash + export RUNCRATE_API_KEY=your-api-key + ``` + +## Usage + +### Inline Syntax + +Use a Runcrate model by prefixing its model ID with `runcrate/`: + +```yaml +agents: + root: + model: runcrate/deepseek-ai/DeepSeek-V4.1-Flash + description: Assistant using Runcrate + instruction: You are a helpful assistant. +``` + +Docker Agent splits only the first slash, so model IDs that include an +organization prefix are preserved. + +### Named Model + +For more control over parameters: + +```yaml +models: + runcrate_glm: + provider: runcrate + model: zai-org/GLM-5.3 + temperature: 0.7 + max_tokens: 8192 + +agents: + root: + model: runcrate_glm + description: Assistant using Runcrate + instruction: You are a helpful assistant. +``` + +## Available Models + +Some chat model IDs Runcrate serves: + +| Model ID | Model | +| --- | --- | +| `deepseek-ai/DeepSeek-V4.1-Flash` | DeepSeek V4.1 Flash (default) | +| `zai-org/GLM-5.3` | GLM-5.3 | +| `Qwen/Qwen3.8-Flash` | Qwen3.8 Flash | +| `openai/gpt-oss-120b` | gpt-oss 120B | + +Model IDs are case-sensitive. The live [`/v1/models` endpoint](https://api.runcrate.ai/v1/models) +lists current IDs; it also lists image, video, speech and embedding models, +which this chat provider does not use. + +Runcrate is not in the models.dev catalog. Docker Agent does not import metadata +from Runcrate's model list, so it has no catalog-derived pricing or context limits +for these models. Attachment capabilities conservatively fall back to text-only; +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 + +Runcrate is implemented as a built-in alias in Docker Agent: + +- **API Type:** OpenAI-compatible Chat Completions (`openai_chatcompletions`) +- **Base URL:** `https://api.runcrate.ai/v1` +- **Token Variable:** `RUNCRATE_API_KEY` + +Because Runcrate serves open-weight models whose chat templates can reject +multiple leading system messages, Docker Agent coalesces its per-source system +messages into one. + +## Example: Code Assistant + +```yaml +agents: + coder: + model: runcrate/deepseek-ai/DeepSeek-V4.1-Flash + description: Code assistant using Runcrate + instruction: | + You are an expert programmer. + Write clean code and follow language best practices. + toolsets: + - type: filesystem + - type: shell + - type: think +``` diff --git a/examples/README.md b/examples/README.md index de091668ee..6cb1f843bc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -212,6 +212,7 @@ remote MCP endpoints. | [`daoxe.yaml`](daoxe.yaml) | DaoXE multi-model gateway provider. | | [`opper.yaml`](opper.yaml) | Opper multi-model gateway provider. | | [`ovhcloud.yaml`](ovhcloud.yaml) | OVHcloud AI Endpoints provider. | +| [`runcrate.yaml`](runcrate.yaml) | Runcrate open-model host (`runcrate`). | | [`groq.yaml`](groq.yaml) | Groq fast-inference provider. | | [`fireworks.yaml`](fireworks.yaml) | Fireworks AI open-model inference (`fireworks-ai`). | | [`deepseek.yaml`](deepseek.yaml) | DeepSeek chat and reasoning provider. | diff --git a/examples/runcrate.yaml b/examples/runcrate.yaml new file mode 100644 index 0000000000..21bc0d7e01 --- /dev/null +++ b/examples/runcrate.yaml @@ -0,0 +1,17 @@ +# yaml-language-server: $schema=../agent-schema.json + +models: + runcrate_model: + provider: runcrate + model: deepseek-ai/DeepSeek-V4.1-Flash + +agents: + root: + model: runcrate_model + description: Assistant using Runcrate + instruction: | + You are a helpful assistant. + toolsets: + - type: filesystem + - type: shell + - type: think diff --git a/pkg/config/auto.go b/pkg/config/auto.go index 55744e8369..e0d39c670e 100644 --- a/pkg/config/auto.go +++ b/pkg/config/auto.go @@ -65,6 +65,7 @@ var cloudProviders = []providerConfig{ {"api-route", []string{"API_ROUTE_API_KEY"}, "API_ROUTE_API_KEY", "API_ROUTE_API_KEY"}, {"atlascloud", []string{"ATLASCLOUD_API_KEY"}, "ATLASCLOUD_API_KEY", "ATLASCLOUD_API_KEY"}, {"opper", []string{"OPPER_API_KEY"}, "OPPER_API_KEY", "OPPER_API_KEY"}, + {"runcrate", []string{"RUNCRATE_API_KEY"}, "RUNCRATE_API_KEY", "RUNCRATE_API_KEY"}, {"baseten", []string{"BASETEN_API_KEY"}, "BASETEN_API_KEY", "BASETEN_API_KEY"}, {"ovhcloud", []string{"OVH_AI_ENDPOINTS_ACCESS_TOKEN"}, "OVH_AI_ENDPOINTS_ACCESS_TOKEN", "OVH_AI_ENDPOINTS_ACCESS_TOKEN"}, {"daoxe", []string{"DAOXE_API_KEY"}, "DAOXE_API_KEY", "DAOXE_API_KEY"}, @@ -165,6 +166,7 @@ var DefaultModels = map[string]string{ "api-route": "gpt-6.1-sol", "atlascloud": "qwen/qwen3.8-max", "opper": "claude-sonnet-4-6", + "runcrate": "deepseek-ai/DeepSeek-V4.1-Flash", "baseten": "deepseek-ai/DeepSeek-V4-Pro-0813", "ovhcloud": "Qwen3.5-397B-A17B", "daoxe": "claude-sonnet-4-6", diff --git a/pkg/config/auto_test.go b/pkg/config/auto_test.go index 686aa9546a..73b7ab08d0 100644 --- a/pkg/config/auto_test.go +++ b/pkg/config/auto_test.go @@ -95,6 +95,13 @@ func TestAvailableProviders_NoGateway(t *testing.T) { }, expectedProvider: "opper", }, + { + name: "runcrate api key present", + envVars: map[string]string{ + "RUNCRATE_API_KEY": "test-key", + }, + expectedProvider: "runcrate", + }, { name: "baseten api key present", envVars: map[string]string{ @@ -352,6 +359,15 @@ func TestAutoModelConfig(t *testing.T) { expectedModel: "claude-sonnet-4-6", expectedMaxTokens: 32000, }, + { + name: "runcrate provider", + envVars: map[string]string{ + "RUNCRATE_API_KEY": "test-key", + }, + expectedProvider: "runcrate", + expectedModel: "deepseek-ai/DeepSeek-V4.1-Flash", + expectedMaxTokens: 32000, + }, { name: "baseten provider", envVars: map[string]string{ diff --git a/pkg/config/examples_test.go b/pkg/config/examples_test.go index 82a4f79e53..a3da240bf6 100644 --- a/pkg/config/examples_test.go +++ b/pkg/config/examples_test.go @@ -31,6 +31,7 @@ var modelsDevCatalogProviders = map[string]string{ var modelsDevAbsentProviders = map[string]bool{ "api-route": true, // API Route 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. + "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) "cloudflare-ai-gateway": true, // multi-provider router; example model ids use the gateway's provider/model form, not guaranteed to match a models.dev id diff --git a/pkg/model/provider/aliases.go b/pkg/model/provider/aliases.go index 0c18b3fe63..3da14c4640 100644 --- a/pkg/model/provider/aliases.go +++ b/pkg/model/provider/aliases.go @@ -70,6 +70,11 @@ var Aliases = map[string]Alias{ BaseURL: "https://global.api-route.com/v1", TokenEnvVar: "API_ROUTE_API_KEY", }, + "runcrate": { + APIType: "openai", + BaseURL: "https://api.runcrate.ai/v1", + TokenEnvVar: "RUNCRATE_API_KEY", + }, "atlascloud": { APIType: "openai", BaseURL: "https://api.atlascloud.ai/v1", diff --git a/pkg/model/provider/aliases_test.go b/pkg/model/provider/aliases_test.go index e8e4489a1c..a625da2315 100644 --- a/pkg/model/provider/aliases_test.go +++ b/pkg/model/provider/aliases_test.go @@ -41,6 +41,7 @@ func TestCatalogAliases(t *testing.T) { "openrouter": {APIType: "openai", BaseURL: "https://openrouter.ai/api/v1", TokenEnvVar: "OPENROUTER_API_KEY"}, "api-route": {APIType: "openai", BaseURL: "https://global.api-route.com/v1", TokenEnvVar: "API_ROUTE_API_KEY"}, "atlascloud": {APIType: "openai", BaseURL: "https://api.atlascloud.ai/v1", TokenEnvVar: "ATLASCLOUD_API_KEY"}, + "runcrate": {APIType: "openai", BaseURL: "https://api.runcrate.ai/v1", TokenEnvVar: "RUNCRATE_API_KEY"}, "opper": {APIType: "openai", BaseURL: "https://api.opper.ai/v3/compat", TokenEnvVar: "OPPER_API_KEY"}, "baseten": {APIType: "openai", BaseURL: "https://inference.baseten.co/v1", TokenEnvVar: "BASETEN_API_KEY"}, "daoxe": {APIType: "openai", BaseURL: "https://api.daoxe.com/v1", TokenEnvVar: "DAOXE_API_KEY"}, @@ -132,6 +133,7 @@ func TestProviderIDsMatchEmbeddedCatalog(t *testing.T) { "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", "chatgpt": "subscription backend, distinct from OpenAI", + "runcrate": "model IDs come from Runcrate's /v1/models endpoint, not models.dev", "dmr": "local Docker Model Runner", "ollama": "local Ollama server", } diff --git a/pkg/model/provider/openai/client.go b/pkg/model/provider/openai/client.go index e98c9d69f1..0f9e534691 100644 --- a/pkg/model/provider/openai/client.go +++ b/pkg/model/provider/openai/client.go @@ -282,6 +282,7 @@ var openModelHostProviders = map[string]bool{ "ovhcloud": true, "openrouter": true, "opper": true, + "runcrate": true, "nebius": true, "nvidia": true, "cerebras": true, diff --git a/pkg/model/provider/openai/system_message_merge_test.go b/pkg/model/provider/openai/system_message_merge_test.go index c9e34da517..70dcb43e7a 100644 --- a/pkg/model/provider/openai/system_message_merge_test.go +++ b/pkg/model/provider/openai/system_message_merge_test.go @@ -55,6 +55,16 @@ func TestOpper_MergesConsecutiveSystemMessages(t *testing.T) { }) } +func TestRuncrate_MergesConsecutiveSystemMessages(t *testing.T) { + t.Parallel() + + assertMergesConsecutiveMessages(t, &latest.ModelConfig{ + Provider: "runcrate", + Model: "deepseek-ai/DeepSeek-V4.1-Flash", + TokenKey: "MY_TOKEN", + }) +} + func TestBaseten_MergesConsecutiveSystemMessages(t *testing.T) { t.Parallel() @@ -183,6 +193,7 @@ func TestShouldMergeConsecutiveMessages_Gating(t *testing.T) { {"baseten", &latest.ModelConfig{Provider: "baseten", Model: "zai-org/GLM-5.2"}, true}, {"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}, {"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 e0678b96ed..f5b34d7ca9 100644 --- a/pkg/model/provider/openai_alias_providers_test.go +++ b/pkg/model/provider/openai_alias_providers_test.go @@ -72,6 +72,14 @@ var openAIAliasProviders = []openAIAliasProvider{ greeting: "Hello from Atlas Cloud", mergesSystemMessages: true, }, + { + provider: "runcrate", + envVar: "RUNCRATE_API_KEY", + testKey: "test-runcrate-key", + model: "deepseek-ai/DeepSeek-V4.1-Flash", + greeting: "Hello from Runcrate", + mergesSystemMessages: true, + }, { provider: "deepseek", envVar: "DEEPSEEK_API_KEY",