Skip to content

Commit dc87dde

Browse files
authored
feat : Add support for custom agent to be selected when assigning Copilot to an issue
1 parent 3b8ff50 commit dc87dde

4 files changed

Lines changed: 127 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ The following sets of tools are available:
735735
- **assign_copilot_to_issue** - Assign Copilot to issue
736736
- **Required OAuth Scopes**: `repo`
737737
- `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)
738+
- `custom_agent`: Optional custom agent slug to use instead of the default Copilot coding agent (string, optional)
738739
- `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)
739740
- `issue_number`: Issue number (number, required)
740741
- `owner`: Repository owner (string, required)

pkg/github/__toolsnaps__/assign_copilot_to_issue.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
"description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch",
2424
"type": "string"
2525
},
26+
"custom_agent": {
27+
"description": "Optional custom agent slug to use instead of the default Copilot coding agent",
28+
"type": "string"
29+
},
2630
"custom_instructions": {
2731
"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",
2832
"type": "string"

pkg/github/copilot.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
196196
Type: "string",
197197
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",
198198
},
199+
"custom_agent": {
200+
Type: "string",
201+
Description: "Optional custom agent slug to use instead of the default Copilot coding agent",
202+
},
199203
},
200204
Required: []string{"owner", "repo", "issue_number"},
201205
},
@@ -208,6 +212,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
208212
IssueNumber int32 `mapstructure:"issue_number"`
209213
BaseRef string `mapstructure:"base_ref"`
210214
CustomInstructions string `mapstructure:"custom_instructions"`
215+
CustomAgent string `mapstructure:"custom_agent"`
211216
}
212217
if err := mapstructure.WeakDecode(args, &params); err != nil {
213218
return utils.NewToolResultError(err.Error()), nil, nil
@@ -328,6 +333,12 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
328333
agentAssignment.CustomInstructions = &customInstructions
329334
}
330335

336+
// Add custom agent if provided
337+
if params.CustomAgent != "" {
338+
customAgent := githubv4.String(params.CustomAgent)
339+
agentAssignment.CustomAgent = &customAgent
340+
}
341+
331342
// Execute the updateIssue mutation with the GraphQL-Features header
332343
// This header is required for the agent assignment API which is not GA yet
333344
var updateIssueMutation struct {

pkg/github/copilot_test.go

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func TestAssignCopilotToIssue(t *testing.T) {
3232
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
3333
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "base_ref")
3434
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "custom_instructions")
35+
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "custom_agent")
3536
assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issue_number"})
3637

3738
// Helper function to create pointer to githubv4.String
@@ -802,8 +803,116 @@ func TestAssignCopilotToIssue(t *testing.T) {
802803
}),
803804
),
804805
),
805-
},
806-
}
806+
}, {
807+
name: "successful assignment with custom_agent specified",
808+
requestArgs: map[string]any{
809+
"owner": "owner",
810+
"repo": "repo",
811+
"issue_number": float64(123),
812+
"custom_agent": "my-custom-agent",
813+
},
814+
mockedClient: githubv4mock.NewMockedHTTPClient(
815+
githubv4mock.NewQueryMatcher(
816+
struct {
817+
Repository struct {
818+
SuggestedActors struct {
819+
Nodes []struct {
820+
Bot struct {
821+
ID githubv4.ID
822+
Login githubv4.String
823+
TypeName string `graphql:"__typename"`
824+
} `graphql:"... on Bot"`
825+
}
826+
PageInfo struct {
827+
HasNextPage bool
828+
EndCursor string
829+
}
830+
} `graphql:"suggestedActors(first: 100, after: $endCursor, capabilities: CAN_BE_ASSIGNED)"`
831+
} `graphql:"repository(owner: $owner, name: $name)"`
832+
}{},
833+
map[string]any{
834+
"owner": githubv4.String("owner"),
835+
"name": githubv4.String("repo"),
836+
"endCursor": (*githubv4.String)(nil),
837+
},
838+
githubv4mock.DataResponse(map[string]any{
839+
"repository": map[string]any{
840+
"suggestedActors": map[string]any{
841+
"nodes": []any{
842+
map[string]any{
843+
"id": githubv4.ID("copilot-swe-agent-id"),
844+
"login": githubv4.String("copilot-swe-agent"),
845+
"__typename": "Bot",
846+
},
847+
},
848+
},
849+
},
850+
}),
851+
),
852+
githubv4mock.NewQueryMatcher(
853+
struct {
854+
Repository struct {
855+
ID githubv4.ID
856+
Issue struct {
857+
ID githubv4.ID
858+
Assignees struct {
859+
Nodes []struct {
860+
ID githubv4.ID
861+
}
862+
} `graphql:"assignees(first: 100)"`
863+
} `graphql:"issue(number: $number)"`
864+
} `graphql:"repository(owner: $owner, name: $name)"`
865+
}{},
866+
map[string]any{
867+
"owner": githubv4.String("owner"),
868+
"name": githubv4.String("repo"),
869+
"number": githubv4.Int(123),
870+
},
871+
githubv4mock.DataResponse(map[string]any{
872+
"repository": map[string]any{
873+
"id": githubv4.ID("test-repo-id"),
874+
"issue": map[string]any{
875+
"id": githubv4.ID("test-issue-id"),
876+
"assignees": map[string]any{
877+
"nodes": []any{},
878+
},
879+
},
880+
},
881+
}),
882+
),
883+
githubv4mock.NewMutationMatcher(
884+
struct {
885+
UpdateIssue struct {
886+
Issue struct {
887+
ID githubv4.ID
888+
Number githubv4.Int
889+
URL githubv4.String
890+
}
891+
} `graphql:"updateIssue(input: $input)"`
892+
}{},
893+
UpdateIssueInput{
894+
ID: githubv4.ID("test-issue-id"),
895+
AssigneeIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")},
896+
AgentAssignment: &AgentAssignmentInput{
897+
BaseRef: nil,
898+
CustomAgent: ptrGitHubv4String("my-custom-agent"),
899+
CustomInstructions: ptrGitHubv4String(""),
900+
TargetRepositoryID: githubv4.ID("test-repo-id"),
901+
},
902+
},
903+
nil,
904+
githubv4mock.DataResponse(map[string]any{
905+
"updateIssue": map[string]any{
906+
"issue": map[string]any{
907+
"id": githubv4.ID("test-issue-id"),
908+
"number": githubv4.Int(123),
909+
"url": githubv4.String("https://github.com/owner/repo/issues/123"),
910+
},
911+
},
912+
}),
913+
),
914+
),
915+
}}
807916

808917
for _, tc := range tests {
809918
t.Run(tc.name, func(t *testing.T) {

0 commit comments

Comments
 (0)