Skip to content
Draft
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@ The following sets of tools are available:
- **assign_copilot_to_issue** - Assign Copilot to issue
- **Required OAuth Scopes**: `repo`
- `base_ref`: Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch (string, optional)
- `custom_instructions`: Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description (string, optional)
- `issue_number`: Issue number (number, required)
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)
Expand Down
4 changes: 4 additions & 0 deletions pkg/github/__toolsnaps__/assign_copilot_to_issue.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"type": "string",
"description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"
},
"custom_instructions": {
"type": "string",
"description": "Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description"
},
"issue_number": {
"type": "number",
"description": "Issue number"
Expand Down
19 changes: 15 additions & 4 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -1650,17 +1650,22 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
Type: "string",
Description: "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch",
},
"custom_instructions": {
Type: "string",
Description: "Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description",
},
},
Required: []string{"owner", "repo", "issue_number"},
},
},
[]scopes.Scope{scopes.Repo},
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
var params struct {
Owner string `mapstructure:"owner"`
Repo string `mapstructure:"repo"`
IssueNumber int32 `mapstructure:"issue_number"`
BaseRef string `mapstructure:"base_ref"`
Owner string `mapstructure:"owner"`
Repo string `mapstructure:"repo"`
IssueNumber int32 `mapstructure:"issue_number"`
BaseRef string `mapstructure:"base_ref"`
CustomInstructions string `mapstructure:"custom_instructions"`
}
if err := mapstructure.Decode(args, &params); err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
Expand Down Expand Up @@ -1775,6 +1780,12 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
agentAssignment.BaseRef = &baseRef
}

// Add custom instructions if provided
if params.CustomInstructions != "" {
customInstructions := githubv4.String(params.CustomInstructions)
agentAssignment.CustomInstructions = &customInstructions
}

// Execute the updateIssue mutation with the GraphQL-Features header
// This header is required for the agent assignment API which is not GA yet
var updateIssueMutation struct {
Expand Down
111 changes: 111 additions & 0 deletions pkg/github/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,7 @@ func TestAssignCopilotToIssue(t *testing.T) {
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "base_ref")
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "custom_instructions")
assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issue_number"})

// Helper function to create pointer to githubv4.String
Expand Down Expand Up @@ -2638,6 +2639,116 @@ func TestAssignCopilotToIssue(t *testing.T) {
),
),
},
{
name: "successful assignment with custom_instructions specified",
requestArgs: map[string]any{
"owner": "owner",
"repo": "repo",
"issue_number": float64(123),
"custom_instructions": "Please ensure all code follows PEP 8 style guidelines and includes comprehensive docstrings",
},
mockedClient: githubv4mock.NewMockedHTTPClient(
githubv4mock.NewQueryMatcher(
struct {
Repository struct {
SuggestedActors struct {
Nodes []struct {
Bot struct {
ID githubv4.ID
Login githubv4.String
TypeName string `graphql:"__typename"`
} `graphql:"... on Bot"`
}
PageInfo struct {
HasNextPage bool
EndCursor string
}
} `graphql:"suggestedActors(first: 100, after: $endCursor, capabilities: CAN_BE_ASSIGNED)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}{},
map[string]any{
"owner": githubv4.String("owner"),
"name": githubv4.String("repo"),
"endCursor": (*githubv4.String)(nil),
},
githubv4mock.DataResponse(map[string]any{
"repository": map[string]any{
"suggestedActors": map[string]any{
"nodes": []any{
map[string]any{
"id": githubv4.ID("copilot-swe-agent-id"),
"login": githubv4.String("copilot-swe-agent"),
"__typename": "Bot",
},
},
},
},
}),
),
githubv4mock.NewQueryMatcher(
struct {
Repository struct {
ID githubv4.ID
Issue struct {
ID githubv4.ID
Assignees struct {
Nodes []struct {
ID githubv4.ID
}
} `graphql:"assignees(first: 100)"`
} `graphql:"issue(number: $number)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}{},
map[string]any{
"owner": githubv4.String("owner"),
"name": githubv4.String("repo"),
"number": githubv4.Int(123),
},
githubv4mock.DataResponse(map[string]any{
"repository": map[string]any{
"id": githubv4.ID("test-repo-id"),
"issue": map[string]any{
"id": githubv4.ID("test-issue-id"),
"assignees": map[string]any{
"nodes": []any{},
},
},
},
}),
),
githubv4mock.NewMutationMatcher(
struct {
UpdateIssue struct {
Issue struct {
ID githubv4.ID
Number githubv4.Int
URL githubv4.String
}
} `graphql:"updateIssue(input: $input)"`
}{},
UpdateIssueInput{
ID: githubv4.ID("test-issue-id"),
AssigneeIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")},
AgentAssignment: &AgentAssignmentInput{
BaseRef: nil,
CustomAgent: ptrGitHubv4String(""),
CustomInstructions: ptrGitHubv4String("Please ensure all code follows PEP 8 style guidelines and includes comprehensive docstrings"),
TargetRepositoryID: githubv4.ID("test-repo-id"),
},
},
nil,
githubv4mock.DataResponse(map[string]any{
"updateIssue": map[string]any{
"issue": map[string]any{
"id": githubv4.ID("test-issue-id"),
"number": githubv4.Int(123),
"url": githubv4.String("https://github.com/owner/repo/issues/123"),
},
},
}),
),
),
},
}

for _, tc := range tests {
Expand Down