diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index ee3acb790d990b2713ad406e6239751b60da36aa..b336a70cd7574bbc59b075f41d0f7fe06415e18a 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -346,6 +346,7 @@ Returns [`ContextPreset`](#contextpreset).
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `forAgenticChat` | [`Boolean`](#boolean) | Set to true if information retrieved is for agentic chat. |
| `projectId` | [`ProjectID`](#projectid) | Global ID of the project the user is acting on. |
| `questionCount` | [`Int`](#int) | Number of questions for the default screen. |
| `resourceId` | [`AiModelID`](#aimodelid) | Global ID of the resource from the current page. |
diff --git a/ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat.vue b/ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat.vue
index bb194127064222548ef42002add9d6f64dd4f521..baafcba9edbe5df4d1dc282eee68b9caf71f346b 100644
--- a/ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat.vue
+++ b/ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat.vue
@@ -140,6 +140,7 @@ export default {
projectId: this.projectId,
url: typeof window !== 'undefined' && window.location ? window.location.href : '',
questionCount: 4,
+ forAgenticChat: true,
};
},
update(data) {
@@ -306,13 +307,21 @@ export default {
return this.contextPresets.questions || [];
},
additionalContext() {
- if (!this.contextPresets.aiResourceData) {
- return null;
+ // Build page context with current URL and title for base context
+ const contextParts = [
+ `${typeof window !== 'undefined' && window.location ? window.location.pathname : ''}`,
+ `${typeof document !== 'undefined' ? document.title : ''}`,
+ ];
+
+ if (this.contextPresets.aiResourceData) {
+ contextParts.push(this.contextPresets.aiResourceData);
}
+ const pageContext = contextParts.join('\n');
+
return [
{
- content: this.contextPresets.aiResourceData,
+ content: pageContext,
// This field depends on INCLUDE_{CATEGORY}_CONTEXT unit primitive:
// https://gitlab.com/gitlab-org/cloud-connector/gitlab-cloud-connector/-/blob/main/src/python/gitlab_cloud_connector/data_model/gitlab_unit_primitives.py?ref_type=heads#L37-47
// Since there is no unit primitives for all resource types and there is no a general one, let's use the one for repository
diff --git a/ee/app/assets/javascripts/ai/graphql/get_ai_chat_context_presets.query.graphql b/ee/app/assets/javascripts/ai/graphql/get_ai_chat_context_presets.query.graphql
index 195288765d1900783ef8191e3e362659ef116a5d..ed0ac3a22a7877f38ff90a662f2587b3e9ebae70 100644
--- a/ee/app/assets/javascripts/ai/graphql/get_ai_chat_context_presets.query.graphql
+++ b/ee/app/assets/javascripts/ai/graphql/get_ai_chat_context_presets.query.graphql
@@ -3,12 +3,14 @@ query getAiChatContextPresets(
$projectId: ProjectID
$url: String
$questionCount: Int
+ $forAgenticChat: Boolean
) {
aiChatContextPresets(
resourceId: $resourceId
projectId: $projectId
url: $url
questionCount: $questionCount
+ forAgenticChat: $forAgenticChat
) {
questions
aiResourceData
diff --git a/ee/app/graphql/resolvers/ai/chat/context_presets_resolver.rb b/ee/app/graphql/resolvers/ai/chat/context_presets_resolver.rb
index 705de658ac9212b079e30c286c695375a3fa27f0..ec7d104f9f4e68e84be57087569c9a1902d4bf0c 100644
--- a/ee/app/graphql/resolvers/ai/chat/context_presets_resolver.rb
+++ b/ee/app/graphql/resolvers/ai/chat/context_presets_resolver.rb
@@ -18,6 +18,11 @@ class ContextPresetsResolver < BaseResolver
required: false,
description: 'URL of the page the user is currently on.'
+ argument :for_agentic_chat,
+ GraphQL::Types::Boolean,
+ required: false,
+ description: 'Set to true if information retrieved is for agentic chat.'
+
argument :resource_id,
::Types::GlobalIDType[::Ai::Model],
required: false,
@@ -27,14 +32,14 @@ class ContextPresetsResolver < BaseResolver
required: false,
description: "Global ID of the project the user is acting on."
- def resolve(url: nil, resource_id: nil, project_id: nil, question_count: 4)
+ def resolve(url: nil, resource_id: nil, project_id: nil, question_count: 4, for_agentic_chat: false)
ai_resource = find_ai_resource(resource_id, project_id)
questions = ::Gitlab::Duo::Chat::DefaultQuestions.new(current_user, url: url, resource: ai_resource)
.execute
{
questions: questions.sample(question_count),
- ai_resource_data: ai_resource&.serialize_for_ai&.to_json
+ ai_resource_data: ai_resource&.serialize_for_ai(for_agentic_chat: for_agentic_chat)&.to_json
}
end
diff --git a/ee/app/models/ai/ai_resource/base_ai_resource.rb b/ee/app/models/ai/ai_resource/base_ai_resource.rb
index 875c6917eb646ccf247a73959b6f4bea290bff5d..51c98afa6b9eed0c10572aa1a256acee78bb3e77 100644
--- a/ee/app/models/ai/ai_resource/base_ai_resource.rb
+++ b/ee/app/models/ai/ai_resource/base_ai_resource.rb
@@ -15,7 +15,7 @@ def initialize(user, resource)
@current_user = user
end
- def serialize_for_ai(_content_limit: default_content_limit)
+ def serialize_for_ai(_content_limit: default_content_limit, _for_agentic_chat: false)
raise NotImplementedError
end
diff --git a/ee/app/models/ai/ai_resource/commit.rb b/ee/app/models/ai/ai_resource/commit.rb
index 6fed7969a66ac2479f181a746209ed3b2c448426..cb00619ed78453d63af5966684e0dece9aab144a 100644
--- a/ee/app/models/ai/ai_resource/commit.rb
+++ b/ee/app/models/ai/ai_resource/commit.rb
@@ -14,7 +14,9 @@ class Commit < Ai::AiResource::BaseAiResource
CHAT_UNIT_PRIMITIVE = :ask_commit
- def serialize_for_ai(content_limit: default_content_limit)
+ def serialize_for_ai(content_limit: default_content_limit, for_agentic_chat: false)
+ return {} if for_agentic_chat
+
EE::CommitSerializer # rubocop:disable CodeReuse/Serializer -- existing serializer
.new(current_user: current_user, project: resource.project)
.represent(resource, {
diff --git a/ee/app/models/ai/ai_resource/epic.rb b/ee/app/models/ai/ai_resource/epic.rb
index 1f57afac643b44322ba892d6e9e231907e257dff..eb0f69e0a1d3815fb2bbecb42f0cc0da162e2b67 100644
--- a/ee/app/models/ai/ai_resource/epic.rb
+++ b/ee/app/models/ai/ai_resource/epic.rb
@@ -14,7 +14,9 @@ class Epic < Ai::AiResource::BaseAiResource
CHAT_UNIT_PRIMITIVE = :ask_epic
- def serialize_for_ai(content_limit: default_content_limit)
+ def serialize_for_ai(content_limit: default_content_limit, for_agentic_chat: false)
+ return {} if for_agentic_chat
+
::EpicSerializer.new(current_user: current_user) # rubocop: disable CodeReuse/Serializer
.represent(resource, {
user: current_user,
diff --git a/ee/app/models/ai/ai_resource/issue.rb b/ee/app/models/ai/ai_resource/issue.rb
index a8648e4b05ef7ef933b7059a0f32225b8e6adf42..9b234b34efdf3c0fa3269241b52390b8f1e0e77c 100644
--- a/ee/app/models/ai/ai_resource/issue.rb
+++ b/ee/app/models/ai/ai_resource/issue.rb
@@ -14,7 +14,9 @@ class Issue < Ai::AiResource::BaseAiResource
CHAT_UNIT_PRIMITIVE = :ask_issue
- def serialize_for_ai(content_limit: default_content_limit)
+ def serialize_for_ai(content_limit: default_content_limit, for_agentic_chat: false)
+ return {} if for_agentic_chat
+
::IssueSerializer.new(current_user: current_user, project: resource.project) # rubocop: disable CodeReuse/Serializer
.represent(resource, {
user: current_user,
diff --git a/ee/app/models/ai/ai_resource/merge_request.rb b/ee/app/models/ai/ai_resource/merge_request.rb
index db9829479a75c7f007102467b58d3943b5777f8e..b9a1587a800f7b34b4bd26efcd792da28ad1c45b 100644
--- a/ee/app/models/ai/ai_resource/merge_request.rb
+++ b/ee/app/models/ai/ai_resource/merge_request.rb
@@ -14,7 +14,9 @@ class MergeRequest < Ai::AiResource::BaseAiResource
CHAT_UNIT_PRIMITIVE = :ask_merge_request
- def serialize_for_ai(content_limit: default_content_limit, is_duo_code_review: false)
+ def serialize_for_ai(content_limit: default_content_limit, is_duo_code_review: false, for_agentic_chat: false)
+ return {} if for_agentic_chat
+
::MergeRequestSerializer.new(current_user: current_user) # rubocop: disable CodeReuse/Serializer -- existing serializer
.represent(resource, {
user: current_user,
diff --git a/ee/app/models/ai/ai_resource/work_item.rb b/ee/app/models/ai/ai_resource/work_item.rb
index d8cf0fc8ca743409e00a7796266cdf403e227333..610271dc20623f526214a6b49ed831d56da4d1e5 100644
--- a/ee/app/models/ai/ai_resource/work_item.rb
+++ b/ee/app/models/ai/ai_resource/work_item.rb
@@ -3,7 +3,9 @@
module Ai
module AiResource
class WorkItem < Ai::AiResource::Issue
- def serialize_for_ai(content_limit: default_content_limit)
+ def serialize_for_ai(content_limit: default_content_limit, for_agentic_chat: false)
+ return {} if for_agentic_chat
+
synced_epic = resource.synced_epic
if synced_epic
::EpicSerializer.new(current_user: current_user) # rubocop: disable CodeReuse/Serializer -- we need to serialize resource here
diff --git a/ee/spec/frontend/ai/duo_agentic_chat/components/duo_agentic_chat_spec.js b/ee/spec/frontend/ai/duo_agentic_chat/components/duo_agentic_chat_spec.js
index 19977ddf1e35f7ff72ead9f836dca4fd8eb7a1ea..977c8e29ae888f4c19970471eda511659e93a801 100644
--- a/ee/spec/frontend/ai/duo_agentic_chat/components/duo_agentic_chat_spec.js
+++ b/ee/spec/frontend/ai/duo_agentic_chat/components/duo_agentic_chat_spec.js
@@ -209,7 +209,9 @@ const MOCK_UTILS_SETUP = () => {
const expectedAdditionalContext = [
{
- content: MOCK_AI_RESOURCE_DATA,
+ content: `/
+
+${MOCK_AI_RESOURCE_DATA}`,
category: DUO_WORKFLOW_ADDITIONAL_CONTEXT_REPOSITORY,
metadata: JSON.stringify({}),
},
@@ -402,6 +404,7 @@ describe('Duo Agentic Chat', () => {
it('calls the context presets GraphQL query when component loads', () => {
expect(contextPresetsQueryHandlerMock).toHaveBeenCalledWith({
+ forAgenticChat: true,
projectId: MOCK_PROJECT_ID,
resourceId: MOCK_RESOURCE_ID,
url: 'http://test.host/',
diff --git a/ee/spec/models/ai/ai_resource/base_ai_resource_spec.rb b/ee/spec/models/ai/ai_resource/base_ai_resource_spec.rb
index fe6c12d6c91307b95d9083332f119e4d26017270..74663dc33a59638e8d8256a6010edf28e0a7e27f 100644
--- a/ee/spec/models/ai/ai_resource/base_ai_resource_spec.rb
+++ b/ee/spec/models/ai/ai_resource/base_ai_resource_spec.rb
@@ -5,7 +5,7 @@
RSpec.describe Ai::AiResource::BaseAiResource, feature_category: :duo_chat do
describe '#serialize_for_ai' do
it 'raises NotImplementedError' do
- expect { described_class.new(nil, nil).serialize_for_ai(_content_limit: nil) }
+ expect { described_class.new(nil, nil).serialize_for_ai(_content_limit: nil, _for_agentic_chat: nil) }
.to raise_error(NotImplementedError)
end
end
diff --git a/ee/spec/models/ai/ai_resource/epic_spec.rb b/ee/spec/models/ai/ai_resource/epic_spec.rb
index 2a77646c74f63b30be506c956d0646f17262456d..9bf22926eb9a32d9809e7bd3af551dd2db5ce866 100644
--- a/ee/spec/models/ai/ai_resource/epic_spec.rb
+++ b/ee/spec/models/ai/ai_resource/epic_spec.rb
@@ -9,17 +9,26 @@
subject(:wrapped_epic) { described_class.new(user, epic) }
describe '#serialize_for_ai' do
- it 'calls the serializations class' do
- expect(EpicSerializer).to receive_message_chain(:new, :represent)
- .with(current_user: user)
- .with(epic, {
- user: user,
- notes_limit: 100,
- serializer: 'ai',
- resource: wrapped_epic
- })
+ context 'when for_agentic_chat is true' do
+ it 'returns empty hash' do
+ result = wrapped_epic.serialize_for_ai(for_agentic_chat: true)
+ expect(result).to eq({})
+ end
+ end
+
+ context 'when for_agentic_chat is false or omitted' do
+ it 'calls the serializations class' do
+ expect(EpicSerializer).to receive_message_chain(:new, :represent)
+ .with(current_user: user)
+ .with(epic, {
+ user: user,
+ notes_limit: 100,
+ serializer: 'ai',
+ resource: wrapped_epic
+ })
- wrapped_epic.serialize_for_ai(content_limit: 100)
+ wrapped_epic.serialize_for_ai(content_limit: 100)
+ end
end
end
diff --git a/ee/spec/models/ai/ai_resource/issue_spec.rb b/ee/spec/models/ai/ai_resource/issue_spec.rb
index 1dad522b48b3c2b0da78be0c19a16acc3557bc17..d7c99caf292c7f96a7b9dfb7fee634b07807bee8 100644
--- a/ee/spec/models/ai/ai_resource/issue_spec.rb
+++ b/ee/spec/models/ai/ai_resource/issue_spec.rb
@@ -9,16 +9,25 @@
subject(:wrapped_issue) { described_class.new(user, issue) }
describe '#serialize_for_ai' do
- it 'calls the serializations class' do
- expect(::IssueSerializer).to receive_message_chain(:new, :represent)
- .with(current_user: user, project: issue.project)
- .with(issue, {
- user: user,
- notes_limit: 100,
- serializer: 'ai',
- resource: wrapped_issue
- })
- wrapped_issue.serialize_for_ai(content_limit: 100)
+ context 'when for_agentic_chat is true' do
+ it 'returns empty hash' do
+ result = wrapped_issue.serialize_for_ai(for_agentic_chat: true)
+ expect(result).to eq({})
+ end
+ end
+
+ context 'when for_agentic_chat is false or omitted' do
+ it 'calls the serializations class' do
+ expect(::IssueSerializer).to receive_message_chain(:new, :represent)
+ .with(current_user: user, project: issue.project)
+ .with(issue, {
+ user: user,
+ notes_limit: 100,
+ serializer: 'ai',
+ resource: wrapped_issue
+ })
+ wrapped_issue.serialize_for_ai(content_limit: 100)
+ end
end
end
diff --git a/ee/spec/models/ai/ai_resource/merge_request_spec.rb b/ee/spec/models/ai/ai_resource/merge_request_spec.rb
index 7fee06ce702fecb7313fb9cf1c5bf10e88ccd052..ef3ed8977bb88c53caeca32bf02654c4343637ba 100644
--- a/ee/spec/models/ai/ai_resource/merge_request_spec.rb
+++ b/ee/spec/models/ai/ai_resource/merge_request_spec.rb
@@ -9,32 +9,41 @@
subject(:wrapped_merge_request) { described_class.new(user, merge_request) }
describe '#serialize_for_ai' do
- it 'calls the serializations class' do
- expect(MergeRequestSerializer).to receive_message_chain(:new, :represent)
- .with(current_user: user)
- .with(merge_request, {
- user: user,
- notes_limit: 100,
- serializer: 'ai',
- resource: wrapped_merge_request,
- is_duo_code_review: false
- })
-
- wrapped_merge_request.serialize_for_ai(content_limit: 100)
+ context 'when for_agentic_chat is true' do
+ it 'returns empty hash' do
+ result = wrapped_merge_request.serialize_for_ai(for_agentic_chat: true)
+ expect(result).to eq({})
+ end
end
- it 'passes is_duo_code_review parameter to serializer' do
- expect(MergeRequestSerializer).to receive_message_chain(:new, :represent)
- .with(current_user: user)
- .with(merge_request, {
- user: user,
- notes_limit: 100,
- serializer: 'ai',
- resource: wrapped_merge_request,
- is_duo_code_review: true
- })
-
- wrapped_merge_request.serialize_for_ai(content_limit: 100, is_duo_code_review: true)
+ context 'when for_agentic_chat is false or omitted' do
+ it 'calls the serializations class' do
+ expect(MergeRequestSerializer).to receive_message_chain(:new, :represent)
+ .with(current_user: user)
+ .with(merge_request, {
+ user: user,
+ notes_limit: 100,
+ serializer: 'ai',
+ resource: wrapped_merge_request,
+ is_duo_code_review: false
+ })
+
+ wrapped_merge_request.serialize_for_ai(content_limit: 100)
+ end
+
+ it 'passes is_duo_code_review parameter to serializer' do
+ expect(MergeRequestSerializer).to receive_message_chain(:new, :represent)
+ .with(current_user: user)
+ .with(merge_request, {
+ user: user,
+ notes_limit: 100,
+ serializer: 'ai',
+ resource: wrapped_merge_request,
+ is_duo_code_review: true
+ })
+
+ wrapped_merge_request.serialize_for_ai(content_limit: 100, is_duo_code_review: true)
+ end
end
end
diff --git a/ee/spec/models/ai/ai_resource/work_item_spec.rb b/ee/spec/models/ai/ai_resource/work_item_spec.rb
index 0b6ac508c6f4f6f63a2b2fed29a1c9e4b8c886b2..00b6cde578523fb88dc20694a74305532d5bd44b 100644
--- a/ee/spec/models/ai/ai_resource/work_item_spec.rb
+++ b/ee/spec/models/ai/ai_resource/work_item_spec.rb
@@ -9,43 +9,52 @@
subject(:wrapped_work_item) { described_class.new(user, work_item) }
describe '#serialize_for_ai' do
- context 'when issue is synced with epic' do
- let(:epic) { build(:epic) }
+ context 'when for_agentic_chat is true' do
+ it 'returns empty hash' do
+ result = wrapped_work_item.serialize_for_ai(for_agentic_chat: true)
+ expect(result).to eq({})
+ end
+ end
+
+ context 'when for_agentic_chat is false or omitted' do
+ context 'when issue is synced with epic' do
+ let(:epic) { build(:epic) }
+
+ before do
+ work_item.synced_epic = epic
+ end
- before do
- work_item.synced_epic = epic
+ it 'calls the epics serializations class' do
+ expect(::EpicSerializer).to receive_message_chain(:new, :represent)
+ .with(current_user: user)
+ .with(epic, {
+ user: user,
+ notes_limit: 100,
+ serializer: 'ai',
+ resource: wrapped_work_item
+ })
+ wrapped_work_item.serialize_for_ai(content_limit: 100)
+ end
end
- it 'calls the epics serializations class' do
- expect(::EpicSerializer).to receive_message_chain(:new, :represent)
- .with(current_user: user)
- .with(epic, {
- user: user,
- notes_limit: 100,
- serializer: 'ai',
- resource: wrapped_work_item
- })
+ it 'calls the serializations class' do
+ expect(::IssueSerializer).to receive_message_chain(:new, :represent)
+ .with(current_user: user, project: work_item.project)
+ .with(work_item, {
+ user: user,
+ notes_limit: 100,
+ serializer: 'ai',
+ resource: wrapped_work_item
+ })
wrapped_work_item.serialize_for_ai(content_limit: 100)
end
- end
-
- it 'calls the serializations class' do
- expect(::IssueSerializer).to receive_message_chain(:new, :represent)
- .with(current_user: user, project: work_item.project)
- .with(work_item, {
- user: user,
- notes_limit: 100,
- serializer: 'ai',
- resource: wrapped_work_item
- })
- wrapped_work_item.serialize_for_ai(content_limit: 100)
- end
- context 'when content_limit is omitted' do
- let(:work_item) { create(:work_item) }
+ context 'when content_limit is omitted' do
+ let(:work_item) { create(:work_item) }
- it 'does not raise error' do
- expect { wrapped_work_item.serialize_for_ai }.not_to raise_error
+ it 'does not raise error' do
+ expect { wrapped_work_item.serialize_for_ai }.not_to raise_error
+ end
end
end
end