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
5 changes: 5 additions & 0 deletions .changeset/patch-add-model-aliases.md

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

28 changes: 27 additions & 1 deletion pkg/parser/import_field_extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ type importAccumulator struct {
skipBotsSet map[string]bool
caches []string
features []map[string]any
runInstallScripts bool // true if any imported workflow sets run-install-scripts: true (global or node-level)
models []map[string][]string // model alias maps from each imported file (appended in import order)
runInstallScripts bool // true if any imported workflow sets run-install-scripts: true (global or node-level)
agentFile string
agentImportSpec string
repositoryImports []string
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦴 Caveman see field change here. Good struct update! Me check: does runInstallScripts now handle both legacy bool AND new config? If yes, add test for old bool value migration.

Expand Down Expand Up @@ -436,6 +437,30 @@ func (acc *importAccumulator) extractAllImportFields(content []byte, item import
}
}

// Extract model aliases from imported file (parse as map[string][]string structure)
modelsContent, err := extractFieldJSONFromMap(fm, "models", "{}")
if err == nil && modelsContent != "" && modelsContent != "{}" {
var rawModels map[string]any
if jsonErr := json.Unmarshal([]byte(modelsContent), &rawModels); jsonErr == nil {
modelsMap := make(map[string][]string, len(rawModels))
for k, v := range rawModels {
if patterns, ok := v.([]any); ok {
strs := make([]string, 0, len(patterns))
for _, p := range patterns {
if s, ok := p.(string); ok {
strs = append(strs, s)
}
}
modelsMap[k] = strs
}
}
if len(modelsMap) > 0 {
acc.models = append(acc.models, modelsMap)
log.Printf("Extracted model aliases from import: %d entries", len(modelsMap))
}
}
}

// Extract run-install-scripts flag from imported file.
// If global run-install-scripts: true is set OR if runtimes.node.run-install-scripts: true is set,
// propagate to the accumulator (OR semantics: any import enabling it enables it overall).
Expand Down Expand Up @@ -510,6 +535,7 @@ func (acc *importAccumulator) toImportsResult(topologicalOrder []string) *Import
MergedEnv: acc.envBuilder.String(),
MergedEnvSources: acc.envSources,
MergedFeatures: acc.features,
MergedModels: acc.models,
MergedObservability: acc.observabilityBuilder.String(),
ImportedFiles: topologicalOrder,
AgentFile: acc.agentFile,
Expand Down
73 changes: 37 additions & 36 deletions pkg/parser/import_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,43 @@ var importLog = logger.New("parser:import_processor")

// ImportsResult holds the result of processing imports from frontmatter
type ImportsResult struct {
MergedTools string // Merged tools configuration from all imports
MergedMCPServers string // Merged mcp-servers configuration from all imports
MergedEngines []string // Merged engine configurations from all imports
MergedSafeOutputs []string // Merged safe-outputs configurations from all imports
MergedMCPScripts []string // Merged mcp-scripts configurations from all imports
MergedMarkdown string // Only contains imports WITH inputs (for compile-time substitution)
ImportPaths []string // List of import file paths for runtime-import macro generation (replaces MergedMarkdown)
MergedSteps string // Merged steps configuration from all imports (excluding copilot-setup-steps)
CopilotSetupSteps string // Steps from copilot-setup-steps.yml (inserted at start)
MergedPreSteps string // Merged pre-steps configuration from all imports (prepended in order)
MergedPreAgentSteps string // Merged pre-agent-steps configuration from all imports (prepended in order)
MergedRuntimes string // Merged runtimes configuration from all imports
MergedRunInstallScripts bool // true if any imported workflow sets run-install-scripts: true (global or node-level)
MergedServices string // Merged services configuration from all imports
MergedNetwork string // Merged network configuration from all imports
MergedPermissions string // Merged permissions configuration from all imports
MergedSecretMasking string // Merged secret-masking steps from all imports
MergedBots []string // Merged bots list from all imports (union of bot names)
MergedSkipRoles []string // Merged skip-roles list from all imports (union of role names)
MergedSkipBots []string // Merged skip-bots list from all imports (union of usernames)
MergedActivationGitHubToken string // GitHub token from on.github-token in first imported workflow that defines it
MergedActivationGitHubApp string // JSON-encoded on.github-app from first imported workflow that defines it
MergedTopLevelGitHubApp string // JSON-encoded top-level github-app from first imported workflow that defines it
MergedCheckout string // JSON-encoded checkout configurations from imported workflows (one JSON value per line)
MergedPostSteps string // Merged post-steps configuration from all imports (appended in order)
MergedLabels []string // Merged labels from all imports (union of label names)
MergedCaches []string // Merged cache configurations from all imports (appended in order)
MergedJobs string // Merged jobs from imported YAML workflows (JSON format)
MergedEnv string // Merged env configuration from all imports (JSON format)
MergedEnvSources map[string]string // env var name → source import path (for conflict detection and lock file header listing)
MergedFeatures []map[string]any // Merged features configuration from all imports (parsed YAML structures)
MergedObservability string // Observability config (JSON) from first import that defines it (first-wins)
ImportedFiles []string // List of imported file paths (for manifest)
AgentFile string // Path to custom agent file (if imported)
AgentImportSpec string // Original import specification for agent file (e.g., "owner/repo/path@ref")
RepositoryImports []string // List of repository imports (format: "owner/repo@ref") for .github folder merging
MergedTools string // Merged tools configuration from all imports
MergedMCPServers string // Merged mcp-servers configuration from all imports
MergedEngines []string // Merged engine configurations from all imports
MergedSafeOutputs []string // Merged safe-outputs configurations from all imports
MergedMCPScripts []string // Merged mcp-scripts configurations from all imports
MergedMarkdown string // Only contains imports WITH inputs (for compile-time substitution)
ImportPaths []string // List of import file paths for runtime-import macro generation (replaces MergedMarkdown)
MergedSteps string // Merged steps configuration from all imports (excluding copilot-setup-steps)
CopilotSetupSteps string // Steps from copilot-setup-steps.yml (inserted at start)
MergedPreSteps string // Merged pre-steps configuration from all imports (prepended in order)
MergedPreAgentSteps string // Merged pre-agent-steps configuration from all imports (prepended in order)
MergedRuntimes string // Merged runtimes configuration from all imports
MergedRunInstallScripts bool // true if any imported workflow sets run-install-scripts: true (global or node-level)
MergedServices string // Merged services configuration from all imports
MergedNetwork string // Merged network configuration from all imports
MergedPermissions string // Merged permissions configuration from all imports
MergedSecretMasking string // Merged secret-masking steps from all imports
MergedBots []string // Merged bots list from all imports (union of bot names)
MergedSkipRoles []string // Merged skip-roles list from all imports (union of role names)
MergedSkipBots []string // Merged skip-bots list from all imports (union of usernames)
MergedActivationGitHubToken string // GitHub token from on.github-token in first imported workflow that defines it
MergedActivationGitHubApp string // JSON-encoded on.github-app from first imported workflow that defines it
MergedTopLevelGitHubApp string // JSON-encoded top-level github-app from first imported workflow that defines it
MergedCheckout string // JSON-encoded checkout configurations from imported workflows (one JSON value per line)
MergedPostSteps string // Merged post-steps configuration from all imports (appended in order)
MergedLabels []string // Merged labels from all imports (union of label names)
MergedCaches []string // Merged cache configurations from all imports (appended in order)
MergedJobs string // Merged jobs from imported YAML workflows (JSON format)
MergedEnv string // Merged env configuration from all imports (JSON format)
MergedEnvSources map[string]string // env var name → source import path (for conflict detection and lock file header listing)
MergedFeatures []map[string]any // Merged features configuration from all imports (parsed YAML structures)
MergedModels []map[string][]string // Merged model alias definitions from all imports (first import to define a key wins among imports)
MergedObservability string // Observability config (JSON) from first import that defines it (first-wins)
ImportedFiles []string // List of imported file paths (for manifest)
AgentFile string // Path to custom agent file (if imported)
AgentImportSpec string // Original import specification for agent file (e.g., "owner/repo/path@ref")
RepositoryImports []string // List of repository imports (format: "owner/repo@ref") for .github folder merging
// ImportInputs uses map[string]any because input values can be different types (string, number, boolean).
// This is parsed from YAML frontmatter where the structure is dynamic and not known at compile time.
// This is an appropriate use of 'any' for dynamic YAML/JSON data.
Expand Down
18 changes: 18 additions & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2730,6 +2730,24 @@
}
]
},
"models": {
"description": "Named model alias definitions with ordered fallback lists, resolved recursively by AWF. Each key is an alias name (use empty string \"\" for the default policy). Each value is an ordered list of vendor/modelid glob patterns or other alias names to try in sequence. Entries defined here are merged on top of the builtin aliases; the main workflow file always wins over imported aliases. Builtin aliases include: sonnet, haiku, opus, gpt-5, gpt-5-mini, gpt-5-codex, gemini-flash, gemini-pro, small, mini, large, auto.",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"description": "A vendor/modelid glob pattern (e.g. 'copilot/*sonnet*') or an alias name (e.g. 'sonnet') to resolve recursively."
},
"description": "Ordered list of model patterns or alias names to try in sequence."
},
"examples": [
{
"sonnet": ["mygateway/*sonnet-v3*"],
"": ["sonnet", "gpt-5-codex"]
}
]
},
"experiments": {
"description": "A/B testing experiments. Each key is an experiment name; the value is either an array of two or more variant strings (bare-array form) or an object with a 'variants' field plus optional metadata fields (description, metric, weight, issue, start_date, end_date, hypothesis, secondary_metrics, guardrail_metrics, min_samples, owner). At runtime the activation job picks a variant using actions/cache to maintain consistent assignment across runs. Use ${{ experiments.<name> }} in the workflow prompt to reference the selected variant. When multiple experiments are declared, assignments are statistically balanced using a counter that round-robins across variants (or weighted when 'weight' is provided).",
"type": "object",
Expand Down
17 changes: 17 additions & 0 deletions pkg/workflow/awf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ type AWFConfigFile struct {

// Container contains container execution configuration.
Container *AWFContainerConfig `json:"container,omitempty"`

// Models contains model alias and fallback policy definitions.
// Keys are alias names (empty string "" = default policy); values are ordered
// lists of vendor/modelid patterns or other alias names to try in sequence.
// AWF resolves aliases recursively; loops are not permitted.
//
// NOTE: Pending AWF binary support (config.models is not yet recognised by the
// AWF firewall schema). This field is intentionally omitted from JSON output
// until the AWF schema at awf-config.v1.json is updated to include "models".
// The field remains here so the struct is ready once AWF support lands.
Models map[string][]string `json:"-"`
}

// AWFNetworkConfig is the "network" section of the AWF config file.
Expand Down Expand Up @@ -209,6 +220,12 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) {
awfConfigLog.Printf("Container section: image_tag=%s", awfImageTag)
}

// ── Models section ────────────────────────────────────────────────────────
if config.WorkflowData != nil && len(config.WorkflowData.ModelMappings) > 0 {
awfConfig.Models = config.WorkflowData.ModelMappings
awfConfigLog.Printf("Models section: %d alias entries", len(config.WorkflowData.ModelMappings))
}

jsonBytes, err := json.Marshal(awfConfig)
if err != nil {
return "", fmt.Errorf("failed to marshal AWF config to JSON: %w", err)
Expand Down
1 change: 1 addition & 0 deletions pkg/workflow/compiler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ type WorkflowData struct {
CachedAllowedDomainsStr string // cached allowed-domains string for sanitization (for performance optimization); computed once and reused across multiple compilation steps
CachedAllowedDomainsComputed bool // true once CachedAllowedDomainsStr has been set; distinguishes "computed empty" from "not yet computed"
KnownActionCredentialEnvVars map[string]bool // env vars for clean_known_action_credentials.sh; keyed by GH_AW_CLEAN_* names; nil when no known credential-leaking actions are detected
ModelMappings map[string][]string // merged model alias map (builtins + imported workflow aliases + main frontmatter overrides, in priority order); NOT yet emitted to AWF config JSON — pending AWF firewall support (config.models)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦴 Caveman note: ModelMappings field in WorkflowData good addition. Me suggest: also document whether nil map is valid (e.g., when no model aliases configured) vs empty map.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smoke test agent agrees! Nil vs empty map distinction is important for callers. A clear nil-means-no-aliases convention would prevent defensive nil-check sprawl throughout the codebase.

Warning

Firewall blocked 6 domains

The following domains were blocked by the firewall during workflow execution:

  • accounts.google.com
  • android.clients.google.com
  • clients2.google.com
  • contentautofill.googleapis.com
  • safebrowsingohttpgateway.googleapis.com
  • www.google.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "accounts.google.com"
    - "android.clients.google.com"
    - "clients2.google.com"
    - "contentautofill.googleapis.com"
    - "safebrowsingohttpgateway.googleapis.com"
    - "www.google.com"

See Network Configuration for more information.

📰 BREAKING: Report filed by Smoke Copilot · ● 1M

}

// PinContext returns an actionpins.PinContext backed by this WorkflowData.
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/frontmatter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ type FrontmatterConfig struct {
// Experiments during frontmatter parsing. Keys match those of Experiments.
ExperimentConfigs map[string]*ExperimentConfig `json:"-"`

// Model aliases and fallback policies.
// Keys are alias names (empty string "" = default policy); values are ordered lists of
// model patterns or alias references to try in sequence.
// Merged with the builtin model aliases at compile time; frontmatter entries take precedence.
Models map[string][]string `json:"models,omitempty"`

// Rate limiting configuration
RateLimit *RateLimitConfig `json:"rate-limit,omitempty"`

Expand Down
Loading
Loading