Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/smoke-copilot-auto.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/workflow/agentic_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ type ModelEnvVarProvider interface {
type InferenceProviderResolver interface {
// ResolveLLMProvider returns the effective provider for the workflow
// (for example "github", "anthropic", or "openai").
ResolveLLMProvider(workflowData *WorkflowData) string
ResolveLLMProvider(workflowData *WorkflowData) LLMProvider
}

// LLMProviderResolver is kept as a backward-compatible alias.
Expand Down
6 changes: 3 additions & 3 deletions pkg/workflow/claude_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (e *ClaudeEngine) GetModelEnvVarName() string {

// ResolveLLMProvider returns the effective provider for Claude inference.
// Default is anthropic, overridable via engine.provider (or engine.model-provider).
func (e *ClaudeEngine) ResolveLLMProvider(workflowData *WorkflowData) string {
func (e *ClaudeEngine) ResolveLLMProvider(workflowData *WorkflowData) LLMProvider {
return resolveEngineLLMProvider(workflowData, LLMProviderAnthropic)
}

Expand Down Expand Up @@ -423,7 +423,7 @@ func (e *ClaudeEngine) buildClaudeFullCommand(workflowData *WorkflowData, claude
func (e *ClaudeEngine) buildClaudeCommandEnv(workflowData *WorkflowData) map[string]string {
provider := e.ResolveLLMProvider(workflowData)
env := buildClaudeBaseEnvMap(provider, workflowData)
env["GH_AW_LLM_PROVIDER"] = provider
env["GH_AW_LLM_PROVIDER"] = string(provider)
if isFirewallEnabled(workflowData) && provider != LLMProviderAnthropic {
env["ANTHROPIC_BASE_URL"] = llmProviderGatewayBaseURL(provider)
}
Expand Down Expand Up @@ -465,7 +465,7 @@ func (e *ClaudeEngine) buildClaudeCommandEnv(workflowData *WorkflowData) map[str

// buildClaudeBaseEnvMap returns the initial Claude execution environment with static flags
// and well-known GitHub Actions context values.
func buildClaudeBaseEnvMap(provider string, workflowData *WorkflowData) map[string]string {
func buildClaudeBaseEnvMap(provider LLMProvider, workflowData *WorkflowData) map[string]string {
return map[string]string{
"ANTHROPIC_API_KEY": llmProviderSecretExpression(provider, workflowData),
"DISABLE_TELEMETRY": "1",
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/claude_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func TestClaudeEngineLLMProviderGitHubUsesCopilotCredentials(t *testing.T) {
workflowData := &WorkflowData{
Name: "test-workflow",
EngineConfig: &EngineConfig{
LLMProvider: "github",
LLMProvider: LLMProviderGitHub,
},
NetworkPermissions: &NetworkPermissions{
Firewall: &FirewallConfig{Enabled: true},
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/codex_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (e *CodexEngine) GetModelEnvVarName() string {

// ResolveLLMProvider returns the effective provider for Codex inference.
// Default is openai, overridable via engine.provider (or engine.model-provider).
func (e *CodexEngine) ResolveLLMProvider(workflowData *WorkflowData) string {
func (e *CodexEngine) ResolveLLMProvider(workflowData *WorkflowData) LLMProvider {
return resolveEngineLLMProvider(workflowData, LLMProviderOpenAI)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/codex_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestCodexEngine_ResolveLLMProvider_DefaultOpenAI(t *testing.T) {
engine := NewCodexEngine()

asserted := engine.ResolveLLMProvider(&WorkflowData{EngineConfig: &EngineConfig{ID: "codex"}})
if asserted != "openai" {
if asserted != LLMProviderOpenAI {
t.Fatalf("expected default model-provider to be openai, got %q", asserted)
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/compiler_model_pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func resolveEngineProviderForPricing(engineConfig *EngineConfig) string {
return "github-copilot" // default provider when no engine is specified
}
if engineConfig.LLMProvider != "" {
return normalizeProviderForPricing(engineConfig.LLMProvider)
return normalizeProviderForPricing(string(engineConfig.LLMProvider))
}
if engineConfig.InlineProviderID != "" {
return normalizeProviderForPricing(engineConfig.InlineProviderID)
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/compiler_model_pricing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ func TestResolveEngineProviderForPricing(t *testing.T) {
config *EngineConfig
want string
}{
{"LLMProvider wins", &EngineConfig{LLMProvider: "openai", InlineProviderID: "other", ID: "claude"}, "openai"},
{"LLMProvider alias normalized", &EngineConfig{LLMProvider: "github_models", ID: "claude"}, "github-copilot"},
{"LLMProvider wins", &EngineConfig{LLMProvider: LLMProviderOpenAI, InlineProviderID: "other", ID: "claude"}, "openai"},
{"LLMProvider alias normalized", &EngineConfig{LLMProvider: LLMProvider("github_models"), ID: "claude"}, "github-copilot"},
{"InlineProviderID second", &EngineConfig{InlineProviderID: "openai", ID: "claude"}, "openai"},
{"claude engine → anthropic", &EngineConfig{ID: "claude"}, "anthropic"},
{"codex engine → openai", &EngineConfig{ID: "codex"}, "openai"},
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/copilot_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (e *CopilotEngine) GetModelEnvVarName() string {

// ResolveLLMProvider returns the effective provider for Copilot inference.
// Default is github, overridable via engine.model-provider.
func (e *CopilotEngine) ResolveLLMProvider(workflowData *WorkflowData) string {
func (e *CopilotEngine) ResolveLLMProvider(workflowData *WorkflowData) LLMProvider {
return resolveEngineLLMProvider(workflowData, LLMProviderGitHub)
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/workflow/copilot_engine_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ type copilotStepEnvFlags struct {

func (e *CopilotEngine) buildCopilotStepEnv(
workflowData *WorkflowData,
llmProvider string,
llmProvider LLMProvider,
modelEnvVar string,
timeoutValue string,
flags copilotStepEnvFlags,
Expand All @@ -537,8 +537,8 @@ func (e *CopilotEngine) buildCopilotStepEnv(
return env
}

func (e *CopilotEngine) buildCopilotBaseStepEnv(workflowData *WorkflowData, llmProvider, timeoutValue string, isBYOKMode, useCopilotRequests bool) map[string]string {
env := map[string]string{"COPILOT_AGENT_RUNNER_TYPE": "STANDALONE", "GITHUB_STEP_SUMMARY": AgentStepSummaryPath, "GITHUB_HEAD_REF": "${{ github.head_ref }}", "GITHUB_REF_NAME": "${{ github.ref_name }}", "GITHUB_WORKSPACE": "${{ github.workspace }}", "RUNNER_TEMP": "${{ runner.temp }}", "GH_AW_TIMEOUT_MINUTES": timeoutValue, "GITHUB_SERVER_URL": "${{ github.server_url }}", "GITHUB_API_URL": "${{ github.api_url }}", "GH_AW_LLM_PROVIDER": llmProvider}
func (e *CopilotEngine) buildCopilotBaseStepEnv(workflowData *WorkflowData, llmProvider LLMProvider, timeoutValue string, isBYOKMode, useCopilotRequests bool) map[string]string {
env := map[string]string{"COPILOT_AGENT_RUNNER_TYPE": "STANDALONE", "GITHUB_STEP_SUMMARY": AgentStepSummaryPath, "GITHUB_HEAD_REF": "${{ github.head_ref }}", "GITHUB_REF_NAME": "${{ github.ref_name }}", "GITHUB_WORKSPACE": "${{ github.workspace }}", "RUNNER_TEMP": "${{ runner.temp }}", "GH_AW_TIMEOUT_MINUTES": timeoutValue, "GITHUB_SERVER_URL": "${{ github.server_url }}", "GITHUB_API_URL": "${{ github.api_url }}", "GH_AW_LLM_PROVIDER": string(llmProvider)}
// Auto-configure Copilot BYOK routing when engine.model-provider selects a non-GitHub provider.
// Explicit engine.env values still win later via maps.Copy.
if llmProvider != LLMProviderGitHub && isFirewallEnabled(workflowData) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/copilot_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3059,7 +3059,7 @@ func TestCopilotEngineLLMProviderAnthropicAutoBYOK(t *testing.T) {
workflowData := &WorkflowData{
Name: "test-workflow",
EngineConfig: &EngineConfig{
LLMProvider: "anthropic",
LLMProvider: LLMProviderAnthropic,
},
NetworkPermissions: &NetworkPermissions{
Firewall: &FirewallConfig{Enabled: true},
Expand Down
8 changes: 4 additions & 4 deletions pkg/workflow/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func toEngineEnvValueString(value any) (string, bool) {
type EngineConfig struct {
ID string
Version string
LLMProvider string // Inference provider override for this engine (engine.provider / engine.model-provider)
LLMProvider LLMProvider // Inference provider override for this engine (engine.provider / engine.model-provider)
PermissionMode string
MaxTurns string
MaxToolDenials string // Maximum repeated tool denials before stopping inference (copilot SDK mode only)
Expand Down Expand Up @@ -292,7 +292,7 @@ func extractInlineEngineConfig(runtime any, engineObj map[string]any, topLevel e
func extractInlineProviderConfig(config *EngineConfig, provider any) string {
switch providerTyped := provider.(type) {
case string:
config.InlineProviderID = normalizeEngineProvider(providerTyped)
config.InlineProviderID = string(normalizeEngineProvider(providerTyped))
case map[string]any:
if id, ok := providerTyped["id"].(string); ok {
config.InlineProviderID = id
Expand Down Expand Up @@ -381,8 +381,8 @@ func applyEngineProviderFields(config *EngineConfig, engineObj map[string]any) {
}
}

func normalizeEngineProvider(provider string) string {
return strings.ToLower(strings.TrimSpace(provider))
func normalizeEngineProvider(provider string) LLMProvider {
return LLMProvider(strings.ToLower(strings.TrimSpace(provider)))
}

func applyEnginePermissionMode(config *EngineConfig, engineObj map[string]any) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/workflow/engine_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func TestExtractEngineConfig(t *testing.T) {
},
},
expectedEngineSetting: "claude",
expectedConfig: &EngineConfig{ID: "claude", LLMProvider: "github"},
expectedConfig: &EngineConfig{ID: "claude", LLMProvider: LLMProviderGitHub},
},
{
name: "object format - with provider override",
Expand All @@ -264,7 +264,7 @@ func TestExtractEngineConfig(t *testing.T) {
},
},
expectedEngineSetting: "claude",
expectedConfig: &EngineConfig{ID: "claude", LLMProvider: "openai"},
expectedConfig: &EngineConfig{ID: "claude", LLMProvider: LLMProviderOpenAI},
},
{
name: "object format - provider override wins over model-provider",
Expand All @@ -276,7 +276,7 @@ func TestExtractEngineConfig(t *testing.T) {
},
},
expectedEngineSetting: "claude",
expectedConfig: &EngineConfig{ID: "claude", LLMProvider: "openai"},
expectedConfig: &EngineConfig{ID: "claude", LLMProvider: LLMProviderOpenAI},
},
{
name: "object format - deprecated llm-provider ignored",
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/engine_helpers_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func TestGetRequiredSecretNames_Copilot(t *testing.T) {
Tools: map[string]any{},
ParsedTools: &ToolsConfig{},
EngineConfig: &EngineConfig{
LLMProvider: "openai",
LLMProvider: LLMProviderOpenAI,
},
}

Expand Down Expand Up @@ -281,7 +281,7 @@ func TestGetRequiredSecretNames_Claude(t *testing.T) {
Tools: map[string]any{},
ParsedTools: &ToolsConfig{},
EngineConfig: &EngineConfig{
LLMProvider: "github",
LLMProvider: LLMProviderGitHub,
},
}

Expand Down
46 changes: 27 additions & 19 deletions pkg/workflow/llm_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ import (

var llmProviderLog = logger.New("workflow:llm_provider")

// LLMProvider identifies the inference provider used by an engine (e.g. "github", "anthropic", "openai").
type LLMProvider string

// String returns the string representation of the provider, satisfying the fmt.Stringer interface.
func (p LLMProvider) String() string {
return string(p)
}

const (
LLMProviderGitHub = "github"
LLMProviderAnthropic = "anthropic"
LLMProviderOpenAI = "openai"
LLMProviderGitHub LLMProvider = "github"
LLMProviderAnthropic LLMProvider = "anthropic"
LLMProviderOpenAI LLMProvider = "openai"
)

var llmProviderAliases = map[string]string{
var llmProviderAliases = map[string]LLMProvider{
"copilot": LLMProviderGitHub,
"github": LLMProviderGitHub,
"github-copilot": LLMProviderGitHub,
Expand All @@ -26,11 +34,11 @@ var llmProviderAliases = map[string]string{
}

type llmProviderProfile struct {
id string
id LLMProvider
gatewayPort int
}

func normalizeLLMProvider(provider string) string {
func normalizeLLMProvider(provider string) LLMProvider {
normalized := strings.TrimSpace(provider)
if normalized == "" {
return LLMProviderAnthropic
Expand All @@ -39,22 +47,22 @@ func normalizeLLMProvider(provider string) string {
if alias, ok := llmProviderAliases[normalized]; ok {
return alias
}
return normalized
return LLMProvider(normalized)
}

func resolveEngineLLMProvider(workflowData *WorkflowData, defaultProvider string) string {
func resolveEngineLLMProvider(workflowData *WorkflowData, defaultProvider LLMProvider) LLMProvider {
if workflowData == nil || workflowData.EngineConfig == nil || workflowData.EngineConfig.LLMProvider == "" {
provider := normalizeLLMProvider(defaultProvider)
provider := normalizeLLMProvider(string(defaultProvider))
llmProviderLog.Printf("Resolved LLM provider from default: %s", provider)
return provider
}
provider := normalizeLLMProvider(workflowData.EngineConfig.LLMProvider)
provider := normalizeLLMProvider(string(workflowData.EngineConfig.LLMProvider))
llmProviderLog.Printf("Resolved LLM provider from engine config: %s", provider)
return provider
}

func llmProviderProfileFor(provider string) llmProviderProfile {
switch normalizeLLMProvider(provider) {
func llmProviderProfileFor(provider LLMProvider) llmProviderProfile {
switch provider {
case LLMProviderGitHub:
return llmProviderProfile{
id: LLMProviderGitHub,
Expand All @@ -73,8 +81,8 @@ func llmProviderProfileFor(provider string) llmProviderProfile {
}
}

func llmProviderSecretNames(provider string) []string {
switch normalizeLLMProvider(provider) {
func llmProviderSecretNames(provider LLMProvider) []string {
switch provider {
case LLMProviderGitHub:
return []string{"COPILOT_GITHUB_TOKEN"}
case LLMProviderOpenAI:
Expand All @@ -84,8 +92,8 @@ func llmProviderSecretNames(provider string) []string {
}
}

func llmProviderSecretExpression(provider string, workflowData *WorkflowData) string {
switch normalizeLLMProvider(provider) {
func llmProviderSecretExpression(provider LLMProvider, workflowData *WorkflowData) string {
switch provider {
case LLMProviderGitHub:
if hasCopilotRequestsWritePermission(workflowData) {
llmProviderLog.Print("Using github.token for GitHub Copilot (copilot-requests write permission present)")
Expand All @@ -100,13 +108,13 @@ func llmProviderSecretExpression(provider string, workflowData *WorkflowData) st
}
}

func llmProviderGatewayBaseURL(provider string) string {
func llmProviderGatewayBaseURL(provider LLMProvider) string {
profile := llmProviderProfileFor(provider)
return fmt.Sprintf("http://host.docker.internal:%d", profile.gatewayPort)
}

func llmProviderDocsURL(provider string) string {
switch normalizeLLMProvider(provider) {
func llmProviderDocsURL(provider LLMProvider) string {
switch provider {
case LLMProviderGitHub:
return "https://github.github.com/gh-aw/reference/engines/#github-copilot-default"
case LLMProviderOpenAI:
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/pi_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (e *PiEngine) GetModelEnvVarName() string {

// ResolveLLMProvider returns the effective provider for Pi inference.
// Default is github, overridable via engine.model-provider.
func (e *PiEngine) ResolveLLMProvider(workflowData *WorkflowData) string {
func (e *PiEngine) ResolveLLMProvider(workflowData *WorkflowData) LLMProvider {
return resolveEngineLLMProvider(workflowData, LLMProviderGitHub)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/pi_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestPiEngine_GetModelEnvVarName(t *testing.T) {

func TestPiEngine_ResolveLLMProvider_DefaultGitHub(t *testing.T) {
engine := NewPiEngine()
assert.Equal(t, "github", engine.ResolveLLMProvider(&WorkflowData{EngineConfig: &EngineConfig{ID: "pi"}}))
assert.Equal(t, LLMProviderGitHub, engine.ResolveLLMProvider(&WorkflowData{EngineConfig: &EngineConfig{ID: "pi"}}))
}

func TestPiEngine_GetRequiredSecretNames(t *testing.T) {
Expand Down
Loading