diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index d35def1b55..921de8c48c 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -420,7 +420,7 @@ jobs: "name": "add_comment" }, { - "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\", model=\"claude-opus-4.6\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent.", "inputSchema": { "additionalProperties": false, "properties": { @@ -428,6 +428,14 @@ jobs: "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", "type": "string" }, + "custom_agent": { + "description": "Custom agent ID to use when assigning a custom agent. Optional. This is used for specialized agent configurations beyond the standard Copilot agent.", + "type": "string" + }, + "custom_instructions": { + "description": "Custom instructions to provide to the agent. Optional. These instructions will guide the agent's behavior when working on the task.", + "type": "string" + }, "issue_number": { "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", "type": [ @@ -435,6 +443,10 @@ jobs: "string" ] }, + "model": { + "description": "AI model to use for the agent. Optional. Examples: 'auto', 'claude-sonnet-4.5', 'claude-opus-4.5', 'claude-opus-4.6', 'gpt-5.1-codex-max', 'gpt-5.2-codex'. If omitted, defaults to 'auto' (currently Claude Sonnet 4.5). This parameter is only supported when assigning the Copilot coding agent.", + "type": "string" + }, "pull_number": { "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", "type": [ diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index e8b8f470aa..33079fd23b 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -400,7 +400,7 @@ jobs: cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ { - "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\", model=\"claude-opus-4.6\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", "inputSchema": { "additionalProperties": false, "properties": { @@ -408,6 +408,14 @@ jobs: "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", "type": "string" }, + "custom_agent": { + "description": "Custom agent ID to use when assigning a custom agent. Optional. This is used for specialized agent configurations beyond the standard Copilot agent.", + "type": "string" + }, + "custom_instructions": { + "description": "Custom instructions to provide to the agent. Optional. These instructions will guide the agent's behavior when working on the task.", + "type": "string" + }, "issue_number": { "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", "type": [ @@ -415,6 +423,10 @@ jobs: "string" ] }, + "model": { + "description": "AI model to use for the agent. Optional. Examples: 'auto', 'claude-sonnet-4.5', 'claude-opus-4.5', 'claude-opus-4.6', 'gpt-5.1-codex-max', 'gpt-5.2-codex'. If omitted, defaults to 'auto' (currently Claude Sonnet 4.5). This parameter is only supported when assigning the Copilot coding agent.", + "type": "string" + }, "pull_number": { "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", "type": [ diff --git a/actions/setup/js/assign_agent_helpers.cjs b/actions/setup/js/assign_agent_helpers.cjs index 22bdf54995..1530ad228e 100644 --- a/actions/setup/js/assign_agent_helpers.cjs +++ b/actions/setup/js/assign_agent_helpers.cjs @@ -247,9 +247,12 @@ async function getPullRequestDetails(owner, repo, pullNumber) { * @param {string} agentName - Agent name for error messages * @param {string[]|null} allowedAgents - Optional list of allowed agent names. If provided, filters out non-allowed agents from current assignees. * @param {string|null} pullRequestRepoId - Optional pull request repository ID for specifying where the PR should be created (GitHub agentAssignment.targetRepositoryId) + * @param {string|null} model - Optional AI model to use (e.g., "claude-opus-4.6", "auto") + * @param {string|null} customAgent - Optional custom agent ID for custom agents + * @param {string|null} customInstructions - Optional custom instructions for the agent * @returns {Promise} True if successful */ -async function assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents = null, pullRequestRepoId = null) { +async function assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents = null, pullRequestRepoId = null, model = null, customAgent = null, customInstructions = null) { // Filter current assignees based on allowed list (if configured) let filteredAssignees = currentAssignees; if (allowedAgents && allowedAgents.length > 0) { @@ -272,29 +275,60 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent // Build actor IDs array - include new agent and preserve filtered assignees const actorIds = [agentId, ...filteredAssignees.map(a => a.id).filter(id => id !== agentId)]; - // Build the mutation - conditionally include agentAssignment if pullRequestRepoId is provided + // Build the agentAssignment object if any agent-specific parameters are provided + const hasAgentAssignment = pullRequestRepoId || model || customAgent || customInstructions; + + // Build the mutation - conditionally include agentAssignment if any parameters are provided let mutation; let variables; - if (pullRequestRepoId) { - // Include agentAssignment with targetRepositoryId for cross-repo PR creation + if (hasAgentAssignment) { + // Build agentAssignment object with only the fields that are provided + const agentAssignmentFields = []; + const agentAssignmentParams = []; + + if (pullRequestRepoId) { + agentAssignmentFields.push("targetRepositoryId: $targetRepoId"); + agentAssignmentParams.push("$targetRepoId: ID!"); + } + if (model) { + agentAssignmentFields.push("model: $model"); + agentAssignmentParams.push("$model: String!"); + } + if (customAgent) { + agentAssignmentFields.push("customAgent: $customAgent"); + agentAssignmentParams.push("$customAgent: String!"); + } + if (customInstructions) { + agentAssignmentFields.push("customInstructions: $customInstructions"); + agentAssignmentParams.push("$customInstructions: String!"); + } + + // Build the mutation with agentAssignment + const allParams = ["$assignableId: ID!", "$actorIds: [ID!]!", ...agentAssignmentParams].join(", "); + const assignmentFields = agentAssignmentFields.join("\n "); + mutation = ` - mutation($assignableId: ID!, $actorIds: [ID!]!, $targetRepoId: ID!) { + mutation(${allParams}) { replaceActorsForAssignable(input: { assignableId: $assignableId, actorIds: $actorIds, agentAssignment: { - targetRepositoryId: $targetRepoId + ${assignmentFields} } }) { __typename } } `; + variables = { assignableId: assignableId, actorIds, - targetRepoId: pullRequestRepoId, + ...(pullRequestRepoId && { targetRepoId: pullRequestRepoId }), + ...(model && { model }), + ...(customAgent && { customAgent }), + ...(customInstructions && { customInstructions }), }; } else { // Standard mutation without agentAssignment @@ -317,7 +351,14 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent try { core.info("Using built-in github object for mutation"); - core.debug(`GraphQL mutation with variables: assignableId=${assignableId}, actorIds=${JSON.stringify(actorIds)}${pullRequestRepoId ? `, targetRepoId=${pullRequestRepoId}` : ""}`); + // Build debug log message with all parameters + let debugMsg = `GraphQL mutation with variables: assignableId=${assignableId}, actorIds=${JSON.stringify(actorIds)}`; + if (pullRequestRepoId) debugMsg += `, targetRepoId=${pullRequestRepoId}`; + if (model) debugMsg += `, model=${model}`; + if (customAgent) debugMsg += `, customAgent=${customAgent}`; + if (customInstructions) debugMsg += `, customInstructions=${customInstructions.substring(0, 50)}...`; + core.debug(debugMsg); + const response = await github.graphql(mutation, { ...variables, headers: { diff --git a/actions/setup/js/assign_agent_helpers.test.cjs b/actions/setup/js/assign_agent_helpers.test.cjs index 1a5502ac35..0332896814 100644 --- a/actions/setup/js/assign_agent_helpers.test.cjs +++ b/actions/setup/js/assign_agent_helpers.test.cjs @@ -306,6 +306,124 @@ describe("assign_agent_helpers.cjs", () => { const agentMatches = calledArgs.actorIds.filter(id => id === "AGENT_456"); expect(agentMatches.length).toBe(1); }); + + it("should include model in agentAssignment when provided", async () => { + mockGithub.graphql.mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, "claude-opus-4.6"); + + const calledArgs = mockGithub.graphql.mock.calls[0]; + const mutation = calledArgs[0]; + const variables = calledArgs[1]; + + // Mutation should include agentAssignment with model + expect(mutation).toContain("agentAssignment"); + expect(mutation).toContain("model: $model"); + expect(variables.model).toBe("claude-opus-4.6"); + }); + + it("should include customAgent in agentAssignment when provided", async () => { + mockGithub.graphql.mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, null, "custom-agent-123"); + + const calledArgs = mockGithub.graphql.mock.calls[0]; + const mutation = calledArgs[0]; + const variables = calledArgs[1]; + + // Mutation should include agentAssignment with customAgent + expect(mutation).toContain("agentAssignment"); + expect(mutation).toContain("customAgent: $customAgent"); + expect(variables.customAgent).toBe("custom-agent-123"); + }); + + it("should include customInstructions in agentAssignment when provided", async () => { + mockGithub.graphql.mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, null, null, "Focus on performance optimization"); + + const calledArgs = mockGithub.graphql.mock.calls[0]; + const mutation = calledArgs[0]; + const variables = calledArgs[1]; + + // Mutation should include agentAssignment with customInstructions + expect(mutation).toContain("agentAssignment"); + expect(mutation).toContain("customInstructions: $customInstructions"); + expect(variables.customInstructions).toBe("Focus on performance optimization"); + }); + + it("should include multiple agentAssignment parameters when provided", async () => { + mockGithub.graphql.mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, "REPO_ID_789", "claude-opus-4.6", "custom-agent-123", "Focus on performance"); + + const calledArgs = mockGithub.graphql.mock.calls[0]; + const mutation = calledArgs[0]; + const variables = calledArgs[1]; + + // Mutation should include agentAssignment with all parameters + expect(mutation).toContain("agentAssignment"); + expect(mutation).toContain("targetRepositoryId: $targetRepoId"); + expect(mutation).toContain("model: $model"); + expect(mutation).toContain("customAgent: $customAgent"); + expect(mutation).toContain("customInstructions: $customInstructions"); + + expect(variables.targetRepoId).toBe("REPO_ID_789"); + expect(variables.model).toBe("claude-opus-4.6"); + expect(variables.customAgent).toBe("custom-agent-123"); + expect(variables.customInstructions).toBe("Focus on performance"); + }); + + it("should omit agentAssignment when no agent-specific parameters provided", async () => { + mockGithub.graphql.mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, null, null, null); + + const calledArgs = mockGithub.graphql.mock.calls[0]; + const mutation = calledArgs[0]; + + // Mutation should NOT include agentAssignment when no parameters provided + expect(mutation).not.toContain("agentAssignment"); + }); + + it("should include only provided agentAssignment fields", async () => { + mockGithub.graphql.mockResolvedValueOnce({ + replaceActorsForAssignable: { + __typename: "ReplaceActorsForAssignablePayload", + }, + }); + + // Only provide model, not customAgent or customInstructions + await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, "claude-opus-4.6", null, null); + + const calledArgs = mockGithub.graphql.mock.calls[0]; + const variables = calledArgs[1]; + + // Only model should be in variables + expect(variables.model).toBe("claude-opus-4.6"); + expect(variables.customAgent).toBeUndefined(); + expect(variables.customInstructions).toBeUndefined(); + }); }); describe("generatePermissionErrorSummary", () => { diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index a9c684739a..40873de36e 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -29,6 +29,12 @@ async function main() { // Check if we're in staged mode if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true") { + // Get defaults for preview + const previewDefaultAgent = process.env.GH_AW_AGENT_DEFAULT?.trim() ?? "copilot"; + const previewDefaultModel = process.env.GH_AW_AGENT_DEFAULT_MODEL?.trim(); + const previewDefaultCustomAgent = process.env.GH_AW_AGENT_DEFAULT_CUSTOM_AGENT?.trim(); + const previewDefaultCustomInstructions = process.env.GH_AW_AGENT_DEFAULT_CUSTOM_INSTRUCTIONS?.trim(); + await generateStagedPreview({ title: "Assign to Agent", description: "The following agent assignments would be made if staged mode was disabled:", @@ -40,7 +46,16 @@ async function main() { } else if (item.pull_number) { content += `**Pull Request:** #${item.pull_number}\n`; } - content += `**Agent:** ${item.agent || "copilot"}\n`; + content += `**Agent:** ${item.agent || previewDefaultAgent}\n`; + if (previewDefaultModel) { + content += `**Model:** ${previewDefaultModel}\n`; + } + if (previewDefaultCustomAgent) { + content += `**Custom Agent:** ${previewDefaultCustomAgent}\n`; + } + if (previewDefaultCustomInstructions) { + content += `**Custom Instructions:** ${previewDefaultCustomInstructions}\n`; + } content += "\n"; return content; }, @@ -52,6 +67,24 @@ async function main() { const defaultAgent = process.env.GH_AW_AGENT_DEFAULT?.trim() ?? "copilot"; core.info(`Default agent: ${defaultAgent}`); + // Get default model from configuration + const defaultModel = process.env.GH_AW_AGENT_DEFAULT_MODEL?.trim(); + if (defaultModel) { + core.info(`Default model: ${defaultModel}`); + } + + // Get default custom agent from configuration + const defaultCustomAgent = process.env.GH_AW_AGENT_DEFAULT_CUSTOM_AGENT?.trim(); + if (defaultCustomAgent) { + core.info(`Default custom agent: ${defaultCustomAgent}`); + } + + // Get default custom instructions from configuration + const defaultCustomInstructions = process.env.GH_AW_AGENT_DEFAULT_CUSTOM_INSTRUCTIONS?.trim(); + if (defaultCustomInstructions) { + core.info(`Default custom instructions: ${defaultCustomInstructions}`); + } + // Get target configuration (defaults to "triggering") const targetConfig = process.env.GH_AW_AGENT_TARGET?.trim() || "triggering"; core.info(`Target configuration: ${targetConfig}`); @@ -175,6 +208,11 @@ async function main() { for (let i = 0; i < itemsToProcess.length; i++) { const item = itemsToProcess[i]; const agentName = item.agent ?? defaultAgent; + // Model, custom agent, and custom instructions are only configurable via frontmatter defaults + // They are NOT available as per-item overrides in the tool call + const model = defaultModel; + const customAgent = defaultCustomAgent; + const customInstructions = defaultCustomInstructions; // Use these variables to allow temporary IDs to override target repo per-item. // Default to the configured target repo. @@ -406,8 +444,18 @@ async function main() { // Assign agent using GraphQL mutation - uses built-in github object authenticated via github-token // Pass the allowed list so existing assignees are filtered before calling replaceActorsForAssignable // Pass the PR repo ID if configured (to specify where the PR should be created) + // Pass model, customAgent, and customInstructions if specified core.info(`Assigning ${agentName} coding agent to ${type} #${number}...`); - const success = await assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents, effectivePullRequestRepoId); + if (model) { + core.info(`Using model: ${model}`); + } + if (customAgent) { + core.info(`Using custom agent: ${customAgent}`); + } + if (customInstructions) { + core.info(`Using custom instructions: ${customInstructions.substring(0, 100)}${customInstructions.length > 100 ? "..." : ""}`); + } + const success = await assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents, effectivePullRequestRepoId, model, customAgent, customInstructions); if (!success) { throw new Error(`Failed to assign ${agentName} via GraphQL`); diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index ea6cab61d6..23000f9826 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -2974,7 +2974,23 @@ safe-outputs: assign-to-agent: # Default agent name to assign (default: 'copilot') # (optional) - name: "My Workflow" + name: "copilot" + + # Default AI model to use for the agent (e.g., 'auto', 'claude-sonnet-4.5', + # 'claude-opus-4.5', 'claude-opus-4.6', 'gpt-5.1-codex-max', 'gpt-5.2-codex'). + # Defaults to 'auto' if not specified. + # (optional) + model: "claude-opus-4.6" + + # Default custom agent ID to use when assigning custom agents. This is used for + # specialized agent configurations beyond the standard Copilot agent. + # (optional) + custom-agent: "agent-id" + + # Default custom instructions to provide to the agent. These instructions will + # guide the agent's behavior when working on the task. + # (optional) + custom-instructions: "Focus on performance optimization" # Optional list of allowed agent names. If specified, only these agents can be # assigned. When configured, existing agent assignees not in the list are removed @@ -2999,6 +3015,21 @@ safe-outputs: # (optional) target-repo: "example-value" + # Target repository where the pull request should be created, in format + # 'owner/repo'. If omitted, the PR will be created in the same repository as the + # issue (specified by target-repo or the workflow's repository). This allows + # issues and code to live in different repositories. + # (optional) + pull-request-repo: "owner/repo" + + # List of additional repositories that pull requests can be created in beyond + # pull-request-repo. Each entry should be in 'owner/repo' format. The repository + # specified by pull-request-repo is automatically allowed without needing to be + # listed here. + # (optional) + allowed-pull-request-repos: [] + # Array of strings + # If true, the workflow continues gracefully when agent assignment fails (e.g., # due to missing token or insufficient permissions), logging a warning instead of # failing. Default is false. Useful for workflows that should not fail when agent diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index fe1863ae1d..2759c414d0 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -1301,6 +1301,9 @@ Auto-resolves target from workflow context (issue/PR events) when `issue_number` safe-outputs: assign-to-agent: name: "copilot" # default agent (default: "copilot") + model: "claude-opus-4.6" # default AI model (default: "auto") + custom-agent: "agent-id" # default custom agent ID (optional) + custom-instructions: "..." # default custom instructions (optional) allowed: [copilot] # restrict to specific agents (optional) max: 1 # max assignments (default: 1) target: "triggering" # "triggering" (default), "*", or number @@ -1309,6 +1312,20 @@ safe-outputs: allowed-pull-request-repos: [owner/repo1, owner/repo2] # additional allowed PR repositories ``` +**Model Selection:** +The `model` parameter allows you to specify which AI model the Copilot agent should use. This is configured at the workflow level in the frontmatter and applies to all agent assignments in the workflow. Available options include: +- `auto` - Auto-select model (default, currently Claude Sonnet 4.5) +- `claude-sonnet-4.5` - Claude Sonnet 4.5 +- `claude-opus-4.5` - Claude Opus 4.5 +- `claude-opus-4.6` - Claude Opus 4.6 +- `gpt-5.1-codex-max` - GPT-5.1 Codex Max +- `gpt-5.2-codex` - GPT-5.2 Codex + +**Custom Agent Configuration:** +For advanced use cases, you can specify custom agent IDs and instructions in the frontmatter. These apply to all agent assignments in the workflow: +- `custom-agent` - Custom agent identifier for specialized agent configurations +- `custom-instructions` - Instructions to guide the agent's behavior when working on the task + **Behavior:** - `target: "triggering"` - Auto-resolves from `github.event.issue.number` or `github.event.pull_request.number` - `target: "*"` - Requires explicit `issue_number` or `pull_number` in agent output diff --git a/pkg/cli/workflows/test-assign-to-agent-with-model.md b/pkg/cli/workflows/test-assign-to-agent-with-model.md new file mode 100644 index 0000000000..e2cc522efe --- /dev/null +++ b/pkg/cli/workflows/test-assign-to-agent-with-model.md @@ -0,0 +1,80 @@ +--- +name: Test Assign to Agent with Model +description: Test workflow for assign_to_agent safe output with model parameter +on: + issues: + types: [labeled] + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number to test with' + required: true + type: string + model: + description: 'AI model to use' + required: false + type: string + default: 'claude-opus-4.6' + +permissions: + actions: read + contents: read + issues: read + pull-requests: read + +# NOTE: Assigning Copilot agents requires: +# 1. A Personal Access Token (PAT) or GitHub App token with repo scope +# - The standard GITHUB_TOKEN does NOT have permission to assign bot agents +# - Create a PAT at: https://github.com/settings/tokens +# - Add it as a repository secret named GH_AW_AGENT_TOKEN +# - Required scopes: repo (full control) or fine-grained: actions, contents, issues, pull-requests (write) +# +# 2. All four workflow permissions declared above (for the safe output job) +# +# 3. Repository Settings > Actions > General > Workflow permissions: +# Must be set to "Read and write permissions" + +engine: copilot +timeout-minutes: 5 + +safe-outputs: + assign-to-agent: + max: 5 + name: copilot + model: claude-opus-4.6 # Default model to use when not specified per-item + target: "triggering" # Auto-resolves from workflow context (default) + allowed: [copilot] # Only allow copilot agent +strict: false +--- + +# Assign to Agent with Model Test Workflow + +This workflow tests the `assign_to_agent` safe output feature with the new `model` parameter. + +## Task + +Assign the Copilot agent to the issue with the specified AI model. The workflow demonstrates: + +1. **Default model configuration**: Set via `safe-outputs.assign-to-agent.model` in frontmatter (applies to all assignments in the workflow) + +**For issues event:** +Assign the Copilot agent using the default model (Claude Opus 4.6) to the triggering issue. + +**For workflow_dispatch:** +Assign the Copilot agent to issue #${{ github.event.inputs.issue_number }} using the default model configured in the frontmatter. + +Use the `assign_to_agent` tool from the `safeoutputs` MCP server: + +``` +assign_to_agent(issue_number=, agent="copilot") +``` + +The model is configured at the workflow level in the frontmatter and applies to all assignments. + +Available models include: +- `auto` - Auto-select model (default, currently Claude Sonnet 4.5) +- `claude-sonnet-4.5` - Claude Sonnet 4.5 +- `claude-opus-4.5` - Claude Opus 4.5 +- `claude-opus-4.6` - Claude Opus 4.6 +- `gpt-5.1-codex-max` - GPT-5.1 Codex Max +- `gpt-5.2-codex` - GPT-5.2 Codex diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index b5a7a65e02..6762504a75 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5360,6 +5360,18 @@ "type": "string", "description": "Default agent name to assign (default: 'copilot')" }, + "model": { + "type": "string", + "description": "Default AI model to use for the agent (e.g., 'auto', 'claude-sonnet-4.5', 'claude-opus-4.5', 'claude-opus-4.6', 'gpt-5.1-codex-max', 'gpt-5.2-codex'). Defaults to 'auto' if not specified." + }, + "custom-agent": { + "type": "string", + "description": "Default custom agent ID to use when assigning custom agents. This is used for specialized agent configurations beyond the standard Copilot agent." + }, + "custom-instructions": { + "type": "string", + "description": "Default custom instructions to provide to the agent. These instructions will guide the agent's behavior when working on the task." + }, "allowed": { "type": "array", "items": { diff --git a/pkg/workflow/assign_to_agent.go b/pkg/workflow/assign_to_agent.go index c86905a9a5..b196f9fbba 100644 --- a/pkg/workflow/assign_to_agent.go +++ b/pkg/workflow/assign_to_agent.go @@ -8,13 +8,16 @@ var assignToAgentLog = logger.New("workflow:assign_to_agent") // AssignToAgentConfig holds configuration for assigning agents to issues from agent output type AssignToAgentConfig struct { - BaseSafeOutputConfig `yaml:",inline"` - SafeOutputTargetConfig `yaml:",inline"` - DefaultAgent string `yaml:"name,omitempty"` // Default agent to assign (e.g., "copilot") - Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed agent names. If omitted, any agents are allowed. - IgnoreIfError bool `yaml:"ignore-if-error,omitempty"` // If true, workflow continues when agent assignment fails - PullRequestRepoSlug string `yaml:"pull-request-repo,omitempty"` // Target repository for PR creation in format "owner/repo" (where the issue lives may differ) - AllowedPullRequestRepos []string `yaml:"allowed-pull-request-repos,omitempty"` // List of additional repositories that PRs can be created in (beyond pull-request-repo which is automatically allowed) + BaseSafeOutputConfig `yaml:",inline"` + SafeOutputTargetConfig `yaml:",inline"` + DefaultAgent string `yaml:"name,omitempty"` // Default agent to assign (e.g., "copilot") + DefaultModel string `yaml:"model,omitempty"` // Default AI model to use (e.g., "claude-opus-4.6", "auto") + DefaultCustomAgent string `yaml:"custom-agent,omitempty"` // Default custom agent ID for custom agents + DefaultCustomInstructions string `yaml:"custom-instructions,omitempty"` // Default custom instructions for the agent + Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed agent names. If omitted, any agents are allowed. + IgnoreIfError bool `yaml:"ignore-if-error,omitempty"` // If true, workflow continues when agent assignment fails + PullRequestRepoSlug string `yaml:"pull-request-repo,omitempty"` // Target repository for PR creation in format "owner/repo" (where the issue lives may differ) + AllowedPullRequestRepos []string `yaml:"allowed-pull-request-repos,omitempty"` // List of additional repositories that PRs can be created in (beyond pull-request-repo which is automatically allowed) } // parseAssignToAgentConfig handles assign-to-agent configuration @@ -39,7 +42,8 @@ func (c *Compiler) parseAssignToAgentConfig(outputMap map[string]any) *AssignToA config.Max = 1 } - assignToAgentLog.Printf("Parsed assign-to-agent config: default_agent=%s, allowed_count=%d, target=%s, max=%d, pull_request_repo=%s", config.DefaultAgent, len(config.Allowed), config.Target, config.Max, config.PullRequestRepoSlug) + assignToAgentLog.Printf("Parsed assign-to-agent config: default_agent=%s, default_model=%s, default_custom_agent=%s, allowed_count=%d, target=%s, max=%d, pull_request_repo=%s", + config.DefaultAgent, config.DefaultModel, config.DefaultCustomAgent, len(config.Allowed), config.Target, config.Max, config.PullRequestRepoSlug) return &config } diff --git a/pkg/workflow/compiler_safe_outputs_specialized.go b/pkg/workflow/compiler_safe_outputs_specialized.go index b78454a9ec..0ecd9e32eb 100644 --- a/pkg/workflow/compiler_safe_outputs_specialized.go +++ b/pkg/workflow/compiler_safe_outputs_specialized.go @@ -26,6 +26,21 @@ func (c *Compiler) buildAssignToAgentStepConfig(data *WorkflowData, mainJobName customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_AGENT_DEFAULT: %q\n", cfg.DefaultAgent)) } + // Add default model environment variable + if cfg.DefaultModel != "" { + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_AGENT_DEFAULT_MODEL: %q\n", cfg.DefaultModel)) + } + + // Add default custom agent environment variable + if cfg.DefaultCustomAgent != "" { + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_AGENT_DEFAULT_CUSTOM_AGENT: %q\n", cfg.DefaultCustomAgent)) + } + + // Add default custom instructions environment variable + if cfg.DefaultCustomInstructions != "" { + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_AGENT_DEFAULT_CUSTOM_INSTRUCTIONS: %q\n", cfg.DefaultCustomInstructions)) + } + // Add target configuration environment variable if cfg.Target != "" { customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_AGENT_TARGET: %q\n", cfg.Target))