Add credit limitation support to Agentic Chat

What does this MR do?

Solves: https://gitlab.com/gitlab-org/gitlab/-/issues/581699

Adds credit limitation support to the Agentic Chat component, enabling the display of informational messages and UI blocking when GitLab credits are exhausted. This change is fully backwards compatible, all new props are optional with sensible defaults. Existing implementations will continue to work without any modifications.

Screenshot
Screenshot_2025-11-27_at_20.19.50

Demo:

To be found here: https://gitlab.com/gitlab-org/gitlab/-/issues/581064#note_2918469425

How to reproduce:

  1. Pull this branch into your GDK via the latest build package job. Example: https://gitlab.com/gitlab-org/duo-ui/-/jobs/12246657613
  2. Apply the following patch on your GDK to get it into a mocked "out of credits" state.
View diff
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 3bacc25fd07e..66cda0a52b2d 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
@@ -2,7 +2,7 @@
 // eslint-disable-next-line no-restricted-imports
 import { mapActions, mapState } from 'vuex';
 import { WebAgenticDuoChat } from '@gitlab/duo-ui';
-import { GlToggle, GlTooltipDirective } from '@gitlab/ui';
+import { GlButton, GlToggle, GlTooltipDirective } from '@gitlab/ui';
 import ChatLoadingState from 'ee/ai/components/chat_loading_state.vue';
 import getUserWorkflows from 'ee/ai/graphql/get_user_workflow.query.graphql';
 import getConfiguredAgents from 'ee/ai/graphql/get_configured_agents.query.graphql';
@@ -16,6 +16,7 @@ import { clearDuoChatCommands, setAgenticMode } from 'ee/ai/utils';
 import { convertToGraphQLId } from '~/graphql_shared/utils';
 import { TYPENAME_AI_DUO_WORKFLOW } from '~/graphql_shared/constants';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import tanukiAiSvgUrl from '@gitlab/svgs/dist/illustrations/tanuki-ai-sm.svg?url';
 import {
   GENIE_CHAT_RESET_MESSAGE,
   GENIE_CHAT_CLEAR_MESSAGE,
@@ -60,6 +61,7 @@ export default {
   name: 'DuoAgenticChatApp',
   components: {
     WebAgenticDuoChat,
+    GlButton,
     GlToggle,
     ModelSelectDropdown,
     ChatLoadingState,
@@ -278,6 +280,7 @@ export default {
       agentDeletedError: '',
       isChatAvailable: true,
       isEmbedded: this.chatConfiguration?.defaultProps?.isEmbedded ?? false,
+      tanukiAiSvgUrl,
       // this is required for classic/agentic toggle
       // eslint-disable-next-line vue/no-unused-properties
       isAgenticAvailable: this.chatConfiguration?.isAgenticAvailable ?? false,
@@ -512,6 +515,9 @@ export default {
         this.$emit('change-title', '');
       }
     },
+    turnOffAgenticMode() {
+      this.duoAgenticModePreference = false;
+    },
 
     cleanupSocket() {
       if (this.socketManager) {
@@ -885,6 +891,7 @@ export default {
       v-else
       id="duo-chat"
       ref="chat"
+      :is-globally-blocked="true"
       :title="dynamicTitle"
       :messages="messages"
       :is-loading="isWaitingOnPrompt"
@@ -904,6 +911,7 @@ export default {
       :is-tool-approval-processing="isProcessingToolApproval"
       :agents="agents"
       :is-chat-available="isChatAvailable"
+      info="No GitLab Credits remain for this billing period. Turn off the <strong>Agentic mode</strong> toggle or ask your administrator for more credits. <a href='#'>Learn more</a>."
       :error="multithreadedView === 'chat' ? agentDeletedError : ''"
       :should-auto-focus-input="!isEmbedded"
       class="gl-h-full gl-w-full"
@@ -918,7 +926,7 @@ export default {
       @back-to-list="onBackToList"
       @delete-thread="onDeleteThread"
     >
-      <template #agentic-model>
+      <!-- <template #agentic-model>
         <div
           v-if="userModelSelectionEnabled"
           v-gl-tooltip
@@ -936,7 +944,7 @@ export default {
             @select="onModelSelect"
           />
         </div>
-      </template>
+      </template> -->
       <template #agentic-switch>
         <gl-toggle v-model="duoAgenticModePreference" label-position="left" class="gl-h-5">
           <template #label>
@@ -946,6 +954,22 @@ export default {
           </template>
         </gl-toggle>
       </template>
+      <!-- <template #custom-empty-state>
+        <div key="no-credits-empty-state" class="duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-default gl-break-anywhere">
+          <div class="gl-flex gl-flex-col gl-items-start gl-justify-center gl-gap-4 gl-py-8">
+            <img :src="tanukiAiSvgUrl" alt="GitLab Duo" class="gl-h-10 gl-w-10" />
+            <h2 class="gl-my-0 gl-text-size-h2 gl-text-left gl-w-full gl-max-w-md">No GitLab Credits remain</h2>
+            <p class="gl-text-base gl-text-subtle gl-max-w-md gl-text-left">
+              No credits remain for this billing period. To continue collaborating with GitLab Duo, turn off the Agentic mode toggle. You can still get AI assistance, just without the advanced agentic features.
+              <a href="#" class="gl-link">Learn more</a>.
+            </p>
+            <p class="gl-text-sm gl-text-subtle gl-text-left gl-w-full gl-max-w-md">Need more credits? Contact your administrator.</p>
+            <gl-button variant="confirm" category="primary" @click="turnOffAgenticMode">
+              Turn off Agentic mode
+            </gl-button>
+          </div>
+        </div>
+      </template> -->
     </web-agentic-duo-chat>
   </div>
 </template>
diff --git a/package.json b/package.json
index 42b45249148f..236fc9608144 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
     "@gitlab/application-sdk-browser": "^0.3.4",
     "@gitlab/at.js": "1.5.7",
     "@gitlab/cluster-client": "^3.0.0",
-    "@gitlab/duo-ui": "^15.0.1",
+    "@gitlab/duo-ui": "https://gitlab.com/gitlab-org/duo-ui/-/jobs/12246085745/artifacts/raw/duo-ui.jnnkl-no-credits-remaining.tgz",
     "@gitlab/favicon-overlay": "2.0.0",
     "@gitlab/fonts": "^1.3.1",
     "@gitlab/query-language-rust": "0.20.9",
diff --git a/yarn.lock b/yarn.lock
index be6888e97ffc..f3530913e632 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1389,10 +1389,9 @@
     core-js "^3.29.1"
     mitt "^3.0.1"
 
-"@gitlab/duo-ui@^15.0.1":
+"@gitlab/duo-ui@https://gitlab.com/gitlab-org/duo-ui/-/jobs/12246085745/artifacts/raw/duo-ui.jnnkl-no-credits-remaining.tgz":
   version "15.0.1"
-  resolved "https://registry.yarnpkg.com/@gitlab/duo-ui/-/duo-ui-15.0.1.tgz#58259a338c1d0e27c9a23c0495c95073511b57ca"
-  integrity sha512-rRI+K8HT/t6MYWN54fW2e4/Am38b+Lgb6uqmF8dhCtXhtGrG8Kxw3UmBOd/q20+fO8OSh2/BIIvEWITbk4sI1Q==
+  resolved "https://gitlab.com/gitlab-org/duo-ui/-/jobs/12246085745/artifacts/raw/duo-ui.jnnkl-no-credits-remaining.tgz#76b947ba71aa4c1d2614bebd91e2cc2da16a87b5"
   dependencies:
     "@floating-ui/dom" "1.7.4"
     diff "^8.0.2"
@@ -1433,7 +1432,8 @@
   resolved "https://registry.yarnpkg.com/@gitlab/fonts/-/fonts-1.3.1.tgz#dd335ed25cb33927abc1d80dfbfa31fc93e9bd15"
   integrity sha512-/7Q5Sy9Q3pmutV/1EgURm3iDZtV0HcE4nTwFpHLCrGDdxQ3pNv6SdujzeJExnfiI5Y+1FGfOvKwOlmiVuyDtzA==
 
-"@gitlab/noop@^1.0.1":
+"@gitlab/noop@^1.0.1", jackspeak@^3.1.2, "jackspeak@npm:@gitlab/noop@1.0.1":
+  name jackspeak
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
   integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
@@ -9641,11 +9641,6 @@ istanbul-reports@^3.1.3:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-jackspeak@^3.1.2, "jackspeak@npm:@gitlab/noop@1.0.1":
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
-  integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
-
 jed@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
Edited by Jannik Lehmann

Merge request reports

Loading