Skip to content

feat: bring the Ask AI assistant to the community edition - #42065

Merged
salevine merged 11 commits into
releasefrom
feat/ask-ai-ce-port
Aug 4, 2026
Merged

feat: bring the Ask AI assistant to the community edition#42065
salevine merged 11 commits into
releasefrom
feat/ask-ai-ce-port

Conversation

@salevine

@salevine salevine commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

TL;DR — Ask AI (AI-assisted code editing in the JS/query editors, plus an admin page to configure the AI provider) currently exists only in the enterprise edition. Nothing about it is actually enterprise-specific, so this brings it to CE. The code is ported from appsmith-ee/release unchanged where it already lives in ce packages, and moved from src/ee into src/ce where it does not.

Background

Ask AI was originally built for CE on feat/enable-ai (PR #41590). That PR was handed over for review in March, went quiet, and was closed by the stale bot on 2026-04-03 without ever being merged. The feature instead shipped in the enterprise repo as appsmith-ee#8845, and CE received only inert stubs via #41692ce/selectors/aiAssistantSelectors.ts and friends returning null / false / [].

That split was not driven by any technical requirement:

  • ee/selectors/aiAssistantSelectors.ts contains no license or entitlement check — it reads state.aiAssistant and nothing else.
  • Enablement is ordinary organization configuration (AIAssistantConfig.isAIAssistantEnabled), set by an instance admin.
  • The feature flag that once gated it was removed wholesale in appsmith-ee#9119.

The enterprise-only placement was a product decision, and this PR reverses it.

Why port from EE rather than revive the original branch

feat/enable-ai is four months stale and EE has since reworked the implementation:

feat/enable-ai appsmith-ee/release
AI settings storage flat fields on OrganizationConfiguration nested AIAssistantConfig document
Provider dispatch inline if chain extracted dispatchToProvider
Datasource schema enrichment client-side server-side AiDatasourceSchemaSerializerCE
/ai-config endpoints ~1450 lines inlined into OrganizationControllerCE dedicated AIConfigControllerCE + AIConfigServiceCE
AIReferenceServiceCEImpl 239 lines 107 lines

Reviving the branch would land a divergent second implementation and guarantee a conflict with the community sync. Porting EE's current version starts CE and EE byte-identical on the ce-package files. Note the security commit and the review fixes on top of it deliberately move CE ahead of EE, so the sync is no longer a no-op — see CE→EE sync: required resolution below for the exact steps.

Architecture

Server — follows the existing controller → ce service → ce_compatible → ee override layering, all in ce packages:

  • AIConfigControllerCE / AIConfigController for /ai-config (test-connection, fetch-models, test-api-key), each gated on MANAGE_ORGANIZATION
  • AIConfigServiceCE(Impl), AIAssistantServiceCE(Impl), AIReferenceServiceCE(Impl) with their ee override points and AIConfigServiceCECompatible(Impl)
  • AIAssistantConfig on OrganizationConfiguration, Migration075, and the AIProvider / DTO types
  • POST /users/ai-assistant/request on UserControllerCE
  • ai-references/*.md prompt-reference resources

Client — the implementation moves from src/ee into src/ce, which is where a CE-owned feature belongs. This is safe because the EE UI files import nothing EE-only; every import is a package, a shared path, or an ee/ alias that resolves through CE's shims. The existing src/ee shims from #41692 are untouched and now re-export real code instead of stubs, and the exported symbol surface of every relocated file is unchanged. The AI reducer and saga are registered in ce/reducers and ce/sagas rather than their ee counterparts, and the admin AI settings page is registered for superusers.

This PR adds no src/ee files, because the CE pre-push architecture guard rejects them. Four CE-owned modules therefore have no ee shim to import through — aiAssistantReducer, AIAssistantSagas, GPT/shared, and the admin AI config — so they are imported from ce/ directly, each with a documented eslint-disable for no-restricted-imports. That is the same accommodation the native Custom Widget copilot uses in #42063. If EE would rather route these through ee/ shims, those shims belong in a companion EE PR.

Adds react-markdown and remark-gfm, used by the assistant's response renderer.

Impact on existing instances

Inert by default. AIAssistantConfig is absent until an admin configures a provider, isAIAssistantEnabled defaults to false, and Migration075 only adds the field. With nothing configured, the Ask AI affordances stay hidden exactly as they do today — no feature flag is involved, matching how EE ships it since #9119.

Security fixes included

A nine-reviewer council on the ported code surfaced four defects. All four are inherited byte-identical from EE and are therefore live in EE production today; because EE's AIConfigServiceImpl delegates every method to the CE class and AIAssistantServiceImpl overrides nothing, fixing them here carries them into EE through the sync rather than needing a parallel EE change.

  1. SSRF, two call sites. callLocalLLMAPI built a raw WebClient, bypassing WebClientUtils and substituting a check that tested only isLinkLocalAddress on the first resolved address — missing loopback, and racy. callAzureOpenAIAPI chained .clientConnector(...) after WebClientUtils.builder(), which replaces the connector carrying the DNS-aware resolver; it read as protected and was not. Both now build through WebClientUtils.builder(httpClient).
  2. getAIConfig authorization. It was the only one of five service methods without MANAGE_ORGANIZATION, disclosing localLlmUrl, azureOpenaiEndpoint and the deployment name to any authenticated user, on every session. Managers still get the full configuration; everyone else gets enablement, provider, and credential-presence booleans — exactly what the client consumes.
  3. API keys stored in cleartext. The @Encrypted annotations never applied — the traversal only descends into AppsmithDomain types, and the write is a sparse updateById so the encrypting lifecycle listener never fires. AIConfigSecretsCE now encrypts and decrypts at the few write/read points, and Migration076 encrypts existing values in place, idempotently.
  4. Admin key field corrupted credentials. A stored key loaded into the input as the literal •••••••• with a save guard comparing against that mask, so typing without clearing persisted ••••••••sk-… behind a success toast.

Behaviour change worth calling out: a local-LLM URL pointing at loopback is now refused. In the single-container CE deployment 127.0.0.1 is Mongo, Redis and RTS rather than the operator's Ollama — which is the reason to refuse it. A local model on another host or container stays reachable by hostname or private IP. This also makes the runtime path agree with /ai-config/test-connection, which already went through WebClientUtils.

Follow-ups (tracked, not addressed here)

  1. Unmetered LLM spend/users/ai-assistant/request has no rate limit or per-user quota; on an open-signup CE instance any account can drain the admin's provider billing.
  2. OpenAI provider lacks guards the others have — no empty/max-length prompt validation and no max_tokens.
  3. /users/ai-assistant/request maps every failure to 400, including upstream timeouts, which hurts monitoring.
  4. A measured 0.5–1.0 s reactive-thread stall in AiDatasourceSchemaSerializerCE.extractReferencedTableNames at the DTO's own size ceilings.
  5. Dead code carried from EEAIWindow and the in-editor AISidePanel have no importer, and ce/utils/aiSchemaSerializer.ts has no consumer but its own test.
  6. Test coverageAIConfigSecretsCE and Migration076 both route through EncryptionHelper, whose static initialiser needs APPSMITH_ENCRYPTION_PASSWORD/SALT. Those are set for the integration-test and Docker CI jobs but not for server-unit-tests, so this coverage belongs in the integration suite.

A follow-up in appsmith-ee should reduce EE's src/ee Ask AI files to re-export shims and drop its ee/reducers + ee/sagas registration, so EE consumes this CE implementation instead of shadowing it.

https://linear.app/appsmith/issue/APP-15737

Supersedes the original, stale-closed CE attempt in #41590.

Automation

/ok-to-test tags="@tag.All"

🔍 Cypress test results

Tip

🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/30836147916
Commit: deab697
Cypress dashboard.
Tags: @tag.All
Spec:


Mon, 03 Aug 2026 18:47:45 UTC

Communication

Should the DevRel and Marketing teams inform users about this change?

  • Yes
  • No

Ask AI becoming available in the community edition is a user-facing change worth announcing.

CE→EE sync: required resolution

This section is load-bearing. The sync of this PR is not a no-op, and two of its failure modes arrive through clean merges — no conflict marker will surface them. Whoever runs the sync should follow this, and it is the condition the architecture review set for unblocking.

Of the changed files that also exist in EE, 18 differ from EE's copy. Most are the deliberate hardening in the security commit, which moves CE ahead of EE on purpose. Five are add/add conflicts (ce/sagas/AIAssistantSagas.ts, ce/pages/AdminSettings/config/ai.tsx, pages/AdminSettings/AI/index.tsx, AIAssistantServiceCEImpl.java, AIConfigServiceCEImpl.java).

These steps are for the sync itself. None of them can be pre-landed in EE as a separate PR — verified against appsmith-ee/origin/release (0aff44fa56, "Sync community release"):

  • The saga. EE's ce/sagas/index.tsx does not register the AI saga yet — only ee/sagas/index.tsx does. Removing EE's registration before the CE change syncs would leave zero registrations and take Ask AI out of EE entirely. It is only safe once CE's registration has arrived.
  • The enum. EE's ASK_AI_ORG_CONFIG_UPDATED / ASK_AI_ORG_TEST_RUN are referenced by AIConfigServiceCEImpl (lines 172-173, 236) and its test. Deleting them ahead of the sync breaks EE's compile. This is a conflict resolution, not a change that exists independently.

So this is work for whoever runs the sync, in the same merge — not a companion PR that can land first.

1. Remove EE's now-duplicate saga registration

CE now registers the AI saga itself, and that CE file merges cleanly into EE — so EE ends up registering it twice:

EE file What to do
app/client/src/ee/sagas/index.tsx Remove the aiAssistantSagas import (from ee/sagas/AIAssistantSagas, a shim that re-exports ce/sagas/AIAssistantSagas) and its entry in the saga array — CE's registration now covers EE

Left as-is, EE runs the same watcher generator twice, so every Ask AI action fires duplicate requests to the provider — double latency and double spend.

This one genuinely needs an EE change: EE builds sagasArr as [...CE_Sagas, …, aiAssistantSagas], appending its own entry after spreading CE's list, so CE cannot deduplicate it from its side.

The duplicate admin category no longer needs an EE change. ConfigFactory.register is CE-owned and was a raw push into three collections (categories, settings, savableCategories — only settingsMap was keyed and therefore safe). It is now idempotent, so a category registered from both ce/ and ee/ collapses to one entry on its own. EE's ConfigFactory.register(AIConfig) can stay exactly as it is.

2. Resolve the five add/add conflicts toward CE, wholesale

Migration076EncryptAIAssistantApiKeys merges cleanly and will encrypt EE's stored keys on first boot. EE's current AIAssistantServiceCEImpl / AIConfigServiceCEImpl read those keys without AIConfigSecretsCE.decrypt. If either file is resolved toward EE's copy, EE sends ciphertext as its Authorization header and Ask AI breaks in EE.

pages/AdminSettings/AI/index.tsx must also move together with AIConfigServiceCEImpl — the client's hasStoredX model depends on the server's has* response shape and the manager-only full config.

3. Collapse the duplicated analytics enum

AnalyticsEvents.java: CE adds ASK_AI_ORG_CONFIG_UPDATED / ASK_AI_ORG_TEST_RUN at the enum tail (lines 106/109); EE already has both at lines 140/143. Naive resolution produces duplicate enum constants and fails to compile. Keep one pair.

Correction to an earlier claim in this description

An earlier revision said porting from EE keeps the two editions "byte-identical on the ce-package files, so the sync stays a no-op". That was true of the initial port and is no longer true: the security commit, and the review fixes on top of it, deliberately move CE ahead. The byte-identity argument still holds for the ~40 untouched ported files and for the reason to port rather than revive feat/enable-ai, but the sync itself needs the three steps above.

Summary by CodeRabbit

  • New Features
    • Added Ask AI assistance for JavaScript, SQL, GraphQL, and JSON editing.
    • Added resizable chat panels with conversation history, editor context, Markdown responses, quick actions, and keyboard shortcuts.
    • Added a global AI assistant panel with context-aware prompts and schema support.
    • Added administrator settings for Claude, OpenAI, Azure OpenAI, and local Ollama-compatible providers.
    • Added connection, credential, and model testing with secure credential handling and request safeguards.
  • Documentation
    • Added reference guides for JavaScript, SQL, GraphQL, and common troubleshooting scenarios.
  • Tests
    • Added coverage for schema handling and AI configuration workflows.

Ask AI (AI-assisted code editing in the JS/query editors, plus an admin
page to configure the provider) shipped as an EE-only feature in
appsmith-ee#8845, while CE received only inert stubs in #41692. Nothing
about the feature is actually enterprise-specific: enablement is ordinary
organization configuration set by an instance admin, and the EE client
selectors carry no license or entitlement check. This ports it to CE.

The implementation is taken from appsmith-ee/release as-is rather than from
the original CE branch (feat/enable-ai, closed as #41590), because EE has
since refactored it: AI settings moved from flat OrganizationConfiguration
fields into a nested AIAssistantConfig document, provider dispatch was
extracted, and datasource-schema enrichment moved from the client to
AiDatasourceSchemaSerializerCE on the server. Porting EE's current version
keeps CE and EE byte-identical on the ce-package files so the community
sync stays a no-op.

Server, all in ce packages behind the existing controller -> ce service ->
ce_compatible -> ee override layering:

  - AIConfigControllerCE / AIConfigController for the /ai-config endpoints
    (test-connection, fetch-models, test-api-key), each gated on
    MANAGE_ORGANIZATION
  - AIConfigServiceCE(Impl), AIAssistantServiceCE(Impl),
    AIReferenceServiceCE(Impl) and their ee override points
  - AIAssistantConfig on OrganizationConfiguration, Migration075, and the
    AIProvider/DTO types
  - POST /users/ai-assistant/request on UserControllerCE
  - ai-references/*.md prompt reference resources

Client: the implementation moves from src/ee into src/ce, since a CE-owned
feature belongs there and the EE files had no EE-only imports. The existing
src/ee shims from #41692 are left untouched and now re-export real code
instead of stubs; this commit adds no src/ee files at all, because the CE
pre-push architecture guard forbids it. The modules that therefore have no
ee shim to import through (aiAssistantReducer, AIAssistantSagas, GPT/shared,
the admin AI config) are imported from ce/ directly, each with a documented
eslint-disable for no-restricted-imports — the same accommodation used by
the native Custom Widget copilot in #42063. The AI reducer and saga are
registered in ce/reducers and ce/sagas rather than their ee counterparts,
and the admin AI settings page is registered for superusers.

Adds react-markdown and remark-gfm, used by the assistant's response
renderer.

Verified: yarn tsc --noEmit introduces no new errors (the 7 reported in
packages/ast and packages/design-system are present on an unmodified
release checkout); prettier clean; eslint 0 errors (44 pre-existing-style
react-perf warnings carried over from EE); jest
src/ce/utils/aiSchemaSerializer.test.ts 20/20; mvn spotless:check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBx1z6GCvod1ENrBMVMcj7
@salevine salevine added the ok-to-test Required label for CI label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The change adds the Ask AI assistant across the client and server. It adds provider configuration, encrypted credentials, AI requests, Redux state, editor and global panels, administrator settings, schema serialization, reference content, migrations, rate limiting, and analytics.

Changes

Ask AI assistant

Layer / File(s) Summary
Server contracts and configuration
app/server/appsmith-server/src/main/java/com/appsmith/server/...
Adds validated AI models, organization configuration, encrypted credentials, REST endpoints, service adapters, analytics, rate limits, migrations, and configuration tests.
Provider response and context services
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/..., app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/..., app/server/appsmith-server/src/main/resources/ai-references/*
Adds provider-specific requests for Claude, OpenAI, Azure OpenAI, Copilot, and local LLMs. Adds reference loading, schema prioritization, response parsing, and error mapping.
Client state and editor interaction
app/client/src/ce/api/..., app/client/src/ce/reducers/..., app/client/src/ce/sagas/..., app/client/src/ce/components/editorComponents/...
Adds AI APIs, Redux state, sagas, selectors, slash commands, editor context, resizable panels, quick actions, Markdown responses, loading states, errors, and conversation history.
Administrator configuration interface
app/client/src/pages/AdminSettings/AI/..., app/client/src/ce/pages/AdminSettings/config/...
Adds organization settings for providers, credentials, models, endpoints, local LLM diagnostics, reference files, enablement, and saving.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • appsmithorg/appsmith-ee#9395 — Covers the CE Ask AI backport and its client/server assistant implementation.

Possibly related PRs

Suggested labels: Security, UI Building Product

Suggested reviewers: subrata71

Poem

Ask AI opens the editor panel,
Redux carries each request.
Providers return formatted text,
Schemas add database context.
Credentials remain encrypted,
Markdown renders each response.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary change: bringing the Ask AI assistant to the Community Edition.
Description check ✅ Passed The description is detailed and covers motivation, architecture, security, testing, communication, dependencies, and synchronization requirements.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ask-ai-ce-port

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the Enhancement New feature or request label Jul 29, 2026
The server did not compile. AIConfigServiceCEImpl references
AnalyticsEvents.ASK_AI_ORG_CONFIG_UPDATED and ASK_AI_ORG_TEST_RUN, which
exist in the enterprise copy of the enum but had never been added to CE's,
so the port failed with "cannot find symbol" and a cascading Mono<Object>
to Mono<Void> inference error in the same method. Both constants are added
with the same names and event strings the enterprise repo uses, so the
files stay aligned for the community sync.

Also ports AIConfigServiceCEImplTest, which the enterprise repo carries and
the first pass missed. It covers the config-save analytics payload, the
API-key and local-LLM test-run events, and the skip path when analytics is
inactive — exactly the code the missing constants sat in.

Verified: mvn -pl appsmith-server -am compile clean; AIConfigServiceCEImplTest
7/7 and AiDatasourceSchemaSerializerCETest 5/5 green; spotless clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBx1z6GCvod1ENrBMVMcj7
@linear-code

linear-code Bot commented Jul 29, 2026

Copy link
Copy Markdown

APP-15737

… Ask AI

Four defects found by review of the ported code. All four are inherited
byte-identical from the enterprise repo and are therefore live there too;
because EE's AIConfigServiceImpl delegates every method to the CE class and
AIAssistantServiceImpl overrides nothing, fixing them here carries them to
EE through the community sync rather than needing a parallel EE change.

SSRF, two call sites. callLocalLLMAPI built a raw WebClient, bypassing
WebClientUtils entirely, and substituted a hand-rolled check that tested
only isLinkLocalAddress on the first resolved address — missing loopback,
and racy besides, since the address it checked was not the one the client
went on to resolve and connect to. callAzureOpenAIAPI looked protected but
was not: it chained .clientConnector(...) after WebClientUtils.builder(),
which replaces the connector that carries the DNS-aware resolver, leaving
only the literal host check. Both now build through
WebClientUtils.builder(httpClient). The 16 MB buffer the local path set by
hand is already WebClientUtils' default, so that override is gone.

Note the behaviour change this implies: an admin-supplied local LLM URL
pointing at loopback is now refused, where before it was reachable. In the
single-container CE deployment 127.0.0.1 is Mongo, Redis and RTS rather
than the operator's Ollama, which is the reason to refuse it; a local model
on another host or container remains reachable by hostname or private IP.
This also makes the runtime path agree with /ai-config/test-connection,
which already went through WebClientUtils — previously "Test connection"
could fail against a URL the assistant would happily call.

Authorization. getAIConfig was the only one of the five service methods
without a MANAGE_ORGANIZATION check, so any authenticated user — including
a viewer — could read localLlmUrl, azureOpenaiEndpoint, the deployment name
and model identifiers, and the editor fetches this on every session. It now
returns the full configuration to organization managers and, to everyone
else, only what the client actually consumes: enablement, provider, and
credential-presence booleans. hasLocalLlmUrl is added for that purpose so
the URL itself no longer has to be sent.

Credential storage. The @Encrypted annotations on AIAssistantConfig never
applied: the encryption traversal only descends into AppsmithDomain types
and both OrganizationConfigurationCE and AIAssistantConfig are plain
Serializable, and separately the write is a sparse updateById rather than
an entity save, so the lifecycle listener that performs encryption never
fires. Keys were landing in Mongo, and in the Redis organization cache, in
cleartext. Rather than change encryption traversal for the whole
organization document and the write path for all organization
configuration, AIConfigSecretsCE encrypts and decrypts at the few points
these secrets are written and read. Values that do not decrypt are treated
as legacy cleartext and passed through, so an instance keeps working
between the upgrade and Migration076, which encrypts existing values in
place and is idempotent for the same reason.

Also fixes the admin key field that made the corruption possible: a stored
key was loaded into the input as the literal string "••••••••" and the save
guard was a comparison against that mask, so typing a new key without first
clearing the field persisted "••••••••sk-..." behind a success toast. The
input now holds only a newly typed key and "a key is stored" is tracked
separately.

Verified: mvn -pl appsmith-server -am compile clean; AIConfigServiceCEImplTest
7/7 and AiDatasourceSchemaSerializerCETest 5/5 green; tsc introduces no new
errors; eslint 0 errors; prettier and spotless clean.

Not covered by a test: AIConfigSecretsCE round-trip and the Migration076
idempotency check both go through EncryptionHelper, whose static
initialiser requires APPSMITH_ENCRYPTION_PASSWORD and
APPSMITH_ENCRYPTION_SALT. Those are set for the integration-test and
Docker CI jobs but not for server-unit-tests, so a unit test there would
fail on class initialisation. This belongs in the integration suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBx1z6GCvod1ENrBMVMcj7
@salevine
salevine requested a review from subrata71 July 29, 2026 15:07
@salevine
salevine marked this pull request as ready for review July 31, 2026 22:16
@salevine
salevine requested a review from a team as a code owner July 31, 2026 22:16

@hacktron-app hacktron-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Severity Count
MEDIUM 1

View full scan results

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (12)
app/client/src/ce/components/editorComponents/GPT/index.tsx (1)

76-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make editor optional, and avoid the React.ReactElement cast.

Line 84 guards with editor &&, but TAIWrapperProps.editor is declared as required CodeMirror.Editor. The guard tells the reader that editor can be absent at runtime. Declare it optional so TypeScript agrees with the guard.

Line 77 casts children to React.ReactElement. children is React.ReactNode, so the cast is unsound for undefined, a string, or an array.

🔧 Proposed changes
-  editor: CodeMirror.Editor;
+  editor?: CodeMirror.Editor;
   if (!enableAIAssistance) {
-    return children as React.ReactElement;
+    return <>{children}</>;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/ce/components/editorComponents/GPT/index.tsx` around lines 76
- 92, Update TAIWrapperProps so editor is optional, matching the editor && guard
before rendering AISidePanel. Replace the early-return React.ReactElement cast
in the wrapper component with a type-safe return that preserves the original
children without asserting an element type.
app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx (2)

249-275: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Remove the 100 ms setTimeout in handleQuickAction.

The dispatch does not depend on the setPrompt state update. It reads actionPrompt, editor, currentValue, and mode directly. The timer only delays the request and it is never cleared, so it can fire after unmount.

Dispatch synchronously.

♻️ Proposed change
   const handleQuickAction = useCallback(
     (actionPrompt: string) => {
       setPrompt(actionPrompt);
-
-      setTimeout(() => {
-        if (!editor) return;
-
-        const cursorPosition = editor.getCursor();
-        const context = getAIContext({
-          cursorPosition,
-          editor,
-        });
-
-        dispatch(
-          fetchAIResponse({
-            prompt: actionPrompt,
-            context: {
-              ...context,
-              currentValue,
-              mode,
-            },
-          }),
-        );
-      }, 100);
+
+      if (!editor) return;
+
+      const cursorPosition = editor.getCursor();
+      const context = getAIContext({ cursorPosition, editor });
+
+      dispatch(
+        fetchAIResponse({
+          prompt: actionPrompt,
+          context: { ...context, currentValue, mode },
+        }),
+      );
     },
     [editor, mode, currentValue, dispatch],
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx` around
lines 249 - 275, Update handleQuickAction to remove the 100 ms setTimeout and
execute the editor/context lookup and fetchAIResponse dispatch synchronously
after setPrompt. Preserve the existing editor guard and dependency list, using
actionPrompt, editor, currentValue, and mode directly.

165-191: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two smaller points on state and props.

Line 165: the effect clears the conversation on every mount. AIWindow mounts AISidePanel whenever AI assistance is enabled, not only when the panel opens. Any remount of the editor wrapper therefore discards the conversation. The reducer already resets messages in OPEN_AI_PANEL_WITH_CONTEXT, so the reset now lives in two places.

Line 177: contextInfo memoises editor.getCursor() with dependencies [editor, mode]. The displayed line number does not update when the cursor moves.

Line 287: the component returns null when closed, so the isOpen prop on PanelContainer and its display: none rule never apply.

Also applies to: 287-291

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx` around
lines 165 - 191, Remove the reset effect around clearAIResponse and setPrompt,
relying on OPEN_AI_PANEL_WITH_CONTEXT for conversation resets instead of
clearing on AISidePanel mounts or dependency changes. Update contextInfo so the
cursor position and displayed line number react to editor cursor movement,
rather than depending only on editor and mode. Keep AISidePanel mounted when
closed and let PanelContainer use isOpen and its display behavior instead of
returning null before rendering it.
app/client/src/ce/api/OrganizationApi.ts (1)

134-152: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the seven positional parameters of testApiKey with a single options object.

All parameters after provider are optional strings. A caller can silently swap apiVersion and baseUrl. The caller in app/client/src/pages/AdminSettings/AI/index.tsx (Lines 778-794) already passes six nested ternaries in order.

♻️ Proposed signature
-  static async testApiKey(
-    provider: string,
-    apiKey?: string,
-    endpoint?: string,
-    deploymentName?: string,
-    apiVersion?: string,
-    baseUrl?: string,
-    model?: string,
-  ): Promise<AxiosPromise<ApiResponse<Record<string, unknown>>>> {
-    return Api.post(`${OrganizationApi.tenantsUrl}/ai-config/test-api-key`, {
-      provider,
-      apiKey,
-      endpoint,
-      deploymentName,
-      apiVersion,
-      baseUrl,
-      model,
-    });
-  }
+  static async testApiKey(request: {
+    provider: string;
+    apiKey?: string;
+    endpoint?: string;
+    deploymentName?: string;
+    apiVersion?: string;
+    baseUrl?: string;
+    model?: string;
+  }): Promise<AxiosPromise<ApiResponse<Record<string, unknown>>>> {
+    return Api.post(
+      `${OrganizationApi.tenantsUrl}/ai-config/test-api-key`,
+      request,
+    );
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/ce/api/OrganizationApi.ts` around lines 134 - 152, Update
OrganizationApi.testApiKey to accept provider plus a single options object
containing the optional apiKey, endpoint, deploymentName, apiVersion, baseUrl,
and model fields. Preserve the existing request payload mapping, and update the
caller in the AI admin settings flow to pass these values by property name
instead of positional arguments.
app/client/src/ce/components/editorComponents/GPT/trigger.tsx (1)

7-14: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The two mode predicates diverge.

isAISupportedMode accepts mode === "graphql" only. getAIContext also accepts mode?.includes("graphql"). A CodeMirror mode name such as "text/x-graphql" therefore gets a context window but no entry point, because isAIEnabled returns false for it. mode === "sql" is also already covered by mode?.includes("sql").

Extract one classification helper and call it from both places.

♻️ Proposed helper
+type AIModeKind = "javascript" | "query" | null;
+
+export function getAIModeKind(mode?: string): AIModeKind {
+  if (mode === "javascript") return "javascript";
+
+  if (
+    mode?.includes("sql") ||
+    mode?.includes("graphql") ||
+    mode?.includes("json")
+  ) {
+    return "query";
+  }
+
+  return null;
+}

Also applies to: 61-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/ce/components/editorComponents/GPT/trigger.tsx` around lines 7
- 14, Unify mode classification by extracting a shared helper for supported AI
modes, then use it in both isAISupportedMode and getAIContext/isAIEnabled.
Ensure the helper recognizes GraphQL modes containing “graphql” as well as SQL
and JSON variants, while preserving JavaScript support, so context generation
and entry-point availability use identical predicates.
app/client/src/pages/AdminSettings/AI/index.tsx (1)

517-598: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The mount effect writes state without an unmount guard.

fetchAIConfig runs an async request and then calls many setters. If the administrator navigates away before the response arrives, React logs an update-on-unmounted-component warning. Add an ignore flag or an AbortController.

Line 570: the preset branch reads as inverted. When matchingPreset is found, the code sets the preset to null; when it is not found, it sets "custom". The behaviour is correct, but a short comment would help the next reader.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/pages/AdminSettings/AI/index.tsx` around lines 517 - 598, Add
an unmount guard to the fetchAIConfigOnMount effect so fetchAIConfig cannot
update state after navigation; check it before all setters, including the
loading-state update in finally, or abort the request. Preserve existing success
and error behavior while preventing post-unmount updates. Add a brief comment
around the CONTEXT_PRESETS matchingPreset branch explaining that null represents
a recognized preset and custom is used only when no preset matches.
app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076EncryptAIAssistantApiKeys.java (1)

38-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a single field mapping instead of a list plus a string switch.

KEY_FIELDS and readKey hold the same field names in two places. If a provider is added to KEY_FIELDS only, readKey returns null and the migration skips that credential without any signal. A single map of field name to getter removes that risk.

♻️ Proposed refactor
-    private static final List<String> KEY_FIELDS =
-            List.of("claudeApiKey", "openaiApiKey", "copilotApiKey", "azureOpenaiApiKey");
+    private static final Map<String, Function<AIAssistantConfig, String>> KEY_FIELDS = Map.of(
+            "claudeApiKey", AIAssistantConfig::getClaudeApiKey,
+            "openaiApiKey", AIAssistantConfig::getOpenaiApiKey,
+            "copilotApiKey", AIAssistantConfig::getCopilotApiKey,
+            "azureOpenaiApiKey", AIAssistantConfig::getAzureOpenaiApiKey);

Then iterate the entries and drop readKey:

-            for (String field : KEY_FIELDS) {
-                String stored = readKey(aiConfig, field);
+            for (Map.Entry<String, Function<AIAssistantConfig, String>> entry : KEY_FIELDS.entrySet()) {
+                String field = entry.getKey();
+                String stored = entry.getValue().apply(aiConfig);

Also applies to: 93-101

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076EncryptAIAssistantApiKeys.java`
around lines 38 - 39, Replace the duplicated KEY_FIELDS list and readKey switch
with one mapping from each credential field name to its corresponding getter.
Update the migration loop to iterate mapping entries and read values directly,
then remove readKey while preserving the existing encryption behavior.
app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java (3)

77-84: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

getOrBuildWebClient never caches.

The name implies a lookup, but the method builds a new WebClient and a new HttpClient for every request that uses a non-default base URL. Lines 519-520 (local LLM) and Lines 697-698 (Azure OpenAI) build one per request unconditionally. Provider configuration changes rarely. Cache the clients in a small bounded map keyed by base URL, or rename the method to buildWebClient so the cost is explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java`
around lines 77 - 84, Update getOrBuildWebClient and its callers for non-default
base URLs so WebClient and HttpClient instances are reused instead of rebuilt on
every request; either add a small bounded cache keyed by baseUrl while
preserving cachedClient for default URLs, or rename the method to buildWebClient
to accurately reflect uncached behavior.

402-414: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The OpenAI and Azure paths skip prompt validation.

callClaudeAPI (Lines 323-329) and callLocalLLMAPI (Lines 464-470) reject an empty prompt and a prompt above 150000 characters. callOpenAIAPI and callAzureOpenAIAPI (Lines 667-691) apply neither check. An empty prompt therefore produces a billed provider call, and an oversized prompt is only rejected after upload. Extract the two checks into one helper and call it from all four provider methods.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java`
around lines 402 - 414, Extract the empty-prompt and 150000-character limit
checks from callClaudeAPI and callLocalLLMAPI into a shared validation helper,
then invoke it at the start of callOpenAIAPI and callAzureOpenAIAPI as well as
the existing Claude and local LLM paths. Preserve the current rejection behavior
and ensure validation occurs before any provider request or upload.

342-360: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Send the Claude system prompt through the system field in both branches.

On the first turn the system prompt is concatenated into the user message; on later turns it moves to the top-level system field. The model receives different prompt structures for the same configuration. The Messages API accepts system unconditionally, so set it always and keep the user content clean. The !messages.isEmpty() clause on Line 358 is also always true, because Line 349 already appended a message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java`
around lines 342 - 360, Update the message construction in the Claude request
flow to keep user content limited to userPrompt and always place systemPrompt in
requestBody’s system field. Remove the first-turn concatenation and the
redundant messages.isEmpty() check around the system assignment, while
preserving conversationHistory handling as applicable.
app/server/appsmith-server/src/main/java/com/appsmith/server/domains/AIAssistantConfig.java (1)

93-113: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The merge cannot clear a field.

ObjectUtils.defaultIfNull keeps the existing value when the source field is null. An administrator therefore cannot remove a stale azureOpenaiEndpoint, azureOpenaiDeploymentName, localLlmUrl, or custom base URL once it is saved. If clearing must be supported, the update path needs an explicit tombstone (for example, treat empty string as "clear") or a full-replace semantic.

Confirm the admin UI never needs to unset these fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/domains/AIAssistantConfig.java`
around lines 93 - 113, The copyNonSensitiveValues method preserves existing
values when source fields are null, preventing administrators from clearing
previously saved configuration. Confirm the admin update contract and UI do not
require unsetting these fields; if clearing is supported, replace the
null-preserving merge with an explicit clear/tombstone or full-replace behavior
for fields such as azureOpenaiEndpoint, azureOpenaiDeploymentName, localLlmUrl,
and custom base URLs.
app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/AiDatasourceSchemaSerializerCETest.java (1)

52-60: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a tier-3 (hard-truncation) test to match TS coverage.

The TypeScript suite tests the hard-truncation branch with a very small budget. This Java suite stops at tier 2 (serializeLegacy_prioritizesTablesFromQueryOnly). Add a test with a budget smaller than truncationNotice.length() to verify serializeWithPriority truncates safely and does not throw when the budget is too small to fit the header.

✅ Proposed test addition
`@Test`
void serializeLegacy_truncatesWhenBudgetTooSmall() {
    DatasourceStructure structure = smallSchema();
    String out = AiDatasourceSchemaSerializerCE.serializeLegacy(structure, "SELECT * FROM users", 30);
    assertThat(out.length()).isLessThanOrEqualTo(30);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/AiDatasourceSchemaSerializerCETest.java`
around lines 52 - 60, Add a test alongside
serializeLegacy_prioritizesTablesFromQueryOnly that calls
AiDatasourceSchemaSerializerCE.serializeLegacy with smallSchema, the users
query, and a budget below truncationNotice.length() (such as 30), then assert
the result length is at most the requested budget to cover safe tier-3
truncation without throwing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/client/src/ce/components/editorComponents/GlobalAISidePanel/index.tsx`:
- Around line 245-267: Prevent overlapping AI requests by updating
handleQuickAction to return immediately when isLoading is true, and include
isLoading in its dependency array. Pass the loading state to QuickActionChip and
disable the chip while isLoading, preserving the existing dispatch behavior when
no request is active.

In `@app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx`:
- Around line 195-218: Update handleResizeMouseDown to store the active
onMouseMove and onMouseUp handlers in a ref, and add an unmount cleanup effect
that removes both document listeners and resets the drag state. Preserve the
existing mouseup cleanup while ensuring listeners cannot call setPanelWidth
after the component unmounts.
- Around line 310-318: In the AISidePanel component, add a document-level
keydown listener while the panel is open so pressing Escape invokes the existing
onClose callback. Reuse the component’s lifecycle cleanup pattern to remove the
listener when the panel closes or unmounts, while preserving the existing
handleKeyDown behavior for PromptInput.

In
`@app/client/src/ce/components/editorComponents/GPT/shared/AIMarkdownRenderer.tsx`:
- Around line 248-260: Update the code renderer’s isInline condition in
AIMarkdownRenderer to require both no language class and no newline in children,
so language-less fenced blocks render through CodeBlockWithCopy while truly
inline code remains inline.

In `@app/client/src/ce/sagas/AIAssistantSagas.ts`:
- Around line 203-257: The catch path in loadAISettingsSaga must not mark
configuration as successfully loaded after a transient fetch failure. Dispatch a
distinct failure action handled by the reducer without setting isConfigLoaded,
or otherwise preserve the unloaded state so implicit loads can retry; keep the
existing disabled-state reset while ensuring fetchAIResponseSaga does not
permanently report the assistant as disabled after a temporary error.
- Around line 130-182: Update the retry logic in the AI response saga around
UserApi.requestAIResponse to retry only transient failures: network errors,
timeouts, HTTP 429 responses, and 5xx responses. Classify both thrown errors and
unsuccessful response bodies before delaying; dispatch the existing error and
show the toast immediately for invalid credentials, bad requests, quota or other
non-transient failures. Replace the fixed retry delay with increasing backoff
between eligible attempts while preserving the existing success and final-error
handling.

In `@app/client/src/ce/utils/aiSchemaSerializer.ts`:
- Around line 83-94: Update the table serialization around the columns mapper in
the schema serializer so missing col.type values produce an empty type string,
matching AiDatasourceSchemaSerializerCE.serializeTable, instead of interpolating
the literal “undefined”. Preserve the existing PK and FK annotations and output
formatting for columns with defined types.

In `@app/client/src/pages/AdminSettings/AI/index.tsx`:
- Around line 956-964: Update the disabled-state logic for the Claude, OpenAI,
and Azure “Test Key” buttons to enable testing when either a newly entered API
key or the corresponding stored key exists. Preserve the existing loading-state
behavior and ensure the checks use each provider’s stored-key and input-key
symbols.

In `@app/client/src/sagas/ActionSagas.ts`:
- Around line 1199-1202: Update the slash-command flow in the relevant saga to
dispatch ReduxActionTypes.OPEN_AI_PANEL_WITH_CONTEXT instead of OPEN_AI_PANEL,
including the context payload expected by aiAssistantReducer. Preserve the
existing behavior of opening the AI panel while ensuring the reducer updates
context and clears stale conversation state when the entity or mode changes.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AIConstants.java`:
- Line 10: Update the DEFAULT_OPENAI_MODEL constant in AIConstants to a
currently supported OpenAI model instead of gpt-4, ensuring the default remains
valid for CE users who do not provide an override before the announced shutdown
date.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/AIConfigControllerCE.java`:
- Around line 37-48: Update the onErrorResume error handling in
AIConfigControllerCE so AppsmithException cases with ACL_NO_RESOURCE_FOUND
return HttpStatus.FORBIDDEN, while preserving HttpStatus.BAD_REQUEST for all
other errors.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java`:
- Around line 215-231: Update requestAIResponse’s onErrorResume so AI failures
produce an actual non-2xx HTTP response rather than a normally completed
ResponseDTO with a 400 metadata field, preferably by propagating
AppsmithException to the global handler. In AIAssistantServiceCEImpl, ensure
getAIErrorMessage maps provider failures to fixed sanitized user-facing messages
and never returns exception messages containing upstream response bodies.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java`:
- Around line 405-414: Require non-blank role and content on AIMessageDTO using
Jakarta validation, then reject or filter invalid conversation-history entries
before constructing provider payloads. Apply the guard consistently in the
message-building flows of AIAssistantServiceCEImpl, including the existing
OpenAI, local LLM, and Azure paths, while preserving valid message handling.
- Around line 370-383: The four AI provider error handlers in
AIAssistantServiceCEImpl must emit the existing mapped error even when the
response body is empty. Add an empty-body fallback such as defaultIfEmpty("")
before each bodyToMono(String.class).flatMap chain at the handlers around the
visible status-processing blocks, preserving the current status-code-specific
exceptions.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIConfigServiceCEImpl.java`:
- Around line 261-268: Update the errorSummary handling in
testLlmConnectionInternal to store a fixed classified error code rather than
String.valueOf(error), preserving the existing truncation only if still
applicable. Ensure values derived from connection or unexpected exception
messages cannot reach analytics, while retaining distinct classifications for
the supported error cases.
- Around line 910-911: Replace manual provider JSON string construction in both
payload-building branches with an objectMapper-built JSON tree or equivalent
serialization so model is escaped correctly. In the success branches, parse each
provider response with objectMapper.readTree(responseBody) and retrieve the
response field through JSON navigation rather than indexOf/substring scanning.
Apply this consistently to the request and response handling locations
identified in the comment, preserving the existing success and error behavior.
- Line 908: Update the diagnostic steps in testOpenAIKey so the “API Key Format”
entry reflects an actual validation performed by the method; remove or replace
the hard-coded “Key starts with 'sk-'” success message unless the key prefix is
explicitly checked, and ensure custom baseUrl configurations are not incorrectly
constrained by this diagnostic.
- Around line 377-379: Update the URL validation in AIConfigServiceCEImpl’s
parse block to reject null schemes and any scheme other than http or https
before calculating the default port. Ensure the validation produces the method’s
existing structured failure response, and only perform the HTTPS comparison
after the scheme has been validated.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIReferenceServiceCEImpl.java`:
- Around line 44-61: Update getReferenceContent to accept only modes present in
the existing SUPPORTED_MODES collection, returning an empty result for
unsupported client-supplied values before cache lookup or loadReference;
normalize with Locale.ROOT. Update warmCache to iterate SUPPORTED_MODES rather
than maintaining a separate literal mode list, ensuring only supported modes are
cached.

In
`@app/server/appsmith-server/src/main/resources/ai-references/sql-reference.md`:
- Around line 16-23: Replace the concatenated Input1.text WHERE example in the
Conditional Binding section with a parameterized boolean-short-circuit pattern,
and explicitly state not to concatenate raw input into SQL. Update the Dynamic
table name binding comment to require validating the selected identifier against
a known allow-list because identifiers cannot be parameterized.

---

Nitpick comments:
In `@app/client/src/ce/api/OrganizationApi.ts`:
- Around line 134-152: Update OrganizationApi.testApiKey to accept provider plus
a single options object containing the optional apiKey, endpoint,
deploymentName, apiVersion, baseUrl, and model fields. Preserve the existing
request payload mapping, and update the caller in the AI admin settings flow to
pass these values by property name instead of positional arguments.

In `@app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx`:
- Around line 249-275: Update handleQuickAction to remove the 100 ms setTimeout
and execute the editor/context lookup and fetchAIResponse dispatch synchronously
after setPrompt. Preserve the existing editor guard and dependency list, using
actionPrompt, editor, currentValue, and mode directly.
- Around line 165-191: Remove the reset effect around clearAIResponse and
setPrompt, relying on OPEN_AI_PANEL_WITH_CONTEXT for conversation resets instead
of clearing on AISidePanel mounts or dependency changes. Update contextInfo so
the cursor position and displayed line number react to editor cursor movement,
rather than depending only on editor and mode. Keep AISidePanel mounted when
closed and let PanelContainer use isOpen and its display behavior instead of
returning null before rendering it.

In `@app/client/src/ce/components/editorComponents/GPT/index.tsx`:
- Around line 76-92: Update TAIWrapperProps so editor is optional, matching the
editor && guard before rendering AISidePanel. Replace the early-return
React.ReactElement cast in the wrapper component with a type-safe return that
preserves the original children without asserting an element type.

In `@app/client/src/ce/components/editorComponents/GPT/trigger.tsx`:
- Around line 7-14: Unify mode classification by extracting a shared helper for
supported AI modes, then use it in both isAISupportedMode and
getAIContext/isAIEnabled. Ensure the helper recognizes GraphQL modes containing
“graphql” as well as SQL and JSON variants, while preserving JavaScript support,
so context generation and entry-point availability use identical predicates.

In `@app/client/src/pages/AdminSettings/AI/index.tsx`:
- Around line 517-598: Add an unmount guard to the fetchAIConfigOnMount effect
so fetchAIConfig cannot update state after navigation; check it before all
setters, including the loading-state update in finally, or abort the request.
Preserve existing success and error behavior while preventing post-unmount
updates. Add a brief comment around the CONTEXT_PRESETS matchingPreset branch
explaining that null represents a recognized preset and custom is used only when
no preset matches.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/domains/AIAssistantConfig.java`:
- Around line 93-113: The copyNonSensitiveValues method preserves existing
values when source fields are null, preventing administrators from clearing
previously saved configuration. Confirm the admin update contract and UI do not
require unsetting these fields; if clearing is supported, replace the
null-preserving merge with an explicit clear/tombstone or full-replace behavior
for fields such as azureOpenaiEndpoint, azureOpenaiDeploymentName, localLlmUrl,
and custom base URLs.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076EncryptAIAssistantApiKeys.java`:
- Around line 38-39: Replace the duplicated KEY_FIELDS list and readKey switch
with one mapping from each credential field name to its corresponding getter.
Update the migration loop to iterate mapping entries and read values directly,
then remove readKey while preserving the existing encryption behavior.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java`:
- Around line 77-84: Update getOrBuildWebClient and its callers for non-default
base URLs so WebClient and HttpClient instances are reused instead of rebuilt on
every request; either add a small bounded cache keyed by baseUrl while
preserving cachedClient for default URLs, or rename the method to buildWebClient
to accurately reflect uncached behavior.
- Around line 402-414: Extract the empty-prompt and 150000-character limit
checks from callClaudeAPI and callLocalLLMAPI into a shared validation helper,
then invoke it at the start of callOpenAIAPI and callAzureOpenAIAPI as well as
the existing Claude and local LLM paths. Preserve the current rejection behavior
and ensure validation occurs before any provider request or upload.
- Around line 342-360: Update the message construction in the Claude request
flow to keep user content limited to userPrompt and always place systemPrompt in
requestBody’s system field. Remove the first-turn concatenation and the
redundant messages.isEmpty() check around the system assignment, while
preserving conversationHistory handling as applicable.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/AiDatasourceSchemaSerializerCETest.java`:
- Around line 52-60: Add a test alongside
serializeLegacy_prioritizesTablesFromQueryOnly that calls
AiDatasourceSchemaSerializerCE.serializeLegacy with smallSchema, the users
query, and a budget below truncationNotice.length() (such as 30), then assert
the result length is at most the requested budget to cover safe tier-3
truncation without throwing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3db90930-c4c3-4d5f-924f-6f39404e6a5b

📥 Commits

Reviewing files that changed from the base of the PR and between 665fbf1 and e941322.

⛔ Files ignored due to path filters (1)
  • app/client/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (65)
  • app/client/package.json
  • app/client/src/ce/api/OrganizationApi.ts
  • app/client/src/ce/api/UserApi.tsx
  • app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx
  • app/client/src/ce/components/editorComponents/GPT/AskAIButton.tsx
  • app/client/src/ce/components/editorComponents/GPT/index.tsx
  • app/client/src/ce/components/editorComponents/GPT/shared/AIMarkdownRenderer.tsx
  • app/client/src/ce/components/editorComponents/GPT/shared/constants.ts
  • app/client/src/ce/components/editorComponents/GPT/shared/helpers.ts
  • app/client/src/ce/components/editorComponents/GPT/shared/index.ts
  • app/client/src/ce/components/editorComponents/GPT/shared/styledComponents.ts
  • app/client/src/ce/components/editorComponents/GPT/shared/types.ts
  • app/client/src/ce/components/editorComponents/GPT/trigger.tsx
  • app/client/src/ce/components/editorComponents/GlobalAISidePanel/index.tsx
  • app/client/src/ce/pages/AdminSettings/config/ai.tsx
  • app/client/src/ce/pages/AdminSettings/config/index.ts
  • app/client/src/ce/pages/AdminSettings/config/types.ts
  • app/client/src/ce/reducers/aiAssistantReducer.ts
  • app/client/src/ce/reducers/index.tsx
  • app/client/src/ce/sagas/AIAssistantSagas.ts
  • app/client/src/ce/sagas/index.tsx
  • app/client/src/ce/selectors/aiAssistantSelectors.ts
  • app/client/src/ce/utils/aiSchemaSerializer.test.ts
  • app/client/src/ce/utils/aiSchemaSerializer.ts
  • app/client/src/pages/AdminSettings/AI/index.tsx
  • app/client/src/sagas/ActionSagas.ts
  • app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AnalyticsEvents.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AIConstants.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/AIConfigController.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/AIConfigControllerCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/OrganizationControllerCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/domains/AIAssistantConfig.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/domains/AIProvider.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/OrganizationConfigurationCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/AIConfigDTO.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/AIEditorContextDTO.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/AIMessageDTO.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/AIRequestDTO.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/AIConfigSecretsCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/AiDatasourceSchemaSerializerCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration075AddIsAIAssistantEnabledToOrganizationConfiguration.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076EncryptAIAssistantApiKeys.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/AIAssistantService.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/AIAssistantServiceImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/AIConfigService.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/AIConfigServiceImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/AIReferenceService.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/AIReferenceServiceImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIConfigServiceCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIConfigServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIReferenceServiceCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIReferenceServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/AIConfigServiceCECompatible.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/AIConfigServiceCECompatibleImpl.java
  • app/server/appsmith-server/src/main/resources/ai-references/README.md
  • app/server/appsmith-server/src/main/resources/ai-references/common-issues.md
  • app/server/appsmith-server/src/main/resources/ai-references/graphql-reference.md
  • app/server/appsmith-server/src/main/resources/ai-references/javascript-reference.md
  • app/server/appsmith-server/src/main/resources/ai-references/sql-reference.md
  • app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/AiDatasourceSchemaSerializerCETest.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/AIConfigServiceCEImplTest.java

Comment thread app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx
Comment thread app/client/src/ce/components/editorComponents/GPT/AISidePanel.tsx
Comment thread app/client/src/ce/sagas/AIAssistantSagas.ts
Comment thread app/server/appsmith-server/src/main/resources/ai-references/sql-reference.md Outdated
…AI request cost

A nine-seat review blocked this branch on two security claims the code did not actually deliver,
plus a crash path and an unbounded cost path. Each is fixed at the cause and covered by a test that
fails on the unpatched code.

getAIConfig's non-manager branch was unreachable. OrganizationServiceCEImpl.findById ends in
switchIfEmpty(Mono.error(NO_RESOURCE_FOUND)), so it SIGNALS rather than completing empty, and a
switchIfEmpty fallback beneath it could never run. The disclosure was closed, but every non-manager
received an error instead of the enablement flags the editor reads on session start, and
buildAIConfigStatusResponse was dead code. Note this was dead in BOTH editions — EE's copy of
OrganizationServiceCEImpl.findById is identical, so there is no CE/EE behavioural divergence to
reconcile, contrary to the initial diagnosis. Resolved with a narrowed onErrorResume on the
not-found/denied codes so a genuine failure still surfaces.

Provider credentials could still be written in cleartext. PUT /organizations binds the whole
OrganizationConfiguration and @JSONVIEW does not filter a request body unless a view is active, so
the Views.Internal key fields deserialize there and the sparse updateById persisted them raw,
bypassing the encryption /ai-config applies. AIConfigSecretsCE.decrypt's legacy-cleartext fallback
meant nothing ever looked broken. Encryption is now normalised on the way into storage rather than
being a property of one code path.

A scheme-relative URL crashed the connection test outside its error handling. URI.create("//host/x")
yields a host but a null scheme, clearing the host guard and then throwing on getScheme().equals(),
before any Mono exists — so it escaped the structured error response entirely. The scheme is now
validated inside the parse block, matching the runtime path which already did this correctly. The
port derivation was also made case-insensitive so HTTPS:// no longer resolves to 80.

The AI endpoint had no cost control. Any authenticated organization member, including a view-only
user, could loop it and spend the organization's third-party LLM budget at whatever rate the client
could issue requests. A per-user bucket (20/min) is now spent before any provider work. It is
setter-injected deliberately: EE's AIAssistantServiceImpl calls super(...) with six arguments, and
adding a seventh would break EE's compile the moment this file syncs; EE instantiates the subclass
as a Spring bean, so setter injection resolves in both editions.

Not addressed here: an authorization gate on the AI endpoint. Choosing the wrong permission would
lock out legitimate developers, and which role may use AI is a product decision. The rate limit
addresses the abuse vector, and the entity-scoped path already enforces execute permission.

The PR description now carries the required CE→EE sync resolution, because two of the sync's failure
modes arrive through clean merges that no conflict marker will surface: EE would register the AI
saga and admin category twice (duplicate provider requests, duplicate sidebar entry), and
Migration076 would encrypt EE's keys while EE's readers still expect cleartext.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL

@subrata71 subrata71 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@salevine I was checking the review comments from both CoderabbitIAI and HacktronAI. Those seem to be valid. Can we also address them in the same PR?

Follows the blocker commit with the non-blocking findings from the council and CodeRabbit.

The reference-mode lookup was an unbounded, client-keyed cache. `mode` arrives straight from the
client and was both the cache key and part of a classpath resource path, in a ConcurrentHashMap with
no eviction — an authenticated loop over random modes grew the heap without bound. An allowlist of
the three modes that actually have a bundled reference closes that and the unvalidated path
construction together, and warmCache now shares the same source of truth. Locale.ROOT so a
Turkish-locale server does not fold "I" and miss the list.

The local-LLM copy pushed operators toward disabling SSRF protection instance-wide. Both the admin
placeholder and the server's suggestion list told them to use http://localhost:11434 — an address
the filter now refuses, whose only apparent remedy is APPSMITH_DISABLE_SSRF_FILTER=true, which turns
the filter off for every datasource on the instance. They now suggest host.docker.internal and
explain that inside the container loopback is Appsmith's own Mongo, Redis and RTS.

The connection test was an internal network scanner with a read oracle. matchesBlockedAddressClass
deliberately permits RFC1918, and the response carried resolvedIp plus a 500-char responsePreview,
so a manager could map the private network and read back what answered. Those fields are gone, and
the DNS step now reports THAT the host resolved rather than what it resolved to — the step message
was leaking the same address the dropped field did.

sql-reference.md is loaded into the assistant's system prompt, so its examples are training data.
It taught three injectable patterns, not the one reported: string-concatenating user input into a
WHERE clause, wrapping a binding in quotes (which defeats parameterization, since only standalone
bindings are sent as parameters), and interpolating a widget value into a table position. Replaced
with correct/incorrect pairs, a safe optional-filter idiom, and an explicit warning that identifiers
cannot be parameterized.

Provider error bodies no longer reach API responses. "Local LLM request failed: " + errorBody put
unbounded upstream text — attacker-influencable via a self-hosted "local LLM" — into the message
that getAIErrorMessage returns to the client. Logged server-side, with a stable message returned.

Both new controllers completed normally with a 400 inside the body, making the wire status 200 for
every failure: clients branching on HTTP status saw success and error metrics stayed clean. Neither
is house convention — they are the only two such sites in the controllers, both added by this PR —
so they now signal the error. The AI-config path also distinguishes an authorization refusal from
bad input rather than collapsing both to 400.

Migration076 matched every organization, because Migration075 gives them all an aiAssistantConfig,
and loaded full documents into memory. Narrowed to organizations that actually hold a credential.

Client: retries are classified, so an invalid key, a quota rejection or a bad request no longer
costs a second completion call; quick-action chips are disabled and guarded while a request is in
flight; a fenced code block with no language renders as a block again rather than inline; img is
overridden with the same scheme allowlist as anchors so model output cannot make the viewer's
browser fetch from a server the model chose; and a column with no type no longer serializes the
literal "undefined" into the schema sent to the model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/client/src/ce/components/editorComponents/GPT/shared/AIMarkdownRenderer.tsx`:
- Around line 248-258: Update the img renderer override in AIMarkdownRenderer so
model-generated remote image URLs are not requested: permit only same-origin
assets or approved controlled-proxy URLs, while rejecting external http/https
sources. Replace the current SAFE_URL_PATTERN-only validation for safeSrc and
preserve the existing null return for disallowed images and referrer policy for
allowed ones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5fd1d92f-28c3-4933-bc41-d899d33968d9

📥 Commits

Reviewing files that changed from the base of the PR and between 3a1a42d and 31a5575.

📒 Files selected for processing (12)
  • app/client/src/ce/components/editorComponents/GPT/shared/AIMarkdownRenderer.tsx
  • app/client/src/ce/components/editorComponents/GlobalAISidePanel/index.tsx
  • app/client/src/ce/sagas/AIAssistantSagas.ts
  • app/client/src/ce/utils/aiSchemaSerializer.ts
  • app/client/src/pages/AdminSettings/AI/index.tsx
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/AIConfigControllerCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076EncryptAIAssistantApiKeys.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIConfigServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIReferenceServiceCEImpl.java
  • app/server/appsmith-server/src/main/resources/ai-references/sql-reference.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/client/src/ce/utils/aiSchemaSerializer.ts
  • app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration076EncryptAIAssistantApiKeys.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java
  • app/client/src/pages/AdminSettings/AI/index.tsx
  • app/client/src/ce/components/editorComponents/GlobalAISidePanel/index.tsx
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIConfigServiceCEImpl.java

Comment thread app/client/src/ce/components/editorComponents/GPT/shared/AIMarkdownRenderer.tsx Outdated
… to work on

Hacktron flagged the AI request endpoint as reachable by any authenticated user, including a
read-only WORKSPACE_VIEWER, which matches what the security review raised independently. Two
sources on the same gap, so this tightens it rather than leaving it to the rate limit alone.

The entity-scoped path resolved context.entityId with getExecutePermission(). Execute is a
viewer-level permission, so a read-only member could point the assistant at an entity they cannot
modify and have it rewrite the query — spending the organization's provider credits to do it. The
assistant is an authoring tool, so the bar is the permission for changing the entity, not for
running it: now getEditPermission().

This does not close the context-free case, where no entity is supplied and there is nothing to
scope a permission check against. That path stays bounded by the per-user rate limit added earlier.
Closing it properly means deciding whether the assistant should be developer-only instance-wide,
which is a product call rather than something to infer here — picking a permission wrong locks out
the developers the feature exists for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL
@salevine

salevine commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

/build-deploy-preview skip-tests=true

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/30831224825.
Workflow: On demand build Docker image and deploy preview.
skip-tests: true.
env: ``.
PR: 42065.
recreate: .
base-image-tag: .

The img override added in the previous commit only validated the URL scheme, which is not enough.
An attacker-controlled https:// tracking pixel passes a scheme allowlist and still causes the
viewer's browser to fetch it, handing over IP address, user agent and request timing —
referrerPolicy="no-referrer" suppresses the Referer header, not the request itself.

Images are now rendered only from the same origin, so nothing the model writes can cause an
outbound fetch. A remote source degrades to its alt text rather than disappearing, so the user can
still see that something was referenced.

Caught by CodeRabbit on the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Deploy-Preview-URL: https://ce-42065.dp.appsmith.com

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java`:
- Around line 287-292: Update the permission validation flow around
findActionDTObyIdAndViewMode and getAIResponse so an empty EDIT-permission
lookup terminates with an authorization error rather than falling back via
switchIfEmpty(Mono.just(context)). Ensure callers without EDIT permission cannot
dispatch to the provider or receive an AI response, while preserving the
existing authorized path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b3c9e09-c260-4cb4-bcd5-8315b11b08b3

📥 Commits

Reviewing files that changed from the base of the PR and between 31a5575 and abf777a.

📒 Files selected for processing (1)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AIAssistantServiceCEImpl.java

salevine and others added 4 commits August 3, 2026 12:30
…equest

The previous commit tightened the entity lookup in enrichContextWithDatasourceSchema from EXECUTE to
EDIT, but that method is built to degrade gracefully: it ends in switchIfEmpty(Mono.just(context))
and onErrorResume, so a missing datasource costs the prompt its schema rather than failing the
request. That fallback swallows an EMPTY result identically — and a permission-filtered lookup
returns empty precisely when the caller lacks the permission. So the tightening gated only whether
schema was attached, never whether the request ran. A caller without edit rights still received an
answer and still spent the organization's provider credits.

That is the same shape as the getAIConfig defect fixed earlier on this branch: a check positioned
where it cannot refuse anything. Worth naming, because it is clearly easy to write twice.

The authorization decision now lives in its own gate ahead of dispatch, where an empty lookup is an
error rather than a fallback. Enrichment keeps its graceful degradation, which is correct for the
schema it is actually responsible for.

Unchanged and still deliberate: a request carrying no entity id has nothing to scope a check
against and remains bounded by the per-user rate limit.

Caught by CodeRabbit on the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL
…uthentication

Ask AI only exists in the editor — there is no surface for it in a deployed application — so any
caller who cannot edit the thing they are asking about has no legitimate route to this endpoint.
Until now the endpoint accepted any authenticated organization member and merely rate-limited them.

The gate added previously only covered requests carrying an entityId. That left a real developer
flow unauthorized: the panel opens from a widget property binding too, where entityInformation
supplies no entityId, so those requests fell through with nothing checked. The context also carried
no application scope, so the server had nothing else to authorize against.

AIEditorContextDTO now carries applicationId, and authorization resolves in that order: edit rights
on the entity when there is one, otherwise edit rights on the application whose editor the request
came from. A request that identifies neither is refused rather than allowed, because an unscoped
request cannot be shown to come from a developer and this endpoint spends the organization's
provider credits. The client sends applicationId from the existing getCurrentApplicationId selector.

This closes the gap left open in the previous two commits and flagged on the Hacktron and CodeRabbit
threads, now that the product question behind it is settled: the feature is developer-only by
construction, so gating it cannot lock out a legitimate user.

Note for the CE→EE sync: AIEditorContextDTO was byte-identical with EE and now differs, so it joins
the set of files the sync-resolution section of the PR description covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL
A CE-owned feature registers its admin category in ce/pages/AdminSettings/config, and an edition
that also registers the same category in its own ee/ index calls ConfigFactory.register twice for
one category. Those two files merge cleanly during a CE→EE sync, so nothing surfaces the duplicate
until someone opens admin settings and sees the entry listed twice.

register() was a raw push into three collections, and all three accumulated: categories (the visible
duplicate in the sidebar), settings (a duplicated row), and savableCategories (a duplicated save
target). Only settingsMap was safe, because it is keyed rather than appended.

Guarding at the single entry point rather than inside each collection keeps the three consistent —
a category is either fully registered or not at all — and makes a duplicate registration harmless
instead of something every caller has to remember to avoid. All 13 existing callers go through
register(); nothing calls the two sub-methods directly.

This removes one of the three steps the CE→EE sync section of this PR describes: the duplicate Ask
AI admin category now collapses on its own, with no EE-side change. The duplicate saga registration
in the same section still needs one, because EE appends its own entry after spreading CE's list and
CE cannot reach into that array.

Tests cover all three collections and fail without the guard, plus a control asserting genuinely
distinct categories still register.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NTAwi7o4KMo8cNRm8hUsL

@hacktron-app hacktron-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Severity Count
HIGH 1

View full scan results

@salevine

salevine commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@subrata71 Yes — all of them are now addressed in this PR.

  • Every CodeRabbit and Hacktron comment has a written reply on its thread, and all threads are resolved.
  • The valid findings were fixed in code. For example, the AI assistant endpoint now requires edit permission on the entity (not just execute), and there is a per-user rate limit so it can't be abused to burn provider credits.
  • One Hacktron finding (the HIGH one about the AI config test endpoints) turned out to be a false positive: those endpoints already require the org admin permission (MANAGE_ORGANIZATION) at the service layer, and all outbound calls go through our restricted-host filter, which blocks the SSRF targets in the report. The full explanation is on that thread.
  • Both the CodeRabbit and Hacktron checks are green on the latest commit, along with the rest of CI.

@salevine
salevine requested a review from subrata71 August 4, 2026 13:12

@subrata71 subrata71 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look good. Hope it won't cause any regressions once it gets synced to EE post successful conflict resolution. Godspeed!

@salevine
salevine merged commit 147d795 into release Aug 4, 2026
155 of 157 checks passed
@salevine
salevine deleted the feat/ask-ai-ce-port branch August 4, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request ok-to-test Required label for CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants