diff --git a/README.md b/README.md
index 8cdcfbea6..204387293 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@
### Core Concepts
- **Agents**: Agents are the main building block of kagent. They are a system prompt, a set of tools and agents, and an LLM configuration represented with a Kubernetes custom resource called "Agent".
-- **LLM Providers**: Kagent supports multiple LLM providers, including [OpenAI](https://kagent.dev/docs/kagent/supported-providers/openai), [Azure OpenAI](https://kagent.dev/docs/kagent/supported-providers/azure-openai), [Anthropic](https://kagent.dev/docs/kagent/supported-providers/anthropic), [Google Vertex AI](https://kagent.dev/docs/kagent/supported-providers/google-vertexai), [Ollama](https://kagent.dev/docs/kagent/supported-providers/ollama) and any other custom providers and models accessible via AI gateways. Providers are represented by the ModelConfig resource.
+- **LLM Providers**: Kagent supports multiple LLM providers, including [OpenAI](https://kagent.dev/docs/kagent/supported-providers/openai), [Azure OpenAI](https://kagent.dev/docs/kagent/supported-providers/azure-openai), [Anthropic](https://kagent.dev/docs/kagent/supported-providers/anthropic), [Google Vertex AI](https://kagent.dev/docs/kagent/supported-providers/google-vertexai), [Ollama](https://kagent.dev/docs/kagent/supported-providers/ollama), [OrcaRouter](https://www.orcarouter.ai) and any other custom providers and models accessible via AI gateways. Providers are represented by the ModelConfig resource.
- **MCP Tools**: Agents can connect to any MCP server that provides tools. Kagent comes with an MCP server with tools for Kubernetes, Istio, Helm, Argo, Prometheus, Grafana, Cilium, and others. All tools are Kubernetes custom resources (ToolServers) and can be used by multiple agents.
- **Observability**: Kagent supports [OpenTelemetry tracing](https://kagent.dev/docs/kagent/getting-started/tracing), which allows you to monitor what's happening with your agents and tools.
diff --git a/docs/architecture/crds-and-types.md b/docs/architecture/crds-and-types.md
index 4ce4165de..0cf6508e4 100644
--- a/docs/architecture/crds-and-types.md
+++ b/docs/architecture/crds-and-types.md
@@ -116,7 +116,7 @@ Configures LLM provider credentials and model parameters.
```
ModelConfigSpec
├── model: string (e.g. "gpt-4o", "claude-sonnet-4-5-20250514")
-├── provider: Anthropic | OpenAI | AzureOpenAI | Ollama | Gemini | GeminiVertexAI | AnthropicVertexAI | Bedrock | SAPAICore | Foundry
+├── provider: Anthropic | OpenAI | AzureOpenAI | Ollama | Gemini | GeminiVertexAI | AnthropicVertexAI | Bedrock | SAPAICore | Foundry | OrcaRouter
├── apiKeySecret: string (Secret name)
├── apiKeySecretKey: string (key within Secret)
├── apiKeyPassthrough: bool (use Bearer token from A2A request)
@@ -146,9 +146,10 @@ ModelConfigSpec
├── bedrock: BedrockConfig
├── sapAICore: SAPAICoreConfig
│ └── baseUrl, resourceGroup, authUrl
-└── foundry: FoundryConfig # Go runtime only
- └── endpoint, endpointFrom, deployment, apiVersion
- └── region
+├── foundry: FoundryConfig # Go runtime only
+│ └── endpoint, endpointFrom, deployment, apiVersion
+└── orcaRouter: OrcaRouterConfig # OpenAI-compatible gateway
+ └── baseUrl, temperature, maxTokens, maxCompletionTokens, topP, reasoningEffort, apiFormat, timeout
```
### Key Validation Rules
diff --git a/go/adk/pkg/agent/agent.go b/go/adk/pkg/agent/agent.go
index 1becb1c00..52335e30f 100644
--- a/go/adk/pkg/agent/agent.go
+++ b/go/adk/pkg/agent/agent.go
@@ -253,6 +253,24 @@ func CreateLLM(ctx context.Context, m adk.Model, log logr.Logger) (adkmodel.LLM,
}
return models.NewOpenAIModelWithLogger(cfg, log)
+ case *adk.OrcaRouter:
+ baseURL := m.BaseUrl
+ if baseURL == "" {
+ baseURL = "https://api.orcarouter.ai/v1"
+ }
+ cfg := &models.OpenAIConfig{
+ TransportConfig: transportConfigFromBase(m.BaseModel, m.Timeout),
+ Model: m.Model,
+ BaseUrl: baseURL,
+ MaxTokens: m.MaxTokens,
+ MaxCompletionTokens: m.MaxCompletionTokens,
+ Temperature: m.Temperature,
+ TopP: m.TopP,
+ ReasoningEffort: m.ReasoningEffort,
+ APIFormat: m.APIFormat,
+ }
+ return models.NewOrcaRouterModelWithLogger(cfg, log)
+
case *adk.AzureOpenAI:
cfg := &models.AzureOpenAIConfig{
TransportConfig: transportConfigFromBase(m.BaseModel, nil),
diff --git a/go/adk/pkg/models/openai.go b/go/adk/pkg/models/openai.go
index 9b532cd50..88d509ac2 100644
--- a/go/adk/pkg/models/openai.go
+++ b/go/adk/pkg/models/openai.go
@@ -69,6 +69,22 @@ func NewOpenAIModelWithLogger(config *OpenAIConfig, logger logr.Logger) (*OpenAI
return newOpenAIModelFromConfig(config, apiKey, logger)
}
+// NewOrcaRouterModelWithLogger creates an OpenAI-compatible model for the
+// OrcaRouter gateway. The wire protocol matches OpenAI chat completions, so it
+// reuses the OpenAI client pointed at the OrcaRouter base URL; the API key is
+// read from ORCAROUTER_API_KEY (or forwarded per-request when APIKeyPassthrough
+// is enabled).
+func NewOrcaRouterModelWithLogger(config *OpenAIConfig, logger logr.Logger) (*OpenAIModel, error) {
+ apiKey := "passthrough" // placeholder; real auth set per-request by transport
+ if !config.APIKeyPassthrough {
+ apiKey = os.Getenv("ORCAROUTER_API_KEY")
+ if apiKey == "" {
+ return nil, fmt.Errorf("ORCAROUTER_API_KEY environment variable is not set")
+ }
+ }
+ return newOpenAIModelFromConfig(config, apiKey, logger)
+}
+
// NewOpenAICompatibleModelWithLogger creates an OpenAI-compatible model (e.g. LiteLLM, Ollama).
// baseURL is the API base (e.g. http://localhost:11434/v1 for Ollama). apiKey is optional; if empty,
// OPENAI_API_KEY is used, then a placeholder for endpoints that do not require a key.
diff --git a/go/api/adk/types.go b/go/api/adk/types.go
index 561c3df96..ea7b0e83a 100644
--- a/go/api/adk/types.go
+++ b/go/api/adk/types.go
@@ -116,6 +116,7 @@ const (
ModelTypeBedrock = "bedrock"
ModelTypeSAPAICore = "sap_ai_core"
ModelTypeFoundry = "foundry"
+ ModelTypeOrcaRouter = "orcarouter"
)
func (o *OpenAI) MarshalJSON() ([]byte, error) {
@@ -359,6 +360,37 @@ func (f *Foundry) GetType() string {
return ModelTypeFoundry
}
+// OrcaRouter is the OrcaRouter OpenAI-compatible gateway model type. The wire
+// format is identical to OpenAI chat completions, so the runtime builds an
+// OpenAI-compatible client pointed at api.orcarouter.ai.
+type OrcaRouter struct {
+ BaseModel
+ BaseUrl string `json:"base_url,omitempty"`
+ MaxTokens *int `json:"max_tokens,omitempty"`
+ MaxCompletionTokens *int `json:"max_completion_tokens,omitempty"`
+ Temperature *float64 `json:"temperature,omitempty"`
+ TopP *float64 `json:"top_p,omitempty"`
+ ReasoningEffort *string `json:"reasoning_effort,omitempty"`
+ Timeout *int `json:"timeout,omitempty"`
+ // APIFormat selects chatCompletions (default) or responses.
+ APIFormat string `json:"api_format,omitempty"`
+}
+
+func (o *OrcaRouter) MarshalJSON() ([]byte, error) {
+ type Alias OrcaRouter
+ return json.Marshal(&struct {
+ Type string `json:"type"`
+ *Alias
+ }{
+ Type: ModelTypeOrcaRouter,
+ Alias: (*Alias)(o),
+ })
+}
+
+func (o *OrcaRouter) GetType() string {
+ return ModelTypeOrcaRouter
+}
+
// GenericModel is a catch-all model type used by the Go ADK when the model
// type doesn't match any known constant.
type GenericModel struct {
@@ -433,6 +465,12 @@ func ParseModel(bytes []byte) (Model, error) {
return nil, err
}
return &foundry, nil
+ case ModelTypeOrcaRouter:
+ var orcaRouter OrcaRouter
+ if err := json.Unmarshal(bytes, &orcaRouter); err != nil {
+ return nil, err
+ }
+ return &orcaRouter, nil
}
return nil, fmt.Errorf("unknown model type: %s", model.Type)
}
diff --git a/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml b/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml
index 0c74c5b93..20db6038d 100644
--- a/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml
+++ b/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml
@@ -791,6 +791,55 @@ spec:
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
+ orcaRouter:
+ description: OrcaRouter-specific configuration
+ properties:
+ apiFormat:
+ default: chatCompletions
+ description: |-
+ APIFormat selects which OpenAI HTTP API the runtime uses for this model.
+ chatCompletions (default) posts to /v1/chat/completions.
+ responses posts to /v1/responses.
+ enum:
+ - chatCompletions
+ - responses
+ type: string
+ baseUrl:
+ description: Base URL for the OrcaRouter API (overrides default
+ https://api.orcarouter.ai/v1)
+ type: string
+ maxCompletionTokens:
+ description: Maximum completion tokens to generate
+ minimum: 1
+ type: integer
+ maxTokens:
+ description: Maximum tokens to generate
+ minimum: 1
+ type: integer
+ reasoningEffort:
+ description: Reasoning effort
+ enum:
+ - none
+ - minimal
+ - low
+ - medium
+ - high
+ - xhigh
+ - max
+ type: string
+ temperature:
+ description: Temperature for sampling
+ type: string
+ timeout:
+ description: Timeout
+ type: integer
+ topP:
+ description: Top-p sampling parameter
+ type: string
+ type: object
+ x-kubernetes-validations:
+ - message: maxTokens and maxCompletionTokens are mutually exclusive
+ rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
provider:
default: OpenAI
description: The provider of the model
@@ -805,6 +854,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
sapAICore:
description: SAP AI Core-specific configuration
@@ -902,6 +952,8 @@ spec:
rule: '!(has(self.sapAICore) && self.provider != ''SAPAICore'')'
- message: provider.foundry must be nil if the provider is not Foundry
rule: '!(has(self.foundry) && self.provider != ''Foundry'')'
+ - message: provider.orcaRouter must be nil if the provider is not OrcaRouter
+ rule: '!(has(self.orcaRouter) && self.provider != ''OrcaRouter'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
@@ -1444,6 +1496,54 @@ spec:
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
+ orcaRouter:
+ description: OrcaRouter-specific configuration
+ properties:
+ apiFormat:
+ default: chatCompletions
+ description: |-
+ APIFormat selects which OpenAI HTTP API the runtime uses for this model.
+ chatCompletions (default) posts to /v1/chat/completions.
+ responses posts to /v1/responses.
+ enum:
+ - chatCompletions
+ - responses
+ type: string
+ baseUrl:
+ description: Base URL for the OrcaRouter API (overrides default
+ https://api.orcarouter.ai/v1)
+ type: string
+ maxCompletionTokens:
+ description: Maximum completion tokens to generate
+ minimum: 1
+ type: integer
+ maxTokens:
+ description: Maximum tokens to generate
+ minimum: 1
+ type: integer
+ reasoningEffort:
+ description: Reasoning effort
+ enum:
+ - none
+ - minimal
+ - low
+ - medium
+ - high
+ - xhigh
+ type: string
+ temperature:
+ description: Temperature for sampling
+ type: string
+ timeout:
+ description: Timeout
+ type: integer
+ topP:
+ description: Top-p sampling parameter
+ type: string
+ type: object
+ x-kubernetes-validations:
+ - message: maxTokens and maxCompletionTokens are mutually exclusive
+ rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
provider:
default: OpenAI
description: The provider of the model
@@ -1458,6 +1558,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
sapAICore:
description: SAP AI Core-specific configuration
@@ -1555,6 +1656,8 @@ spec:
rule: '!(has(self.sapAICore) && self.provider != ''SAPAICore'')'
- message: provider.foundry must be nil if the provider is not Foundry
rule: '!(has(self.foundry) && self.provider != ''Foundry'')'
+ - message: provider.orcaRouter must be nil if the provider is not OrcaRouter
+ rule: '!(has(self.orcaRouter) && self.provider != ''OrcaRouter'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
diff --git a/go/api/config/crd/bases/kagent.dev_modelproviderconfigs.yaml b/go/api/config/crd/bases/kagent.dev_modelproviderconfigs.yaml
index 17679b6aa..fffaec47b 100644
--- a/go/api/config/crd/bases/kagent.dev_modelproviderconfigs.yaml
+++ b/go/api/config/crd/bases/kagent.dev_modelproviderconfigs.yaml
@@ -92,6 +92,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
required:
- type
@@ -272,6 +273,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
required:
- type
diff --git a/go/api/v1alpha2/modelconfig_types.go b/go/api/v1alpha2/modelconfig_types.go
index fa7d3faa0..729325351 100644
--- a/go/api/v1alpha2/modelconfig_types.go
+++ b/go/api/v1alpha2/modelconfig_types.go
@@ -28,7 +28,7 @@ const (
)
// ModelProvider represents the model provider type
-// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock;SAPAICore;Foundry
+// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock;SAPAICore;Foundry;OrcaRouter
type ModelProvider string
const (
@@ -42,6 +42,7 @@ const (
ModelProviderBedrock ModelProvider = "Bedrock"
ModelProviderSAPAICore ModelProvider = "SAPAICore"
ModelProviderFoundry ModelProvider = "Foundry"
+ ModelProviderOrcaRouter ModelProvider = "OrcaRouter"
)
type BaseVertexAIConfig struct {
@@ -432,6 +433,53 @@ type FoundryConfig struct {
APIVersion string `json:"apiVersion,omitempty"`
}
+// OrcaRouterConfig contains OrcaRouter-specific configuration options.
+//
+// OrcaRouter is an OpenAI-compatible AI gateway (https://www.orcarouter.ai) that
+// routes to many upstream models behind a single endpoint. Like the OpenAI
+// provider, it speaks the chat completions wire format; the default base URL is
+// the OrcaRouter gateway endpoint.
+//
+// +kubebuilder:validation:XValidation:message="maxTokens and maxCompletionTokens are mutually exclusive",rule="!(has(self.maxTokens) && has(self.maxCompletionTokens))"
+type OrcaRouterConfig struct {
+ // Base URL for the OrcaRouter API (overrides default https://api.orcarouter.ai/v1)
+ // +optional
+ BaseURL string `json:"baseUrl,omitempty"`
+
+ // Temperature for sampling
+ // +optional
+ Temperature string `json:"temperature,omitempty"`
+
+ // Maximum tokens to generate
+ // +optional
+ // +kubebuilder:validation:Minimum=1
+ MaxTokens int `json:"maxTokens,omitempty"`
+
+ // Maximum completion tokens to generate
+ // +optional
+ // +kubebuilder:validation:Minimum=1
+ MaxCompletionTokens int `json:"maxCompletionTokens,omitempty"`
+
+ // Top-p sampling parameter
+ // +optional
+ TopP string `json:"topP,omitempty"`
+
+ // Reasoning effort
+ // +optional
+ ReasoningEffort *OpenAIReasoningEffort `json:"reasoningEffort,omitempty"`
+
+ // APIFormat selects which OpenAI HTTP API the runtime uses for this model.
+ // chatCompletions (default) posts to /v1/chat/completions.
+ // responses posts to /v1/responses.
+ // +optional
+ // +kubebuilder:default=chatCompletions
+ APIFormat *OpenAIAPIFormat `json:"apiFormat,omitempty"`
+
+ // Timeout
+ // +optional
+ Timeout *int `json:"timeout,omitempty"`
+}
+
// TLSConfig contains TLS/SSL configuration options for outbound HTTPS
// connections from the agent (model provider, RemoteMCPServer). The
// XValidation rules below apply at admission to every CRD field that
@@ -500,6 +548,7 @@ func (t *TLSConfig) IsEmpty() bool {
// +kubebuilder:validation:XValidation:message="provider.bedrock must be nil if the provider is not Bedrock",rule="!(has(self.bedrock) && self.provider != 'Bedrock')"
// +kubebuilder:validation:XValidation:message="provider.sapAICore must be nil if the provider is not SAPAICore",rule="!(has(self.sapAICore) && self.provider != 'SAPAICore')"
// +kubebuilder:validation:XValidation:message="provider.foundry must be nil if the provider is not Foundry",rule="!(has(self.foundry) && self.provider != 'Foundry')"
+// +kubebuilder:validation:XValidation:message="provider.orcaRouter must be nil if the provider is not OrcaRouter",rule="!(has(self.orcaRouter) && self.provider != 'OrcaRouter')"
// +kubebuilder:validation:XValidation:message="apiKeySecret must be set if apiKeySecretKey is set",rule="!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))"
// +kubebuilder:validation:XValidation:message="apiKeySecretKey must be set if apiKeySecret is set (except for Bedrock and SAPAICore providers)",rule="!(has(self.apiKeySecret) && !has(self.apiKeySecretKey) && self.provider != 'Bedrock' && self.provider != 'SAPAICore')"
// +kubebuilder:validation:XValidation:message="apiKeyPassthrough and apiKeySecret are mutually exclusive",rule="!(has(self.apiKeyPassthrough) && self.apiKeyPassthrough && has(self.apiKeySecret) && size(self.apiKeySecret) > 0)"
@@ -577,6 +626,10 @@ type ModelConfigSpec struct {
// +optional
Foundry *FoundryConfig `json:"foundry,omitempty"`
+ // OrcaRouter-specific configuration
+ // +optional
+ OrcaRouter *OrcaRouterConfig `json:"orcaRouter,omitempty"`
+
// TLS configuration for provider connections.
// Enables agents to connect to internal LiteLLM gateways or other providers
// that use self-signed certificates or custom certificate authorities.
diff --git a/go/api/v1alpha2/modelproviderconfig_types.go b/go/api/v1alpha2/modelproviderconfig_types.go
index 907001232..378c025eb 100644
--- a/go/api/v1alpha2/modelproviderconfig_types.go
+++ b/go/api/v1alpha2/modelproviderconfig_types.go
@@ -44,6 +44,8 @@ func DefaultModelProviderEndpoint(providerType ModelProvider) string {
return "https://generativelanguage.googleapis.com"
case ModelProviderOllama:
return "http://localhost:11434"
+ case ModelProviderOrcaRouter:
+ return "https://api.orcarouter.ai/v1"
default:
// Azure, Bedrock, Vertex AI require user-specific endpoints
return ""
diff --git a/go/api/v1alpha2/zz_generated.deepcopy.go b/go/api/v1alpha2/zz_generated.deepcopy.go
index d1220e844..12e70282e 100644
--- a/go/api/v1alpha2/zz_generated.deepcopy.go
+++ b/go/api/v1alpha2/zz_generated.deepcopy.go
@@ -1183,6 +1183,11 @@ func (in *ModelConfigSpec) DeepCopyInto(out *ModelConfigSpec) {
*out = new(FoundryConfig)
(*in).DeepCopyInto(*out)
}
+ if in.OrcaRouter != nil {
+ in, out := &in.OrcaRouter, &out.OrcaRouter
+ *out = new(OrcaRouterConfig)
+ (*in).DeepCopyInto(*out)
+ }
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSConfig)
@@ -1419,6 +1424,36 @@ func (in *OpenAIConfig) DeepCopy() *OpenAIConfig {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OrcaRouterConfig) DeepCopyInto(out *OrcaRouterConfig) {
+ *out = *in
+ if in.ReasoningEffort != nil {
+ in, out := &in.ReasoningEffort, &out.ReasoningEffort
+ *out = new(OpenAIReasoningEffort)
+ **out = **in
+ }
+ if in.APIFormat != nil {
+ in, out := &in.APIFormat, &out.APIFormat
+ *out = new(OpenAIAPIFormat)
+ **out = **in
+ }
+ if in.Timeout != nil {
+ in, out := &in.Timeout, &out.Timeout
+ *out = new(int)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrcaRouterConfig.
+func (in *OrcaRouterConfig) DeepCopy() *OrcaRouterConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(OrcaRouterConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PromptSource) DeepCopyInto(out *PromptSource) {
*out = *in
diff --git a/go/api/v1alpha3/modelconfig_types.go b/go/api/v1alpha3/modelconfig_types.go
index 015575a62..76bd80408 100644
--- a/go/api/v1alpha3/modelconfig_types.go
+++ b/go/api/v1alpha3/modelconfig_types.go
@@ -28,7 +28,7 @@ const (
)
// ModelProvider represents the model provider type
-// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock;SAPAICore;Foundry
+// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock;SAPAICore;Foundry;OrcaRouter
type ModelProvider string
const (
@@ -42,6 +42,7 @@ const (
ModelProviderBedrock ModelProvider = "Bedrock"
ModelProviderSAPAICore ModelProvider = "SAPAICore"
ModelProviderFoundry ModelProvider = "Foundry"
+ ModelProviderOrcaRouter ModelProvider = "OrcaRouter"
)
type BaseVertexAIConfig struct {
@@ -432,6 +433,53 @@ type FoundryConfig struct {
APIVersion string `json:"apiVersion,omitempty"`
}
+// OrcaRouterConfig contains OrcaRouter-specific configuration options.
+//
+// OrcaRouter is an OpenAI-compatible AI gateway (https://www.orcarouter.ai) that
+// routes to many upstream models behind a single endpoint. Like the OpenAI
+// provider, it speaks the chat completions wire format; the default base URL is
+// the OrcaRouter gateway endpoint.
+//
+// +kubebuilder:validation:XValidation:message="maxTokens and maxCompletionTokens are mutually exclusive",rule="!(has(self.maxTokens) && has(self.maxCompletionTokens))"
+type OrcaRouterConfig struct {
+ // Base URL for the OrcaRouter API (overrides default https://api.orcarouter.ai/v1)
+ // +optional
+ BaseURL string `json:"baseUrl,omitempty"`
+
+ // Temperature for sampling
+ // +optional
+ Temperature string `json:"temperature,omitempty"`
+
+ // Maximum tokens to generate
+ // +optional
+ // +kubebuilder:validation:Minimum=1
+ MaxTokens int `json:"maxTokens,omitempty"`
+
+ // Maximum completion tokens to generate
+ // +optional
+ // +kubebuilder:validation:Minimum=1
+ MaxCompletionTokens int `json:"maxCompletionTokens,omitempty"`
+
+ // Top-p sampling parameter
+ // +optional
+ TopP string `json:"topP,omitempty"`
+
+ // Reasoning effort
+ // +optional
+ ReasoningEffort *OpenAIReasoningEffort `json:"reasoningEffort,omitempty"`
+
+ // APIFormat selects which OpenAI HTTP API the runtime uses for this model.
+ // chatCompletions (default) posts to /v1/chat/completions.
+ // responses posts to /v1/responses.
+ // +optional
+ // +kubebuilder:default=chatCompletions
+ APIFormat *OpenAIAPIFormat `json:"apiFormat,omitempty"`
+
+ // Timeout
+ // +optional
+ Timeout *int `json:"timeout,omitempty"`
+}
+
// TLSConfig contains TLS/SSL configuration options for outbound HTTPS
// connections from the agent (model provider, RemoteMCPServer). The
// XValidation rules below apply at admission to every CRD field that
@@ -500,6 +548,7 @@ func (t *TLSConfig) IsEmpty() bool {
// +kubebuilder:validation:XValidation:message="provider.bedrock must be nil if the provider is not Bedrock",rule="!(has(self.bedrock) && self.provider != 'Bedrock')"
// +kubebuilder:validation:XValidation:message="provider.sapAICore must be nil if the provider is not SAPAICore",rule="!(has(self.sapAICore) && self.provider != 'SAPAICore')"
// +kubebuilder:validation:XValidation:message="provider.foundry must be nil if the provider is not Foundry",rule="!(has(self.foundry) && self.provider != 'Foundry')"
+// +kubebuilder:validation:XValidation:message="provider.orcaRouter must be nil if the provider is not OrcaRouter",rule="!(has(self.orcaRouter) && self.provider != 'OrcaRouter')"
// +kubebuilder:validation:XValidation:message="apiKeySecret must be set if apiKeySecretKey is set",rule="!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))"
// +kubebuilder:validation:XValidation:message="apiKeySecretKey must be set if apiKeySecret is set (except for Bedrock and SAPAICore providers)",rule="!(has(self.apiKeySecret) && !has(self.apiKeySecretKey) && self.provider != 'Bedrock' && self.provider != 'SAPAICore')"
// +kubebuilder:validation:XValidation:message="apiKeyPassthrough and apiKeySecret are mutually exclusive",rule="!(has(self.apiKeyPassthrough) && self.apiKeyPassthrough && has(self.apiKeySecret) && size(self.apiKeySecret) > 0)"
@@ -577,6 +626,10 @@ type ModelConfigSpec struct {
// +optional
Foundry *FoundryConfig `json:"foundry,omitempty"`
+ // OrcaRouter-specific configuration
+ // +optional
+ OrcaRouter *OrcaRouterConfig `json:"orcaRouter,omitempty"`
+
// TLS configuration for provider connections.
// Enables agents to connect to internal LiteLLM gateways or other providers
// that use self-signed certificates or custom certificate authorities.
diff --git a/go/api/v1alpha3/modelproviderconfig_types.go b/go/api/v1alpha3/modelproviderconfig_types.go
index 2fdca8586..72bb1f6bf 100644
--- a/go/api/v1alpha3/modelproviderconfig_types.go
+++ b/go/api/v1alpha3/modelproviderconfig_types.go
@@ -44,6 +44,8 @@ func DefaultModelProviderEndpoint(providerType ModelProvider) string {
return "https://generativelanguage.googleapis.com"
case ModelProviderOllama:
return "http://localhost:11434"
+ case ModelProviderOrcaRouter:
+ return "https://api.orcarouter.ai/v1"
default:
// Azure, Bedrock, Vertex AI require user-specific endpoints
return ""
diff --git a/go/api/v1alpha3/zz_generated.deepcopy.go b/go/api/v1alpha3/zz_generated.deepcopy.go
index a42d4f8f6..5d61619c7 100644
--- a/go/api/v1alpha3/zz_generated.deepcopy.go
+++ b/go/api/v1alpha3/zz_generated.deepcopy.go
@@ -1723,6 +1723,11 @@ func (in *ModelConfigSpec) DeepCopyInto(out *ModelConfigSpec) {
*out = new(FoundryConfig)
(*in).DeepCopyInto(*out)
}
+ if in.OrcaRouter != nil {
+ in, out := &in.OrcaRouter, &out.OrcaRouter
+ *out = new(OrcaRouterConfig)
+ (*in).DeepCopyInto(*out)
+ }
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSConfig)
@@ -1959,6 +1964,36 @@ func (in *OpenAIConfig) DeepCopy() *OpenAIConfig {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OrcaRouterConfig) DeepCopyInto(out *OrcaRouterConfig) {
+ *out = *in
+ if in.ReasoningEffort != nil {
+ in, out := &in.ReasoningEffort, &out.ReasoningEffort
+ *out = new(OpenAIReasoningEffort)
+ **out = **in
+ }
+ if in.APIFormat != nil {
+ in, out := &in.APIFormat, &out.APIFormat
+ *out = new(OpenAIAPIFormat)
+ **out = **in
+ }
+ if in.Timeout != nil {
+ in, out := &in.Timeout, &out.Timeout
+ *out = new(int)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrcaRouterConfig.
+func (in *OrcaRouterConfig) DeepCopy() *OrcaRouterConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(OrcaRouterConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PluginBundle) DeepCopyInto(out *PluginBundle) {
*out = *in
diff --git a/go/core/cli/internal/cli/agent/const.go b/go/core/cli/internal/cli/agent/const.go
index 08edd89a5..e3d44ecfe 100644
--- a/go/core/cli/internal/cli/agent/const.go
+++ b/go/core/cli/internal/cli/agent/const.go
@@ -36,6 +36,8 @@ func GetModelProvider() v1alpha3.ModelProvider {
return v1alpha3.ModelProviderAnthropicVertexAI
case GetModelProviderHelmValuesKey(v1alpha3.ModelProviderBedrock):
return v1alpha3.ModelProviderBedrock
+ case GetModelProviderHelmValuesKey(v1alpha3.ModelProviderOrcaRouter):
+ return v1alpha3.ModelProviderOrcaRouter
default:
return v1alpha3.ModelProviderOpenAI
}
@@ -68,6 +70,8 @@ func GetProviderAPIKey(provider v1alpha3.ModelProvider) string {
return env.GoogleAPIKey.Name()
}
return "GEMINI_API_KEY"
+ case v1alpha3.ModelProviderOrcaRouter:
+ return env.OrcaRouterAPIKey.Name()
default:
// Ollama, Bedrock, GeminiVertexAI, AnthropicVertexAI use cloud
// credentials rather than a simple API key, so no check is needed.
diff --git a/go/core/internal/service/model/discovery.go b/go/core/internal/service/model/discovery.go
index ea24bcac5..50bc4ebfc 100644
--- a/go/core/internal/service/model/discovery.go
+++ b/go/core/internal/service/model/discovery.go
@@ -230,8 +230,7 @@ func (s *Service) ListSupportedModels(context.Context) ProviderModels {
},
v1alpha3.ModelProviderSAPAICore: {
// Anthropic (via SAP Generative AI Hub proxy naming)
- {Name: "anthropic--claude-4.7-opus", FunctionCalling: true},
- {Name: "anthropic--claude-4.6-sonnet", FunctionCalling: true},
+ {Name: "anthropic--claude-4.7-opus", FunctionCalling: true}, {Name: "anthropic--claude-4.6-sonnet", FunctionCalling: true},
{Name: "anthropic--claude-4.6-opus", FunctionCalling: true},
{Name: "anthropic--claude-4.5-sonnet", FunctionCalling: true},
{Name: "anthropic--claude-4.5-opus", FunctionCalling: true},
@@ -285,6 +284,18 @@ func (s *Service) ListSupportedModels(context.Context) ProviderModels {
// SAP
{Name: "sap-abap-1", FunctionCalling: false},
},
+ v1alpha3.ModelProviderOrcaRouter: {
+ // OrcaRouter routes to many upstream models behind one endpoint; these
+ // are curated suggestions and the model field accepts any OrcaRouter
+ // model id (full catalog at https://www.orcarouter.ai/models).
+ {Name: "orcarouter/auto", FunctionCalling: true},
+ {Name: "openai/gpt-5.5", FunctionCalling: true},
+ {Name: "anthropic/claude-opus-4.8", FunctionCalling: true},
+ {Name: "google/gemini-3.5-flash", FunctionCalling: true},
+ {Name: "grok/grok-4.3", FunctionCalling: true},
+ {Name: "deepseek/deepseek-v4-pro", FunctionCalling: true},
+ {Name: "qwen/qwen3.7-max", FunctionCalling: true},
+ },
}
}
@@ -303,6 +314,7 @@ func (s *Service) ListSupportedModelProviders(context.Context) []ProviderDefinit
{v1alpha3.ModelProviderAnthropicVertexAI, reflect.TypeFor[v1alpha3.AnthropicVertexAIConfig]()},
{v1alpha3.ModelProviderBedrock, reflect.TypeFor[v1alpha3.BedrockConfig]()},
{v1alpha3.ModelProviderSAPAICore, reflect.TypeFor[v1alpha3.SAPAICoreConfig]()},
+ {v1alpha3.ModelProviderOrcaRouter, reflect.TypeFor[v1alpha3.OrcaRouterConfig]()},
}
providers := []ProviderDefinition{}
@@ -423,7 +435,7 @@ func getRequiredKeysForModelProvider(providerType v1alpha3.ModelProvider) []stri
return []string{"baseUrl"}
case v1alpha3.ModelProviderFoundry:
return []string{"deployment", "endpoint"}
- case v1alpha3.ModelProviderOpenAI, v1alpha3.ModelProviderAnthropic, v1alpha3.ModelProviderOllama:
+ case v1alpha3.ModelProviderOpenAI, v1alpha3.ModelProviderAnthropic, v1alpha3.ModelProviderOllama, v1alpha3.ModelProviderOrcaRouter:
return []string{}
default:
return []string{}
diff --git a/go/core/internal/service/model/discovery_test.go b/go/core/internal/service/model/discovery_test.go
index b80e6af14..d06b4eac7 100644
--- a/go/core/internal/service/model/discovery_test.go
+++ b/go/core/internal/service/model/discovery_test.go
@@ -55,7 +55,7 @@ func TestDiscoverySupportedProviderDefinitions(t *testing.T) {
service := model.NewService(nil, nil, "default")
modelProviders := service.ListSupportedModelProviders(context.Background())
- require.Len(t, modelProviders, 10)
+ require.Len(t, modelProviders, 11)
assert.Equal(t, []string{
"OpenAI",
"Anthropic",
@@ -67,6 +67,7 @@ func TestDiscoverySupportedProviderDefinitions(t *testing.T) {
"AnthropicVertexAI",
"Bedrock",
"SAPAICore",
+ "OrcaRouter",
}, providerNames(modelProviders))
assert.Empty(t, modelProviders[0].RequiredParams)
assert.Equal(t, []string{
@@ -103,12 +104,13 @@ func TestDiscoveryStaticModelCatalog(t *testing.T) {
service := model.NewService(nil, nil, "default")
models := service.ListSupportedModels(context.Background())
- require.Len(t, models, 10)
+ require.Len(t, models, 11)
require.NotEmpty(t, models[v1alpha3.ModelProviderOpenAI])
assert.Equal(t, "gpt-5.6-terra", models[v1alpha3.ModelProviderOpenAI][0].Name)
assert.True(t, models[v1alpha3.ModelProviderOpenAI][0].FunctionCalling)
assert.Equal(t, model.ModelInfo{Name: "deepseek-r1", FunctionCalling: false}, models[v1alpha3.ModelProviderOllama][5])
assert.Equal(t, model.ModelInfo{Name: "us.amazon.nova-2-lite-v1:0", FunctionCalling: false}, models[v1alpha3.ModelProviderBedrock][10])
+ assert.Equal(t, "orcarouter/auto", models[v1alpha3.ModelProviderOrcaRouter][0].Name)
encoded, err := json.Marshal(models[v1alpha3.ModelProviderOpenAI][0])
require.NoError(t, err)
diff --git a/go/core/pkg/env/providers.go b/go/core/pkg/env/providers.go
index cace92159..f2565a2c8 100644
--- a/go/core/pkg/env/providers.go
+++ b/go/core/pkg/env/providers.go
@@ -201,3 +201,13 @@ var (
ComponentAgentRuntime,
)
)
+
+// OrcaRouter
+var (
+ OrcaRouterAPIKey = RegisterStringVar(
+ "ORCAROUTER_API_KEY",
+ "",
+ "API key for OrcaRouter.",
+ ComponentAgentRuntime,
+ )
+)
diff --git a/go/core/v2/translator/kagent/compiler.go b/go/core/v2/translator/kagent/compiler.go
index 5be6d5890..943b2ca61 100644
--- a/go/core/v2/translator/kagent/compiler.go
+++ b/go/core/v2/translator/kagent/compiler.go
@@ -469,6 +469,8 @@ func agentConfigDestinations(cfg *adk.AgentConfig, modelConfig *v1alpha3.ModelCo
destinations = append(destinations, "api.anthropic.com")
case v1alpha3.ModelProviderGemini:
destinations = append(destinations, "generativelanguage.googleapis.com")
+ case v1alpha3.ModelProviderOrcaRouter:
+ destinations = append(destinations, "api.orcarouter.ai")
}
slices.Sort(destinations)
return slices.Compact(destinations)
diff --git a/go/core/v2/translator/kagent/model.go b/go/core/v2/translator/kagent/model.go
index d7f91aa64..031cfb567 100644
--- a/go/core/v2/translator/kagent/model.go
+++ b/go/core/v2/translator/kagent/model.go
@@ -748,6 +748,54 @@ func (c *Compiler) translateModel(ctx context.Context, model *v1alpha3.ModelConf
foundry.APIKeyPassthrough = model.Spec.APIKeyPassthrough
return foundry, modelDeploymentData, nil
+ case v1alpha3.ModelProviderOrcaRouter:
+ if !model.Spec.APIKeyPassthrough && model.Spec.APIKeySecret != "" {
+ modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
+ Name: env.OrcaRouterAPIKey.Name(),
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: model.Spec.APIKeySecret,
+ },
+ Key: model.Spec.APIKeySecretKey,
+ },
+ },
+ })
+ }
+
+ orcaRouter := &adk.OrcaRouter{
+ BaseModel: adk.BaseModel{
+ Model: model.Spec.Model,
+ Headers: model.Spec.DefaultHeaders,
+ },
+ }
+ // Populate TLS fields in BaseModel
+ populateTLSFields(&orcaRouter.BaseModel, model.Spec.TLS)
+ orcaRouter.APIKeyPassthrough = model.Spec.APIKeyPassthrough
+
+ if model.Spec.OrcaRouter != nil {
+ spec := model.Spec.OrcaRouter
+ orcaRouter.BaseUrl = spec.BaseURL
+ orcaRouter.Temperature = utils.ParseStringToFloat64(spec.Temperature)
+ orcaRouter.TopP = utils.ParseStringToFloat64(spec.TopP)
+ if spec.MaxTokens > 0 {
+ orcaRouter.MaxTokens = &spec.MaxTokens
+ }
+ if spec.MaxCompletionTokens > 0 {
+ orcaRouter.MaxCompletionTokens = &spec.MaxCompletionTokens
+ }
+ if spec.ReasoningEffort != nil {
+ effort := string(*spec.ReasoningEffort)
+ orcaRouter.ReasoningEffort = &effort
+ }
+ if spec.APIFormat != nil && *spec.APIFormat != "" {
+ orcaRouter.APIFormat = string(*spec.APIFormat)
+ }
+ if spec.Timeout != nil {
+ orcaRouter.Timeout = spec.Timeout
+ }
+ }
+ return orcaRouter, modelDeploymentData, nil
default:
return nil, nil, fmt.Errorf("unsupported model provider: %s", model.Spec.Provider)
}
diff --git a/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml b/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml
index 0c74c5b93..20db6038d 100644
--- a/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml
+++ b/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml
@@ -791,6 +791,55 @@ spec:
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
+ orcaRouter:
+ description: OrcaRouter-specific configuration
+ properties:
+ apiFormat:
+ default: chatCompletions
+ description: |-
+ APIFormat selects which OpenAI HTTP API the runtime uses for this model.
+ chatCompletions (default) posts to /v1/chat/completions.
+ responses posts to /v1/responses.
+ enum:
+ - chatCompletions
+ - responses
+ type: string
+ baseUrl:
+ description: Base URL for the OrcaRouter API (overrides default
+ https://api.orcarouter.ai/v1)
+ type: string
+ maxCompletionTokens:
+ description: Maximum completion tokens to generate
+ minimum: 1
+ type: integer
+ maxTokens:
+ description: Maximum tokens to generate
+ minimum: 1
+ type: integer
+ reasoningEffort:
+ description: Reasoning effort
+ enum:
+ - none
+ - minimal
+ - low
+ - medium
+ - high
+ - xhigh
+ - max
+ type: string
+ temperature:
+ description: Temperature for sampling
+ type: string
+ timeout:
+ description: Timeout
+ type: integer
+ topP:
+ description: Top-p sampling parameter
+ type: string
+ type: object
+ x-kubernetes-validations:
+ - message: maxTokens and maxCompletionTokens are mutually exclusive
+ rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
provider:
default: OpenAI
description: The provider of the model
@@ -805,6 +854,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
sapAICore:
description: SAP AI Core-specific configuration
@@ -902,6 +952,8 @@ spec:
rule: '!(has(self.sapAICore) && self.provider != ''SAPAICore'')'
- message: provider.foundry must be nil if the provider is not Foundry
rule: '!(has(self.foundry) && self.provider != ''Foundry'')'
+ - message: provider.orcaRouter must be nil if the provider is not OrcaRouter
+ rule: '!(has(self.orcaRouter) && self.provider != ''OrcaRouter'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
@@ -1444,6 +1496,54 @@ spec:
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
+ orcaRouter:
+ description: OrcaRouter-specific configuration
+ properties:
+ apiFormat:
+ default: chatCompletions
+ description: |-
+ APIFormat selects which OpenAI HTTP API the runtime uses for this model.
+ chatCompletions (default) posts to /v1/chat/completions.
+ responses posts to /v1/responses.
+ enum:
+ - chatCompletions
+ - responses
+ type: string
+ baseUrl:
+ description: Base URL for the OrcaRouter API (overrides default
+ https://api.orcarouter.ai/v1)
+ type: string
+ maxCompletionTokens:
+ description: Maximum completion tokens to generate
+ minimum: 1
+ type: integer
+ maxTokens:
+ description: Maximum tokens to generate
+ minimum: 1
+ type: integer
+ reasoningEffort:
+ description: Reasoning effort
+ enum:
+ - none
+ - minimal
+ - low
+ - medium
+ - high
+ - xhigh
+ type: string
+ temperature:
+ description: Temperature for sampling
+ type: string
+ timeout:
+ description: Timeout
+ type: integer
+ topP:
+ description: Top-p sampling parameter
+ type: string
+ type: object
+ x-kubernetes-validations:
+ - message: maxTokens and maxCompletionTokens are mutually exclusive
+ rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
provider:
default: OpenAI
description: The provider of the model
@@ -1458,6 +1558,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
sapAICore:
description: SAP AI Core-specific configuration
@@ -1555,6 +1656,8 @@ spec:
rule: '!(has(self.sapAICore) && self.provider != ''SAPAICore'')'
- message: provider.foundry must be nil if the provider is not Foundry
rule: '!(has(self.foundry) && self.provider != ''Foundry'')'
+ - message: provider.orcaRouter must be nil if the provider is not OrcaRouter
+ rule: '!(has(self.orcaRouter) && self.provider != ''OrcaRouter'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
diff --git a/helm/kagent-crds/templates/kagent.dev_modelproviderconfigs.yaml b/helm/kagent-crds/templates/kagent.dev_modelproviderconfigs.yaml
index 17679b6aa..fffaec47b 100644
--- a/helm/kagent-crds/templates/kagent.dev_modelproviderconfigs.yaml
+++ b/helm/kagent-crds/templates/kagent.dev_modelproviderconfigs.yaml
@@ -92,6 +92,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
required:
- type
@@ -272,6 +273,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
+ - OrcaRouter
type: string
required:
- type
diff --git a/python/packages/kagent-adk/src/kagent/adk/models/_openai.py b/python/packages/kagent-adk/src/kagent/adk/models/_openai.py
index b0cca99d6..adff65bd5 100644
--- a/python/packages/kagent-adk/src/kagent/adk/models/_openai.py
+++ b/python/packages/kagent-adk/src/kagent/adk/models/_openai.py
@@ -580,6 +580,16 @@ class OpenAI(BaseOpenAI):
type: Literal["openai"]
+class OrcaRouterOpenAI(BaseOpenAI):
+ """OrcaRouter gateway model implementation (OpenAI wire-compatible).
+
+ The model type discriminator keeps the serialized config distinct from
+ OpenAI so the controller can inject the OrcaRouter API key and base URL.
+ """
+
+ type: Literal["orcarouter"]
+
+
class AzureOpenAI(BaseOpenAI):
"""Azure OpenAI model implementation."""
diff --git a/python/packages/kagent-adk/src/kagent/adk/types.py b/python/packages/kagent-adk/src/kagent/adk/types.py
index 9d4a25b28..41b941b26 100644
--- a/python/packages/kagent-adk/src/kagent/adk/types.py
+++ b/python/packages/kagent-adk/src/kagent/adk/types.py
@@ -23,6 +23,7 @@
from kagent.adk.models._ollama import create_ollama_llm
from kagent.adk.models._openai import AzureOpenAI as OpenAIAzure
from kagent.adk.models._openai import OpenAI as OpenAINative
+from kagent.adk.models._openai import OrcaRouterOpenAI
from kagent.adk.models._ssl import create_ssl_context
from kagent.adk.tools.ask_user_tool import AskUserTool
@@ -348,7 +349,18 @@ class SAPAICore(BaseLLM):
type: Literal["sap_ai_core"]
-ModelUnion = Union[OpenAI, Anthropic, GeminiVertexAI, GeminiAnthropic, Ollama, AzureOpenAI, Gemini, Bedrock, SAPAICore]
+class OrcaRouter(BaseLLM):
+ base_url: str | None = None
+ max_tokens: int | None = Field(default=None, ge=1)
+ max_completion_tokens: int | None = Field(default=None, ge=1)
+ reasoning_effort: str | None = None
+ temperature: float | None = None
+ timeout: int | None = None
+ top_p: float | None = None
+ type: Literal["orcarouter"]
+
+
+ModelUnion = Union[OpenAI, Anthropic, GeminiVertexAI, GeminiAnthropic, Ollama, AzureOpenAI, Gemini, Bedrock, SAPAICore, OrcaRouter]
class ContextCompressionSettings(BaseModel):
@@ -733,6 +745,21 @@ def _create_llm_from_model_config(model_config: ModelUnion):
auth_url=model_config.auth_url,
**_transport_kwargs(model_config),
)
+ if model_config.type == "orcarouter":
+ # OrcaRouter is OpenAI wire-compatible; default to its gateway base URL.
+ return OrcaRouterOpenAI(
+ type="orcarouter",
+ base_url=base_url or "https://api.orcarouter.ai/v1",
+ default_headers=extra_headers,
+ max_completion_tokens=model_config.max_completion_tokens,
+ max_tokens=model_config.max_tokens,
+ model=model_config.model,
+ reasoning_effort=model_config.reasoning_effort,
+ temperature=model_config.temperature,
+ timeout=model_config.timeout,
+ top_p=model_config.top_p,
+ **_transport_kwargs(model_config),
+ )
raise ValueError(f"Invalid model type: {model_config.type}")
diff --git a/ui/src/api/domain/models.ts b/ui/src/api/domain/models.ts
index 70d8ff240..1e001010a 100644
--- a/ui/src/api/domain/models.ts
+++ b/ui/src/api/domain/models.ts
@@ -79,6 +79,17 @@ export interface SAPAICoreConfig {
authUrl?: string;
}
+export interface OrcaRouterConfig {
+ baseUrl?: string;
+ temperature?: string;
+ maxTokens?: number;
+ maxCompletionTokens?: number;
+ topP?: string;
+ reasoningEffort?: string;
+ apiFormat?: string;
+ timeout?: number;
+}
+
export interface ModelConfigSpec {
model: string;
provider: string;
@@ -96,6 +107,7 @@ export interface ModelConfigSpec {
anthropicVertexAI?: AnthropicVertexAIConfig;
bedrock?: BedrockConfig;
sapAICore?: SAPAICoreConfig;
+ orcaRouter?: OrcaRouterConfig;
}
/** One row of `ModelService.ListModelConfigs`: `ref` is `namespace/name`. */
diff --git a/ui/src/components/model-form/ProviderIcon.tsx b/ui/src/components/model-form/ProviderIcon.tsx
index ceaba9438..6a14efcb0 100644
--- a/ui/src/components/model-form/ProviderIcon.tsx
+++ b/ui/src/components/model-form/ProviderIcon.tsx
@@ -127,6 +127,22 @@ function SAPIcon() {
);
}
+/** OrcaRouter: an orca tail in brand blue. */
+function OrcaRouterIcon() {
+ return (
+
+ );
+}
+
// region Public API
export const PROVIDER_ICONS: Record = {
@@ -139,6 +155,7 @@ export const PROVIDER_ICONS: Record = {
Ollama: OllamaIcon,
Bedrock: BedrockIcon,
SAPAICore: SAPIcon,
+ OrcaRouter: OrcaRouterIcon,
};
/**
diff --git a/ui/src/components/model-form/modelDraft.ts b/ui/src/components/model-form/modelDraft.ts
index 9e989d8c1..6077b81de 100644
--- a/ui/src/components/model-form/modelDraft.ts
+++ b/ui/src/components/model-form/modelDraft.ts
@@ -107,6 +107,7 @@ const PROVIDER_SPEC_KEY: Record = {
AnthropicVertexAI: "anthropicVertexAI",
Bedrock: "bedrock",
SAPAICore: "sapAICore",
+ OrcaRouter: "orcaRouter",
};
/** Parameters the CRD types as numbers rather than strings. */
diff --git a/ui/src/components/model-form/providerInfo.ts b/ui/src/components/model-form/providerInfo.ts
index b3ac22e50..e090705a7 100644
--- a/ui/src/components/model-form/providerInfo.ts
+++ b/ui/src/components/model-form/providerInfo.ts
@@ -65,6 +65,11 @@ export const PROVIDER_INFO: Record = {
modelDocsLink:
"https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/models-and-scenarios-in-generative-ai-hub",
},
+ OrcaRouter: {
+ displayName: "OrcaRouter",
+ apiKeyLink: "https://www.orcarouter.ai/console",
+ modelDocsLink: "https://www.orcarouter.ai/models",
+ },
};
/** The provider's friendly name, or its raw type if unknown. */
@@ -87,6 +92,7 @@ export const PASSTHROUGH_ALLOWED_PROVIDERS = new Set([
"Ollama",
"Bedrock",
"SAPAICore",
+ "OrcaRouter",
]);
export function supportsPassthrough(providerType: string | undefined): boolean {
diff --git a/ui/src/mocks/fixtures.ts b/ui/src/mocks/fixtures.ts
index 0ad0d7807..7966f0ea2 100644
--- a/ui/src/mocks/fixtures.ts
+++ b/ui/src/mocks/fixtures.ts
@@ -283,6 +283,11 @@ export const mockProviderModels: ProviderModelsResponse = {
{ name: "claude-haiku-4", function_calling: true },
],
Ollama: [{ name: "llama3.2", function_calling: false }],
+ OrcaRouter: [
+ { name: "orcarouter/auto", function_calling: true },
+ { name: "openai/gpt-5.5", function_calling: true },
+ { name: "anthropic/claude-opus-4.8", function_calling: true },
+ ],
};
/**
@@ -348,6 +353,21 @@ export const mockProviders: Provider[] = [
requiredParams: ["baseUrl"],
optionalParams: ["resourceGroup", "authUrl"],
},
+ {
+ name: "OrcaRouter",
+ type: "OrcaRouter",
+ requiredParams: [],
+ optionalParams: [
+ "baseUrl",
+ "temperature",
+ "maxTokens",
+ "maxCompletionTokens",
+ "topP",
+ "reasoningEffort",
+ "apiFormat",
+ "timeout",
+ ],
+ },
/*
* One provider an operator added, rather than one the controller ships with.
*