Skip to content

Commit 0c00ca4

Browse files
committed
Merge branch 'main' into tommy/bug-fix-invalid-tool-should-error-out
2 parents 739f39a + f62ff63 commit 0c00ca4

File tree

10 files changed

+321
-86
lines changed

10 files changed

+321
-86
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ The following sets of tools are available:
751751

752752
- **assign_copilot_to_issue** - Assign Copilot to issue
753753
- **Required OAuth Scopes**: `repo`
754+
- `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)
754755
- `issue_number`: Issue number (number, required)
755756
- `owner`: Repository owner (string, required)
756757
- `repo`: Repository name (string, required)

internal/ghmcp/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,12 @@ type bearerAuthTransport struct {
625625
func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
626626
req = req.Clone(req.Context())
627627
req.Header.Set("Authorization", "Bearer "+t.token)
628+
629+
// Check for GraphQL-Features in context and add header if present
630+
if features := github.GetGraphQLFeatures(req.Context()); len(features) > 0 {
631+
req.Header.Set("GraphQL-Features", strings.Join(features, ", "))
632+
}
633+
628634
return t.transport.RoundTrip(req)
629635
}
630636

pkg/github/__toolsnaps__/assign_copilot_to_issue.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"inputSchema": {
88
"type": "object",
99
"properties": {
10+
"base_ref": {
11+
"type": "string",
12+
"description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"
13+
},
1014
"issue_number": {
1115
"type": "number",
1216
"description": "Issue number"

pkg/github/issues.go

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,10 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
16461646
Type: "number",
16471647
Description: "Issue number",
16481648
},
1649+
"base_ref": {
1650+
Type: "string",
1651+
Description: "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch",
1652+
},
16491653
},
16501654
Required: []string{"owner", "repo", "issue_number"},
16511655
},
@@ -1656,6 +1660,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
16561660
Owner string `mapstructure:"owner"`
16571661
Repo string `mapstructure:"repo"`
16581662
IssueNumber int32 `mapstructure:"issue_number"`
1663+
BaseRef string `mapstructure:"base_ref"`
16591664
}
16601665
if err := mapstructure.Decode(args, &params); err != nil {
16611666
return utils.NewToolResultError(err.Error()), nil, nil
@@ -1724,10 +1729,10 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
17241729
return utils.NewToolResultError("copilot isn't available as an assignee for this issue. Please inform the user to visit https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot for more information."), nil, nil
17251730
}
17261731

1727-
// Next let's get the GQL Node ID and current assignees for this issue because the only way to
1728-
// assign copilot is to use replaceActorsForAssignable which requires the full list.
1732+
// Next, get the issue ID and repository ID
17291733
var getIssueQuery struct {
17301734
Repository struct {
1735+
ID githubv4.ID
17311736
Issue struct {
17321737
ID githubv4.ID
17331738
Assignees struct {
@@ -1749,30 +1754,54 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
17491754
return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to get issue ID", err), nil, nil
17501755
}
17511756

1752-
// Finally, do the assignment. Just for reference, assigning copilot to an issue that it is already
1753-
// assigned to seems to have no impact (which is a good thing).
1754-
var assignCopilotMutation struct {
1755-
ReplaceActorsForAssignable struct {
1756-
Typename string `graphql:"__typename"` // Not required but we need a selector or GQL errors
1757-
} `graphql:"replaceActorsForAssignable(input: $input)"`
1758-
}
1759-
1757+
// Build the assignee IDs list including copilot
17601758
actorIDs := make([]githubv4.ID, len(getIssueQuery.Repository.Issue.Assignees.Nodes)+1)
17611759
for i, node := range getIssueQuery.Repository.Issue.Assignees.Nodes {
17621760
actorIDs[i] = node.ID
17631761
}
17641762
actorIDs[len(getIssueQuery.Repository.Issue.Assignees.Nodes)] = copilotAssignee.ID
17651763

1764+
// Prepare agent assignment input
1765+
emptyString := githubv4.String("")
1766+
agentAssignment := &AgentAssignmentInput{
1767+
CustomAgent: &emptyString,
1768+
CustomInstructions: &emptyString,
1769+
TargetRepositoryID: getIssueQuery.Repository.ID,
1770+
}
1771+
1772+
// Add base ref if provided
1773+
if params.BaseRef != "" {
1774+
baseRef := githubv4.String(params.BaseRef)
1775+
agentAssignment.BaseRef = &baseRef
1776+
}
1777+
1778+
// Execute the updateIssue mutation with the GraphQL-Features header
1779+
// This header is required for the agent assignment API which is not GA yet
1780+
var updateIssueMutation struct {
1781+
UpdateIssue struct {
1782+
Issue struct {
1783+
ID githubv4.ID
1784+
Number githubv4.Int
1785+
URL githubv4.String
1786+
}
1787+
} `graphql:"updateIssue(input: $input)"`
1788+
}
1789+
1790+
// Add the GraphQL-Features header for the agent assignment API
1791+
// The header will be read by the HTTP transport if it's configured to do so
1792+
ctxWithFeatures := withGraphQLFeatures(ctx, "issues_copilot_assignment_api_support")
1793+
17661794
if err := client.Mutate(
1767-
ctx,
1768-
&assignCopilotMutation,
1769-
ReplaceActorsForAssignableInput{
1770-
AssignableID: getIssueQuery.Repository.Issue.ID,
1771-
ActorIDs: actorIDs,
1795+
ctxWithFeatures,
1796+
&updateIssueMutation,
1797+
UpdateIssueInput{
1798+
ID: getIssueQuery.Repository.Issue.ID,
1799+
AssigneeIDs: actorIDs,
1800+
AgentAssignment: agentAssignment,
17721801
},
17731802
nil,
17741803
); err != nil {
1775-
return nil, nil, fmt.Errorf("failed to replace actors for assignable: %w", err)
1804+
return nil, nil, fmt.Errorf("failed to update issue with agent assignment: %w", err)
17761805
}
17771806

17781807
return utils.NewToolResultText("successfully assigned copilot to issue"), nil, nil
@@ -1784,6 +1813,21 @@ type ReplaceActorsForAssignableInput struct {
17841813
ActorIDs []githubv4.ID `json:"actorIds"`
17851814
}
17861815

1816+
// AgentAssignmentInput represents the input for assigning an agent to an issue.
1817+
type AgentAssignmentInput struct {
1818+
BaseRef *githubv4.String `json:"baseRef,omitempty"`
1819+
CustomAgent *githubv4.String `json:"customAgent,omitempty"`
1820+
CustomInstructions *githubv4.String `json:"customInstructions,omitempty"`
1821+
TargetRepositoryID githubv4.ID `json:"targetRepositoryId"`
1822+
}
1823+
1824+
// UpdateIssueInput represents the input for updating an issue with agent assignment.
1825+
type UpdateIssueInput struct {
1826+
ID githubv4.ID `json:"id"`
1827+
AssigneeIDs []githubv4.ID `json:"assigneeIds"`
1828+
AgentAssignment *AgentAssignmentInput `json:"agentAssignment,omitempty"`
1829+
}
1830+
17871831
// parseISOTimestamp parses an ISO 8601 timestamp string into a time.Time object.
17881832
// Returns the parsed time or an error if parsing fails.
17891833
// Example formats supported: "2023-01-15T14:30:00Z", "2023-01-15"
@@ -1869,3 +1913,19 @@ func AssignCodingAgentPrompt(t translations.TranslationHelperFunc) inventory.Ser
18691913
},
18701914
)
18711915
}
1916+
1917+
// graphQLFeaturesKey is a context key for GraphQL feature flags
1918+
type graphQLFeaturesKey struct{}
1919+
1920+
// withGraphQLFeatures adds GraphQL feature flags to the context
1921+
func withGraphQLFeatures(ctx context.Context, features ...string) context.Context {
1922+
return context.WithValue(ctx, graphQLFeaturesKey{}, features)
1923+
}
1924+
1925+
// GetGraphQLFeatures retrieves GraphQL feature flags from the context
1926+
func GetGraphQLFeatures(ctx context.Context) []string {
1927+
if features, ok := ctx.Value(graphQLFeaturesKey{}).([]string); ok {
1928+
return features
1929+
}
1930+
return nil
1931+
}

0 commit comments

Comments
 (0)