Skip to content

Commit fe980d2

Browse files
committed
feat: support any OpenAI-compatible provider (GitHub Copilot, Azure, Ollama, etc.)
1 parent d9cf3fd commit fe980d2

8 files changed

Lines changed: 1032 additions & 498 deletions

File tree

api/v1alpha1/agentrun_types.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,22 @@ type ParentRunRef struct {
6666

6767
// ModelSpec defines which LLM to use.
6868
type ModelSpec struct {
69-
// Provider is the AI provider (anthropic, openai, etc.).
69+
// Provider is the AI provider (openai, anthropic, azure-openai, github-copilot, ollama, etc.).
7070
Provider string `json:"provider"`
7171

7272
// Model is the model identifier.
7373
Model string `json:"model"`
7474

75+
// BaseURL overrides the provider's default API endpoint.
76+
// Use this for OpenAI-compatible providers (GitHub Copilot, Azure OpenAI,
77+
// Ollama, vLLM, LMStudio, etc.).
78+
// Examples:
79+
// GitHub Copilot: https://api.githubcopilot.com
80+
// Azure OpenAI: https://<resource>.openai.azure.com/openai/deployments/<deployment>
81+
// Ollama: http://ollama.default.svc:11434/v1
82+
// +optional
83+
BaseURL string `json:"baseURL,omitempty"`
84+
7585
// Thinking mode (off, low, medium, high).
7686
// +optional
7787
Thinking string `json:"thinking,omitempty"`

api/v1alpha1/clawinstance_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ type AgentConfig struct {
4949
// Model is the LLM model to use.
5050
Model string `json:"model"`
5151

52+
// BaseURL overrides the provider's default API endpoint.
53+
// Use for OpenAI-compatible providers (GitHub Copilot, Azure OpenAI, Ollama, etc.).
54+
// +optional
55+
BaseURL string `json:"baseURL,omitempty"`
56+
5257
// Thinking is the thinking mode (off, low, medium, high).
5358
// +optional
5459
Thinking string `json:"thinking,omitempty"`

cmd/k8sclaw/main.go

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -453,30 +453,55 @@ func runOnboard() error {
453453
fmt.Println(" Which model provider do you want to use?")
454454
fmt.Println(" 1) OpenAI")
455455
fmt.Println(" 2) Anthropic")
456-
fmt.Println(" 3) Other / bring-your-own")
457-
providerChoice := prompt(reader, " Choice [1-3]", "1")
456+
fmt.Println(" 3) GitHub Copilot (uses your Copilot subscription)")
457+
fmt.Println(" 4) Azure OpenAI")
458+
fmt.Println(" 5) Ollama (local, no API key needed)")
459+
fmt.Println(" 6) Other / OpenAI-compatible")
460+
providerChoice := prompt(reader, " Choice [1-6]", "1")
458461

459-
var providerName, secretEnvKey, modelName string
462+
var providerName, secretEnvKey, modelName, baseURL string
460463
switch providerChoice {
461464
case "2":
462465
providerName = "anthropic"
463466
secretEnvKey = "ANTHROPIC_API_KEY"
464467
modelName = prompt(reader, " Model name", "claude-sonnet-4-20250514")
465468
case "3":
469+
providerName = "github-copilot"
470+
secretEnvKey = "GITHUB_TOKEN"
471+
baseURL = "https://api.githubcopilot.com"
472+
modelName = prompt(reader, " Model name", "gpt-4o")
473+
fmt.Println("\n 💡 Use a GitHub PAT with the 'copilot' scope.")
474+
fmt.Println(" Create one at: https://github.com/settings/tokens")
475+
case "4":
476+
providerName = "azure-openai"
477+
secretEnvKey = "AZURE_OPENAI_API_KEY"
478+
baseURL = prompt(reader, " Azure OpenAI endpoint URL", "")
479+
modelName = prompt(reader, " Deployment name", "gpt-4o")
480+
case "5":
481+
providerName = "ollama"
482+
secretEnvKey = ""
483+
baseURL = prompt(reader, " Ollama URL", "http://ollama.default.svc:11434/v1")
484+
modelName = prompt(reader, " Model name", "llama3")
485+
fmt.Println(" 💡 No API key needed for Ollama.")
486+
case "6":
466487
providerName = prompt(reader, " Provider name", "custom")
467-
secretEnvKey = prompt(reader, " API key env var name", "API_KEY")
488+
secretEnvKey = prompt(reader, " API key env var name (empty if none)", "API_KEY")
489+
baseURL = prompt(reader, " API base URL", "")
468490
modelName = prompt(reader, " Model name", "")
469491
default:
470492
providerName = "openai"
471493
secretEnvKey = "OPENAI_API_KEY"
472494
modelName = prompt(reader, " Model name", "gpt-4o")
473495
}
474496

475-
apiKey := promptSecret(reader, fmt.Sprintf(" %s", secretEnvKey))
476-
if apiKey == "" {
477-
fmt.Println(" ⚠ No API key provided — you can add it later:")
478-
fmt.Printf(" kubectl create secret generic %s-%s-key --from-literal=%s=<key>\n",
479-
instanceName, providerName, secretEnvKey)
497+
var apiKey string
498+
if secretEnvKey != "" {
499+
apiKey = promptSecret(reader, fmt.Sprintf(" %s", secretEnvKey))
500+
if apiKey == "" {
501+
fmt.Println(" ⚠ No API key provided — you can add it later:")
502+
fmt.Printf(" kubectl create secret generic %s-%s-key --from-literal=%s=<key>\n",
503+
instanceName, providerName, secretEnvKey)
504+
}
480505
}
481506

482507
providerSecretName := fmt.Sprintf("%s-%s-key", instanceName, providerName)
@@ -530,6 +555,9 @@ func runOnboard() error {
530555
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
531556
fmt.Printf(" Instance: %s (namespace: %s)\n", instanceName, namespace)
532557
fmt.Printf(" Provider: %s (model: %s)\n", providerName, modelName)
558+
if baseURL != "" {
559+
fmt.Printf(" Base URL: %s\n", baseURL)
560+
}
533561
if channelType != "" {
534562
fmt.Printf(" Channel: %s\n", channelType)
535563
} else {
@@ -581,7 +609,7 @@ func runOnboard() error {
581609

582610
// 4. Create ClawInstance.
583611
fmt.Printf(" Creating ClawInstance %s...\n", instanceName)
584-
instanceYAML := buildClawInstanceYAML(instanceName, namespace, modelName,
612+
instanceYAML := buildClawInstanceYAML(instanceName, namespace, modelName, baseURL,
585613
providerName, providerSecretName, channelType, channelSecretName,
586614
policyName, applyPolicy)
587615
if err := kubectlApplyStdin(instanceYAML); err != nil {
@@ -687,7 +715,7 @@ spec:
687715
`, name, ns)
688716
}
689717

690-
func buildClawInstanceYAML(name, ns, model, provider, providerSecret,
718+
func buildClawInstanceYAML(name, ns, model, baseURL, provider, providerSecret,
691719
channelType, channelSecret, policyName string, hasPolicy bool) string {
692720

693721
var channelsBlock string
@@ -704,6 +732,11 @@ func buildClawInstanceYAML(name, ns, model, provider, providerSecret,
704732
policyBlock = fmt.Sprintf(" policyRef: %s\n", policyName)
705733
}
706734

735+
var baseURLLine string
736+
if baseURL != "" {
737+
baseURLLine = fmt.Sprintf(" baseURL: %s\n", baseURL)
738+
}
739+
707740
return fmt.Sprintf(`apiVersion: k8sclaw.io/v1alpha1
708741
kind: ClawInstance
709742
metadata:
@@ -713,10 +746,10 @@ spec:
713746
%s agents:
714747
default:
715748
model: %s
716-
authRefs:
749+
%s authRefs:
717750
- provider: %s
718751
secret: %s
719-
%s`, name, ns, channelsBlock, model, provider, providerSecret, policyBlock)
752+
%s`, name, ns, channelsBlock, model, baseURLLine, provider, providerSecret, policyBlock)
720753
}
721754

722755
func kubectlApplyStdin(yaml string) error {

0 commit comments

Comments
 (0)