diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md index 568acb76e5f1ffd08f19df54d5070adf1a179f86..a97e4ad8adb04754593fc73e9aceff0359be3c83 100644 --- a/doc/api/api_resources.md +++ b/doc/api/api_resources.md @@ -148,56 +148,57 @@ The following API resources are available in the group context: The following API resources are available outside of project and group contexts (including `/users`): -| Resource | Available endpoints | -|:----------------------------------------------------------------------------------------|:--------------------| -| [Appearance](appearance.md) **(FREE SELF)** | `/application/appearance` | -| [Applications](applications.md) | `/applications` | -| [Audit Events](audit_events.md) **(PREMIUM SELF)** | `/audit_events` | -| [Avatar](avatar.md) | `/avatar` | -| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` | -| [Code snippets](snippets.md) | `/snippets` | -| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | -| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | -| [Deploy tokens](deploy_tokens.md) | `/deploy_tokens` (also available for projects and groups) | -| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | -| [Feature flags](features.md) | `/features` | -| [Geo Nodes](geo_nodes.md) **(PREMIUM SELF)** | `/geo_nodes` | -| [Group Activity Analytics](group_activity_analytics.md) | `/analytics/group_activity/{issues_count}` | -| [Group repository storage moves](group_repository_storage_moves.md) **(PREMIUM SELF)** | `/group_repository_storage_moves` | -| [Import repository from GitHub](import.md#import-repository-from-github) | `/import/github` | -| [Import repository from Bitbucket Server](import.md#import-repository-from-bitbucket-server) | `/import/bitbucket_server` | -| [Instance clusters](instance_clusters.md) **(FREE SELF)** | `/admin/clusters` | -| [Instance-level CI/CD variables](instance_level_ci_variables.md) **(FREE SELF)** | `/admin/ci/variables` | -| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) | -| [Issues](issues.md) | `/issues` (also available for groups and projects) | -| [Jobs](jobs.md) | `/job` | -| [Keys](keys.md) | `/keys` | -| [License](license.md) **(FREE SELF)** | `/license` | -| [Markdown](markdown.md) | `/markdown` | -| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) | -| [Metrics dashboard annotations](metrics_dashboard_annotations.md) | `/environments/:id/metrics_dashboard/annotations`, `/clusters/:id/metrics_dashboard/annotations` | -| [Namespaces](namespaces.md) | `/namespaces` | -| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | -| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) | -| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` | -| [Plan limits](plan_limits.md) | `/application/plan_limits` | -| [Project repository storage moves](project_repository_storage_moves.md) **(FREE SELF)** | `/project_repository_storage_moves` | -| [Projects](projects.md) | `/users/:id/projects` (also available for projects) | -| [Runners](runners.md) | `/runners` (also available for projects) | -| [Search](search.md) | `/search` (also available for groups and projects) | -| [Service Data](usage_data.md) | `/usage_data` (For GitLab instance [Administrator](../user/permissions.md) users only) | -| [Settings](settings.md) **(FREE SELF)** | `/application/settings` | -| [Sidekiq metrics](sidekiq_metrics.md) **(FREE SELF)** | `/sidekiq` | -| [Sidekiq queues administration](admin_sidekiq_queues.md) **(FREE SELF)** | `/admin/sidekiq/queues/:queue_name` | -| [Snippet repository storage moves](snippet_repository_storage_moves.md) **(FREE SELF)** | `/snippet_repository_storage_moves` | -| [Statistics](statistics.md) | `/application/statistics` | -| [Suggestions](suggestions.md) | `/suggestions` | -| [System hooks](system_hooks.md) | `/hooks` | -| [To-dos](todos.md) | `/todos` | -| [Topics](topics.md) | `/topics` | -| [Users](users.md) | `/users` | -| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` | -| [Version](version.md) | `/version` | +| Resource | Available endpoints | +|:---------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------| +| [Appearance](appearance.md) **(FREE SELF)** | `/application/appearance` | +| [Applications](applications.md) | `/applications` | +| [Audit Events](audit_events.md) **(PREMIUM SELF)** | `/audit_events` | +| [Avatar](avatar.md) | `/avatar` | +| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` | +| [Code snippets](snippets.md) | `/snippets` | +| [Code suggestions](code_suggestions.md) | `/code_suggestions` | +| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | +| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | +| [Deploy tokens](deploy_tokens.md) | `/deploy_tokens` (also available for projects and groups) | +| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | +| [Feature flags](features.md) | `/features` | +| [Geo Nodes](geo_nodes.md) **(PREMIUM SELF)** | `/geo_nodes` | +| [Group Activity Analytics](group_activity_analytics.md) | `/analytics/group_activity/{issues_count}` | +| [Group repository storage moves](group_repository_storage_moves.md) **(PREMIUM SELF)** | `/group_repository_storage_moves` | +| [Import repository from GitHub](import.md#import-repository-from-github) | `/import/github` | +| [Import repository from Bitbucket Server](import.md#import-repository-from-bitbucket-server) | `/import/bitbucket_server` | +| [Instance clusters](instance_clusters.md) **(FREE SELF)** | `/admin/clusters` | +| [Instance-level CI/CD variables](instance_level_ci_variables.md) **(FREE SELF)** | `/admin/ci/variables` | +| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) | +| [Issues](issues.md) | `/issues` (also available for groups and projects) | +| [Jobs](jobs.md) | `/job` | +| [Keys](keys.md) | `/keys` | +| [License](license.md) **(FREE SELF)** | `/license` | +| [Markdown](markdown.md) | `/markdown` | +| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) | +| [Metrics dashboard annotations](metrics_dashboard_annotations.md) | `/environments/:id/metrics_dashboard/annotations`, `/clusters/:id/metrics_dashboard/annotations` | +| [Namespaces](namespaces.md) | `/namespaces` | +| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | +| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) | +| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` | +| [Plan limits](plan_limits.md) | `/application/plan_limits` | +| [Project repository storage moves](project_repository_storage_moves.md) **(FREE SELF)** | `/project_repository_storage_moves` | +| [Projects](projects.md) | `/users/:id/projects` (also available for projects) | +| [Runners](runners.md) | `/runners` (also available for projects) | +| [Search](search.md) | `/search` (also available for groups and projects) | +| [Service Data](usage_data.md) | `/usage_data` (For GitLab instance [Administrator](../user/permissions.md) users only) | +| [Settings](settings.md) **(FREE SELF)** | `/application/settings` | +| [Sidekiq metrics](sidekiq_metrics.md) **(FREE SELF)** | `/sidekiq` | +| [Sidekiq queues administration](admin_sidekiq_queues.md) **(FREE SELF)** | `/admin/sidekiq/queues/:queue_name` | +| [Snippet repository storage moves](snippet_repository_storage_moves.md) **(FREE SELF)** | `/snippet_repository_storage_moves` | +| [Statistics](statistics.md) | `/application/statistics` | +| [Suggestions](suggestions.md) | `/suggestions` | +| [System hooks](system_hooks.md) | `/hooks` | +| [To-dos](todos.md) | `/todos` | +| [Topics](topics.md) | `/topics` | +| [Users](users.md) | `/users` | +| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` | +| [Version](version.md) | `/version` | ## Templates API resources diff --git a/doc/api/code_suggestions.md b/doc/api/code_suggestions.md new file mode 100644 index 0000000000000000000000000000000000000000..8057686897f79e71009df58e41ea2a917bd0ab52 --- /dev/null +++ b/doc/api/code_suggestions.md @@ -0,0 +1,81 @@ +--- +stage: Manage +group: AI assisted +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Code Suggestions API + +Use the Code Suggestions API to access the Code Suggestions feature. + +## Create an access token + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404427) in GitLab 16.1. + +Creates an access token to access Code Suggestions. + +```plaintext +POST /code_suggestions/tokens +``` + +```shell +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/code_suggestions/tokens" +``` + +Example response: + +```json +{ + "access_token": "secret-access-token", + "expires_in": 3600, + "created_at": 1687865199 +} +``` + +## Generate code completions (Experiment) + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/415581) in GitLab 16.2 [with a flag](../administration/feature_flags.md) named `code_suggestions_completion_api`. Disabled by default. This feature is an Experiment. + +FLAG: +On self-managed GitLab, by default this feature is not available. +On GitLab.com, this feature is not available. +This feature is not ready for production use. + +Use the AI abstraction layer to generate code completions. + +```plaintext +POST /code_suggestions/completions +``` + +Requests to this endpoint are proxied directly to the [model gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist#completions). The documentation for the endpoint is currently the SSoT for named parameters. + +Authentication to this endpoint requires both a GitLab access token and a Code Suggestions JWT. The access token is used to authenticate the user and the JWT is used to authenticate the request to the model gateway. + +```shell +curl --header "Authorization: Bearer <YOUR_ACCESS_TOKEN>" --header "X-Gitlab-Oidc-Token: <TOKEN_GENERATED_FROM_TOKENS_ENDPOINT>" --data "<JSON_BODY>" https://gitlab.example.com/api/v4/code_suggestions/completions +``` + +Example body: + +The [model gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist#completions) is the SSoT for parameters. + +Example response: + +```json +{ + "id": "id", + "model": { + "engine": "vertex-ai", + "name": "code-gecko" + }, + "object": "text_completion", + "created": 1688557841, + "choices": [ + { + "text": "\n if self.is_running:\n self.speed += increment\n print(\"The car's speed is now", + "index": 0, + "finish_reason": "length" + } + ] +} +``` diff --git a/ee/config/feature_flags/development/code_suggestions_completion_api.yml b/ee/config/feature_flags/development/code_suggestions_completion_api.yml new file mode 100644 index 0000000000000000000000000000000000000000..45215762ed96e0e90261a5f1d0ef0b35dc51ef7a --- /dev/null +++ b/ee/config/feature_flags/development/code_suggestions_completion_api.yml @@ -0,0 +1,8 @@ +--- +name: code_suggestions_completion_api +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125401 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/416371 +milestone: '16.2' +type: development +group: group::ai-enablement +default_enabled: false diff --git a/ee/lib/api/code_suggestions.rb b/ee/lib/api/code_suggestions.rb index fd6b5ab8a338c54914789185f0228b7f1545cdef..bb139b549713e3de0172b77660a8a5b3667901ca 100644 --- a/ee/lib/api/code_suggestions.rb +++ b/ee/lib/api/code_suggestions.rb @@ -18,6 +18,22 @@ def user_allowed? current_user.can?(:access_code_suggestions) && access_code_suggestions_when_proxied_to_saas? end + def model_gateway_headers(headers) + token = headers["X-Gitlab-Oidc-Token"] + + telemetry_headers = { + 'X-GitLab-CS-Accepts' => headers['X-Gitlab-Cs-Accepts'], + 'X-GitLab-CS-Requests' => headers['X-Gitlab-Cs-Requests'], + 'X-GitLab-CS-Errors' => headers['X-Gitlab-Cs-Errors'] + }.compact + + { + 'X-Gitlab-Authentication-Type' => 'oidc', + 'Authorization' => "Bearer #{token}", + 'Content-Type' => 'application/json' + }.merge(telemetry_headers) + end + # In case the request was proxied from the self-managed instance, # we have an extra check on Gitlab.com if FF is enabled for self-managed admin. # The FF is used for gradual rollout for handpicked self-managed customers interested to use code suggestions. @@ -64,6 +80,22 @@ def gitlab_realm end end end + + resources :completions do + post do + not_found! unless ::Feature.enabled?(:code_suggestions_completion_api, current_user) + + response = ::Gitlab::HTTP.post('https://codesuggestions.gitlab.com/v2/completions', { + body: params.except(:private_token).to_json, + headers: model_gateway_headers(headers), + open_timeout: 3, + read_timeout: 5, + write_timeout: 5 + }) + status response.code + response + end + end end end end diff --git a/ee/spec/requests/api/code_suggestions_spec.rb b/ee/spec/requests/api/code_suggestions_spec.rb index 7860a572814a673508aca3abc18b863631bb6937..aaad1f03adeef989b3dab1fafc65863002e3863a 100644 --- a/ee/spec/requests/api/code_suggestions_spec.rb +++ b/ee/spec/requests/api/code_suggestions_spec.rb @@ -161,4 +161,122 @@ end end end + + describe 'POST /code_suggestions/completions' do + let(:access_code_suggestions) { true } + let(:headers) do + { + 'X-Gitlab-Authentication-Type' => 'oidc', + 'X-Gitlab-Oidc-Token' => "JWTTOKEN", + 'Content-Type' => 'application/json' + } + end + + let(:body) do + { + prompt_version: 1, + project_path: "gitlab-org/gitlab-shell", + project_id: 33191677, + current_file: { + file_name: "test.py", + content_above_cursor: "def is_even(n: int) ->", + content_below_cursor: "" + } + } + end + + subject(:post_api) do + post api('/code_suggestions/completions', current_user), headers: headers, params: body.to_json + end + + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :access_code_suggestions, :global) + .and_return(access_code_suggestions) + allow(Gitlab).to receive(:org_or_com?).and_return(true) + end + + context 'when user is not logged in' do + let(:current_user) { nil } + + include_examples 'an unauthorized response' + end + + context 'when user does not have access to code suggestions' do + let(:access_code_suggestions) { false } + + include_examples 'an unauthorized response' + end + + context 'when user is logged in' do + let(:current_user) { create(:user) } + + it 'proxies request to code suggestions service' do + expect(Gitlab::HTTP).to receive(:post).with( + "https://codesuggestions.gitlab.com/v2/completions", + { + body: body.to_json, + headers: { + 'X-Gitlab-Authentication-Type' => 'oidc', + 'Authorization' => 'Bearer JWTTOKEN', + 'Content-Type' => 'application/json' + }, + open_timeout: 3, + read_timeout: 5, + write_timeout: 5 + } + ) + + post_api + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(code_suggestions_completion_api: false) + end + + include_examples 'a response', 'not found' do + let(:result) { :not_found } + let(:body) do + { "message" => "404 Not Found" } + end + end + end + + context 'with telemetry headers' do + let(:headers) do + { + 'X-Gitlab-Authentication-Type' => 'oidc', + 'X-Gitlab-Oidc-Token' => "JWTTOKEN", + 'Content-Type' => 'application/json', + 'X-GitLab-CS-Accepts' => 'accepts', + 'X-GitLab-CS-Requests' => "requests", + 'X-GitLab-CS-Errors' => 'errors' + } + end + + it 'proxies request to code suggestions service' do + expect(Gitlab::HTTP).to receive(:post).with( + "https://codesuggestions.gitlab.com/v2/completions", + { + body: body.to_json, + headers: { + 'X-Gitlab-Authentication-Type' => 'oidc', + 'Authorization' => 'Bearer JWTTOKEN', + 'Content-Type' => 'application/json', + 'X-GitLab-CS-Accepts' => 'accepts', + 'X-GitLab-CS-Requests' => "requests", + 'X-GitLab-CS-Errors' => 'errors' + }, + open_timeout: 3, + read_timeout: 5, + write_timeout: 5 + } + ) + + post_api + end + end + end + end end