Skip to content

feat[Go]: implement agent session/download/logs/get-agent APIs#15661

Open
hunnyboy1217 wants to merge 1 commit into
infiniflow:mainfrom
hunnyboy1217:feat/go-agent-session-download-logs-api
Open

feat[Go]: implement agent session/download/logs/get-agent APIs#15661
hunnyboy1217 wants to merge 1 commit into
infiniflow:mainfrom
hunnyboy1217:feat/go-agent-session-download-logs-api

Conversation

@hunnyboy1217
Copy link
Copy Markdown
Contributor

@hunnyboy1217 hunnyboy1217 commented Jun 4, 2026

Closes #15654:

  • GET /api/v1/agents/:agent_id (GetAgent)
  • GET /api/v1/agents/:agent_id/sessions (ListAgentSessions)
  • GET /api/v1/agents/:agent_id/sessions/:session_id (GetAgentSession)
  • DELETE /api/v1/agents/:agent_id/sessions/:session_id (DeleteAgentSessionItem)
  • DELETE /api/v1/agents/:agent_id/sessions (DeleteAgentSessions – bulk)
  • GET /api/v1/agents/:agent_id/logs/:message_id (GetAgentLogs – Redis)
  • GET /api/v1/agents/download (DownloadAgentFile)
  • GET /api/v1/agents/attachments/:attachment_id/download (DownloadAttachment)

New files:
internal/dao/agent_session.go – DAO for conversation table (agent view)

Changed files:
internal/entity/canvas.go – AgentSession entity (conversation table)
internal/service/agent.go – service methods for all new endpoints
internal/handler/agent.go – HTTP handlers for all new endpoints
internal/router/router.go – route registration

Ownership checks mirror Python:

  • canvas access (team permission or owner) gates session read/single-delete
  • canvas ownership gates bulk session delete
  • attachment download uses caller's tenant ID as the storage bucket

What problem does this PR solve?

Briefly describe what this PR aims to solve. Include background context that will help reviewers understand the purpose of the PR.

Type of change

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Documentation Update
  • Refactoring
  • Performance Improvement
  • Other (please describe):

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10c1b71d-55b2-4593-ae69-64664380d29b

📥 Commits

Reviewing files that changed from the base of the PR and between 37f704f and f5defcb.

📒 Files selected for processing (5)
  • internal/dao/agent_session.go
  • internal/entity/canvas.go
  • internal/handler/agent.go
  • internal/router/router.go
  • internal/service/agent.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/router/router.go
  • internal/entity/canvas.go
  • internal/handler/agent.go
  • internal/dao/agent_session.go
  • internal/service/agent.go

📝 Walkthrough

Walkthrough

Adds AgentSession model and DAO; extends AgentService with session listing, retrieval, deletion, Redis log retrieval, and storage-backed file/attachment downloads; implements HTTP handlers and registers routes under /api/v1/agents.

Changes

Agent Session Management and File Download APIs

Layer / File(s) Summary
Data model and DAO
internal/entity/canvas.go, internal/dao/agent_session.go
AgentSession maps to conversation with JSON Message/Reference. AgentSessionDAO provides GetByID, ListByAgentID, ListByAgentIDPaged (filters, ordering, pagination), DeleteByID (hard delete), GetByIDAndAgentID, and BelongsToAgent.
Service: Access control & session management
internal/service/agent.go
AgentService wired with agentSessionDAO and fileDAO; adds GetAgent (owner or team-member access), CheckCanvasOwner, ListAgentSessions, GetAgentSession, DeleteAgentSessionByID, and DeleteAgentSessions (bulk delete with duplicate detection and per-id error aggregation).
Service: Logs and file download helpers
internal/service/agent.go
GetAgentLogs reads Redis and returns parsed payload or empty map on missing/invalid data. DownloadAgentFile validates tenant/file/storage, initializes storage, and returns blob bytes + filename. DownloadAttachment fetches attachment blob from tenant storage.
Handler endpoints and imports
internal/handler/agent.go
Handlers added: GetAgent, ListAgentSessions (clamped page_size), GetAgentSession, DeleteAgentSessionItem, DeleteAgentSessions (optional body, aggregated errors), GetAgentLogs, DownloadAgentFile (conditional nosniff + UTF-8 Content-Disposition), and DownloadAttachment (streamed blob).
Router updates
internal/router/router.go
Registers static download routes (/download, /attachments/:attachment_id/download) before parameterized /:agent_id and adds session CRUD and logs endpoints under the agents route group.

Sequence Diagram

sequenceDiagram
  participant AgentHandler
  participant AgentService
  participant AgentSessionDAO
  participant Redis
  participant Storage
  AgentHandler->>AgentService: GetAgent(userID, agentID)
  AgentService->>AgentService: Check ownership / team permission
  AgentHandler->>AgentService: ListAgentSessions(agentID, params)
  AgentService->>AgentSessionDAO: ListByAgentIDPaged(agentID, params)
  AgentSessionDAO-->>AgentService: sessions, total
  AgentService-->>AgentHandler: sessions, total
  AgentHandler->>AgentService: DeleteAgentSessionByID(agentID, sessionID)
  AgentService->>AgentSessionDAO: BelongsToAgent(sessionID, agentID)
  AgentService->>AgentSessionDAO: DeleteByID(sessionID)
  AgentHandler->>AgentService: GetAgentLogs(agentID, messageID)
  AgentService->>Redis: GET formatted logs key
  Redis-->>AgentService: raw payload or nil
  AgentHandler->>AgentService: DownloadAgentFile(tenantID, fileID)
  AgentService->>Storage: Initialize & Download blob(fileID)
  Storage-->>AgentService: blob bytes
  AgentService-->>AgentHandler: blob bytes, filename
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

💞 feature, ci, size:L

Suggested reviewers

  • yingfeng
  • yuzhichang
  • Haruko386
  • JinHai-CN
  • Hz-186

Poem

🐰 I hop through rows of JSON light,
Fetch sessions, logs, and files at night.
DAO digs roots, service leads the way,
Handlers stream the bytes that play.
A tiny rabbit cheers the day!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: implementing agent session, download, logs, and get-agent APIs in Go.
Description check ✅ Passed The description lists all implemented endpoints, files added/changed, and ownership checks, but lacks details on the problems being solved section and specific rationale.
Linked Issues check ✅ Passed All eight required endpoints from issue #15654 are implemented: session management (GET list, GET single, DELETE single, DELETE bulk), agent retrieval, logs retrieval, and file/attachment downloads with proper ownership checks.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue #15654 requirements and do not include unrelated modifications such as test implementations or build configuration changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/infiniflow/ragflow/issues/comments/4622607088","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/infiniflow/ragflow/pull/15661?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `7c8a59e4-9076-428e-83ca-8ee3235c28ef`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 98f2a2e60b6a6da3906628a157c1751ee439e5ca and 6d87f69df62579bcfbda1b29813eb81ca073e738.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (5)</summary>\n> \n> * `internal/dao/agent_session.go`\n> * `internal/entity/canvas.go`\n> * `internal/handler/agent.go`\n> * `internal/router/router.go`\n> * `internal/service/agent.go`\n> \n> </details>\n> \n> ```ascii\n>  ______________________________________________________\n> < Unit tests are cute. Integration tests pay the rent. >\n>  ------------------------------------------------------\n>   \\\n>    \\   \\\n>         \\ /\\\n>         ( )\n>       .( o ).\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=infiniflow/ragflow&utm_content=15661)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n<!-- usage_tips_start -->\n\n> [!TIP]\n> <details>\n> <summary>CodeRabbit can approve clean reviews and PRs whose review comments are fully resolved.</summary>\n> \n> Enable the `reviews.request_changes_workflow` setting to allow automatic approval for clean reviews, approval after CodeRabbit comments are resolved, and explicit top-level `@coderabbitai resolve` or `@coderabbitai approve` commands.\n> \n> </details>\n\n<!-- usage_tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@internal/dao/agent_session.go`:
- Around line 72-83: AgentSessionDAO.ListByAgentIDPaged currently assigns
user-controlled orderby directly to order and passes it into base.Order(order),
enabling SQL injection; replace that by validating orderby against a hardcoded
allowlist (e.g., map[string]string
{"id":"id","name":"name","create_time":"create_time","update_time":"update_time"})
and mapping the input to the canonical column name, then build order as
mappedColumn + (desc ? " DESC" : " ASC") and pass only that into base.Order;
ensure the code references the existing orderby variable, the desc boolean, and
the call site base.Order(order) so no raw user input flows into the ORDER BY
clause.

In `@internal/handler/agent.go`:
- Around line 672-684: The handler currently trusts the client-supplied ext and
serves blobs inline, enabling MIME spoofing; update the attachment download
logic (around h.agentService.DownloadAttachment, ext, utility.GetContentType,
contentType and c.Data) to ignore or not trust the ext query for determining
Content-Type, instead sniff the blob (e.g., use http.DetectContentType or
equivalent) to derive contentType and fall back to "application/octet-stream" if
unknown, map/normalize unsafe types (e.g., downgrade "text/html",
"application/javascript" to "text/plain" or "application/octet-stream"), and
force a Content-Disposition: attachment header (with a safe filename) so
responses are downloaded rather than rendered inline.
- Around line 467-470: The handler currently treats any error from agentService
as "Session not found"; update the service and handlers so not-found and
internal errors are distinguishable: in internal/service/agent.go return a
sentinel exported error (e.g. ErrSessionNotFound) or wrap gorm.ErrRecordNotFound
with %w when a record is absent in functions like
GetAgentSession/DeleteAgentSessionItem, and update the handler code that calls
h.agentService.GetAgentSession and DeleteAgentSessionItem to use errors.Is(err,
service.ErrSessionNotFound) to return common.CodeNotFound with the "Session not
found" response, otherwise log the error and return common.CodeServerError (and
an appropriate message) for internal failures; ensure service functions preserve
the original error via wrapping so errors.Is works.
- Around line 547-550: DeleteAgentSessions currently treats any error from
c.ShouldBindJSON(&body) as an empty/no-op and returns common.CodeSuccess;
instead detect genuine empty body vs malformed JSON and return an error for
malformed input. Change the c.ShouldBindJSON(&body) error handling: if the error
is an EOF/empty-body case (use errors.Is(err, io.EOF) or check for empty body)
then keep the no-op behavior, otherwise respond with HTTP 400 and a JSON error
(non-success code) describing the malformed JSON; update the branch that
currently returns common.CodeSuccess so only empty-body errors produce that
response and all other binding errors return an error response. Ensure you
modify the error handling around c.ShouldBindJSON(&body) in DeleteAgentSessions
accordingly.

In `@internal/service/agent.go`:
- Around line 304-307: The current check conflates DAO errors and ownership
falsehoods for s.agentSessionDAO.BelongsToAgent; change the logic to first
handle a non-nil err returned by BelongsToAgent (e.g. append a distinct error
like "failed to verify ownership for session %s: <err>" or record it as an
internal/DAO error) and only when err == nil evaluate belongs and append the
existing "the agent doesn't own the session %s" message; update the block around
s.agentSessionDAO.BelongsToAgent, belongs, and err to separate these two cases
so transient DB failures are not reported as ownership failures.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c8a59e4-9076-428e-83ca-8ee3235c28ef

📥 Commits

Reviewing files that changed from the base of the PR and between 98f2a2e and 6d87f69.

📒 Files selected for processing (5)
  • internal/dao/agent_session.go
  • internal/entity/canvas.go
  • internal/handler/agent.go
  • internal/router/router.go
  • internal/service/agent.go

Comment on lines +72 to +83
order := orderby
if order == "" {
order = "update_time"
}
if desc {
order += " DESC"
} else {
order += " ASC"
}

query := base.Order(order)
if page > 0 && pageSize > 0 {
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In GORM v1.25.x, does db.Order(string) treat input as raw SQL, requiring allowlisting/safe column construction for user-provided sort fields?

💡 Result:

Yes, in GORM v1.25.x, the db.Order(string) method treats its input as raw SQL and is not automatically sanitized against SQL injection [1][2]. Because SQL standards do not allow column names or directions to be parameterized in an ORDER BY clause, GORM does not provide built-in escaping for this method [3][4]. To securely handle user-provided sort fields, you must use an allowlist approach [1][4]. Never pass raw user input directly into db.Order [4]. Instead, map the user input to a predefined set of safe, hardcoded column names [4]. Example of secure pattern: // SECURE: Use a map to validate allowed columns allowedOrders:= map[string]string{ "id": "id", "name": "name", "price": "price", } if col, ok:= allowedOrders[userInput]; ok { db.Order(col + " asc").Find(&products) } else { // Default fallback db.Order("id asc").Find(&products) } The official GORM security documentation explicitly lists db.Order among the methods that are not escaped and requires developer caution to avoid SQL injection [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section with line numbers
sed -n '50,120p' internal/dao/agent_session.go

# Find where orderby/desc are defined/received in this file
rg -n "orderby|desc\b|Order\\(" internal/dao/agent_session.go

# Show DAO method signature around the snippet (best effort)
rg -n "ListByAgentIDPaged|Paged|ByAgent" internal/dao/agent_session.go

Repository: infiniflow/ragflow

Length of output: 2190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Where ListByAgentIDPaged is called
rg -n "ListByAgentIDPaged\\(" -S internal | head -n 50

# Trace how orderby/desc are populated (look for "orderby" string or query params)
rg -n "\\borderby\\b" -S internal | head -n 80
rg -n "\\bdesc\\b" -S internal/handler internal/service 2>/dev/null | head -n 80

Repository: infiniflow/ragflow

Length of output: 16829


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' internal/handler/agent.go
sed -n '200,320p' internal/service/agent.go

# Narrow search for any allowlist/sanitization of orderby in agent flow
rg -n "orderby.*(allowed|allow|whitelist|map|switch|case)" internal/handler/agent.go internal/service/agent.go internal/dao/agent_session.go || true

Repository: infiniflow/ragflow

Length of output: 9598


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect internal/dao/agent_session.go imports + the full ListByAgentIDPaged function for exact context
sed -n '1,120p' internal/dao/agent_session.go
sed -n '120,220p' internal/dao/agent_session.go

# 2) Inspect the handler code around where orderby/desc are set for agent session listing
sed -n '330,470p' internal/handler/agent.go

# 3) Check entity fields for AgentSession to ensure allowlist column names match actual struct/columns
fd -a -e go internal/entity | head -n 50
rg -n "type AgentSession struct" internal/entity || true
ls internal/entity | rg -i "agent|session" || true
fd -a "agent_session.go" internal/entity || true
fd -a "session.go" internal/entity || true

Repository: infiniflow/ragflow

Length of output: 8416


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' internal/service/agent.go
sed -n '220,320p' internal/service/agent.go
rg -n "ListAgentSessions\\(" internal/service/agent.go
rg -n "Orderby" internal/service/agent.go

Repository: infiniflow/ragflow

Length of output: 9737


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check GORM version from go.mod
cat go.mod | rg -n "gorm\\.io/gorm" || true
cat go.mod | rg -n "gorm\\.io/datatypes|gorm\\.io/driver" || true

# See if clause.OrderByColumn is used anywhere in repo
rg -n "gorm\\.io/gorm/clause|OrderByColumn" internal | head -n 50

Repository: infiniflow/ragflow

Length of output: 119


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the AgentSession entity definition
rg -n "type AgentSession struct" internal/entity -S || true
fd --full-path "*session*.go" internal/entity | head -n 50
fd --full-path "*agent*session*.go" internal/entity | head -n 50

Repository: infiniflow/ragflow

Length of output: 428


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' internal/entity/canvas.go
rg -n "type AgentSession struct" -n internal/entity/canvas.go
sed -n '1,80p' internal/entity/canvas.go | nl -ba | sed -n '1,120p'

Repository: infiniflow/ragflow

Length of output: 4439


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "type BaseModel struct" internal/entity -S
rg -n "create_time|update_time" internal/entity -S
sed -n '1,200p' internal/entity/base_model.go 2>/dev/null || true
fd --full-path "base*model*.go" internal/entity | head -n 20

Repository: infiniflow/ragflow

Length of output: 3139


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,160p' internal/entity/base.go
rg -n "gorm\\.io/gorm/clause" -S . || true
rg -n "OrderByColumn" -S . || true

Repository: infiniflow/ragflow

Length of output: 4482


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Best-effort: look into module cache for clause.OrderByColumn definition
if command -v go >/dev/null 2>&1; then
  GOPATH="$(go env GOPATH 2>/dev/null || true)"
  if [ -n "${GOPATH:-}" ] && [ -d "$GOPATH/pkg/mod" ]; then
    rg -n "type OrderByColumn" "$GOPATH/pkg/mod" 2>/dev/null | head -n 20 || true
    rg -n "OrderByColumn" "$GOPATH/pkg/mod/gorm.io/gorm@"*/clause/* 2>/dev/null | head -n 50 || true
  else
    echo "GOPATH/pkg/mod not found"
  fi
else
  echo "go not available"
fi

# Confirm clause import usage in repo (should be none)
rg -n "gorm\\.io/gorm/clause" -S . || true

Repository: infiniflow/ragflow

Length of output: 87


Prevent SQL injection from user-controlled orderby in AgentSessionDAO.ListByAgentIDPaged.

internal/dao/agent_session.go builds order := orderby (only defaulting when empty) and then calls query := base.Order(order), so the orderby query param reaches GORM as raw ORDER BY SQL (not identifier-escaped) via internal/handler/agent.go → service → DAO.

Fix: allowlist orderby to a fixed set of permitted column names (e.g., id, name, create_time, update_time, etc.) and construct ORDER BY <mapped_column> <ASC|DESC> using only the mapped value and the desc boolean.

🤖 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 `@internal/dao/agent_session.go` around lines 72 - 83,
AgentSessionDAO.ListByAgentIDPaged currently assigns user-controlled orderby
directly to order and passes it into base.Order(order), enabling SQL injection;
replace that by validating orderby against a hardcoded allowlist (e.g.,
map[string]string
{"id":"id","name":"name","create_time":"create_time","update_time":"update_time"})
and mapping the input to the canonical column name, then build order as
mappedColumn + (desc ? " DESC" : " ASC") and pass only that into base.Order;
ensure the code references the existing orderby variable, the desc boolean, and
the call site base.Order(order) so no raw user input flows into the ORDER BY
clause.

Comment thread internal/handler/agent.go
Comment on lines +467 to +470
session, err := h.agentService.GetAgentSession(agentID, sessionID)
if err != nil {
c.JSON(http.StatusOK, gin.H{"code": common.CodeNotFound, "data": false, "message": "Session not found!"})
return
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the referenced handler code
sed -n '430,540p' internal/handler/agent.go | cat -n

# Find definitions/usages of GetAgentSession / DeleteAgentSessionItem handlers
rg -n "GetAgentSession|DeleteAgentSessionItem|DeleteAgentSessions" internal/handler/agent.go

# Inspect imports to see available error checks (gorm, errors.Is, etc.)
sed -n '1,120p' internal/handler/agent.go | cat -n

# Search for gorm.ErrRecordNotFound and common CodeServerError usage across the repo
rg -n "gorm\.ErrRecordNotFound|CodeServerError" -S internal | head -n 50

# Inspect the agentService implementations that return errors for session read/delete
# (search for GetAgentSession / DeleteAgentSessionByID)
rg -n "func \\(.*\\) GetAgentSession\\b|GetAgentSession\\(" -S internal | head -n 50
rg -n "func \\(.*\\) DeleteAgentSessionByID\\b|DeleteAgentSessionByID\\(" -S internal | head -n 50

Repository: infiniflow/ragflow

Length of output: 15393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- internal/handler/agent.go (lines ~450-520) ----"
sed -n '440,520p' internal/handler/agent.go | cat -n

echo "---- internal/handler/agent.go (lines ~500-520) ----"
sed -n '490,520p' internal/handler/agent.go | cat -n

echo "---- internal/service/agent.go (GetAgentSession / DeleteAgentSessionByID) ----"
sed -n '220,310p' internal/service/agent.go | cat -n

Repository: infiniflow/ragflow

Length of output: 8498


Differentiate not-found from internal failures in agent session handlers.

GetAgentSession and DeleteAgentSessionItem currently return common.CodeNotFound for any agentService error, masking backend/DB/infra failures as “Session not found!” and breaking client retry/diagnostics.

Additionally, internal/service/agent.go turns gorm.ErrRecordNotFound into fmt.Errorf("session not found") (without wrapping), so handler-level errors.Is(err, gorm.ErrRecordNotFound) can’t reliably distinguish not-found vs internal errors—use a sentinel/typed error or wrap the original error so handlers can map non-not-found errors to common.CodeServerError (and log).

🤖 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 `@internal/handler/agent.go` around lines 467 - 470, The handler currently
treats any error from agentService as "Session not found"; update the service
and handlers so not-found and internal errors are distinguishable: in
internal/service/agent.go return a sentinel exported error (e.g.
ErrSessionNotFound) or wrap gorm.ErrRecordNotFound with %w when a record is
absent in functions like GetAgentSession/DeleteAgentSessionItem, and update the
handler code that calls h.agentService.GetAgentSession and
DeleteAgentSessionItem to use errors.Is(err, service.ErrSessionNotFound) to
return common.CodeNotFound with the "Session not found" response, otherwise log
the error and return common.CodeServerError (and an appropriate message) for
internal failures; ensure service functions preserve the original error via
wrapping so errors.Is works.

Comment thread internal/handler/agent.go
Comment on lines +547 to +550
if err := c.ShouldBindJSON(&body); err != nil {
// Empty / missing body is OK — means no-op.
c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": true, "message": ""})
return
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print the reported region with line numbers
sed -n '520,590p' internal/handler/agent.go | nl -ba | sed -n '1,120p'

# Show the full import block to see whether io is already imported
rg -n "import \\(" -n internal/handler/agent.go
sed -n '1,120p' internal/handler/agent.go | nl -ba

# Find all ShouldBindJSON occurrences in this file
rg -n "ShouldBindJSON\\(" internal/handler/agent.go

Repository: infiniflow/ragflow

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== internal/handler/agent.go lines 520-590 ==="
sed -n '520,590p' internal/handler/agent.go | cat -n

echo "=== import block (first 140 lines) ==="
sed -n '1,140p' internal/handler/agent.go | cat -n

echo "=== all ShouldBindJSON occurrences in file ==="
rg -n "ShouldBindJSON\\(" internal/handler/agent.go

Repository: infiniflow/ragflow

Length of output: 8115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for existing patterns around ShouldBindJSON error handling
rg -n "ShouldBindJSON\\(" internal | head -n 50

# Search for io.EOF handling in the codebase
rg -n "io\\.EOF" . || true

# Search for invalid request / argument error responses near binding errors
rg -n "invalid request body|CodeArgumentError.*bind|ShouldBind.*CodeArgumentError|CodeArgumentError.*ShouldBind" internal || true

Repository: infiniflow/ragflow

Length of output: 4679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect tenant.go around the CodeBadRequest invalid request body pattern
sed -n '560,630p' internal/handler/tenant.go | cat -n

# Inspect one representative handler that just returns on bind errors
sed -n '300,470p' internal/handler/file.go | cat -n

# Inspect internal/handler/search.go around one bind error handler
sed -n '60,120p' internal/handler/search.go | cat -n

# Inspect internal/handler/user.go around one bind error handler
sed -n '40,90p' internal/handler/user.go | cat -n

Repository: infiniflow/ragflow

Length of output: 12192


Bulk-delete should return an error for malformed JSON
In internal/handler/agent.go (lines 547-550), DeleteAgentSessions treats any c.ShouldBindJSON(&body) error as an “empty/missing body” no-op, so invalid JSON returns common.CodeSuccess (data: true) while skipping deletion.

Suggested fix
 import (
 	"errors"
 	"fmt"
+	"io"
 	"mime/multipart"
 	"net/http"
 	"net/url"
@@
 if err := c.ShouldBindJSON(&body); err != nil {
-	// Empty / missing body is OK — means no-op.
-	c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": true, "message": ""})
+	if errors.Is(err, io.EOF) {
+		// Empty / missing body is OK — means no-op.
+		c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": true, "message": ""})
+		return
+	}
+	c.JSON(http.StatusOK, gin.H{"code": common.CodeArgumentError, "data": false, "message": "invalid request body"})
 	return
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := c.ShouldBindJSON(&body); err != nil {
// Empty / missing body is OK — means no-op.
c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": true, "message": ""})
return
if err := c.ShouldBindJSON(&body); err != nil {
if errors.Is(err, io.EOF) {
// Empty / missing body is OK — means no-op.
c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": true, "message": ""})
return
}
c.JSON(http.StatusOK, gin.H{"code": common.CodeArgumentError, "data": false, "message": "invalid request body"})
return
}
🤖 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 `@internal/handler/agent.go` around lines 547 - 550, DeleteAgentSessions
currently treats any error from c.ShouldBindJSON(&body) as an empty/no-op and
returns common.CodeSuccess; instead detect genuine empty body vs malformed JSON
and return an error for malformed input. Change the c.ShouldBindJSON(&body)
error handling: if the error is an EOF/empty-body case (use errors.Is(err,
io.EOF) or check for empty body) then keep the no-op behavior, otherwise respond
with HTTP 400 and a JSON error (non-success code) describing the malformed JSON;
update the branch that currently returns common.CodeSuccess so only empty-body
errors produce that response and all other binding errors return an error
response. Ensure you modify the error handling around c.ShouldBindJSON(&body) in
DeleteAgentSessions accordingly.

Comment thread internal/handler/agent.go
Comment on lines +672 to +684
ext := c.DefaultQuery("ext", "markdown")

blob, err := h.agentService.DownloadAttachment(user.ID, attachmentID)
if err != nil {
c.JSON(http.StatusOK, gin.H{"code": common.CodeServerError, "data": false, "message": err.Error()})
return
}

contentType := utility.GetContentType(ext, "")
if contentType == "" {
contentType = "application/octet-stream"
}
c.Data(http.StatusOK, contentType, blob)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent client-driven MIME spoofing in attachment downloads.

ext directly controls response Content-Type, and response is served inline. A crafted request can force active content types for user-controlled blobs.

Suggested fix
-	ext := c.DefaultQuery("ext", "markdown")
+	ext := utility.GetFileExtension(attachmentID)
@@
 	contentType := utility.GetContentType(ext, "")
 	if contentType == "" {
 		contentType = "application/octet-stream"
 	}
+	c.Header("X-Content-Type-Options", "nosniff")
+	c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+url.QueryEscape(attachmentID))
 	c.Data(http.StatusOK, contentType, blob)
🤖 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 `@internal/handler/agent.go` around lines 672 - 684, The handler currently
trusts the client-supplied ext and serves blobs inline, enabling MIME spoofing;
update the attachment download logic (around h.agentService.DownloadAttachment,
ext, utility.GetContentType, contentType and c.Data) to ignore or not trust the
ext query for determining Content-Type, instead sniff the blob (e.g., use
http.DetectContentType or equivalent) to derive contentType and fall back to
"application/octet-stream" if unknown, map/normalize unsafe types (e.g.,
downgrade "text/html", "application/javascript" to "text/plain" or
"application/octet-stream"), and force a Content-Disposition: attachment header
(with a safe filename) so responses are downloaded rather than rendered inline.

Comment thread internal/service/agent.go
Comment on lines +304 to +307
belongs, err := s.agentSessionDAO.BelongsToAgent(id, agentID)
if err != nil || !belongs {
res.Errors = append(res.Errors, fmt.Sprintf("the agent doesn't own the session %s", id))
continue
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Separate DAO errors from “not owned” results in bulk delete.

Line 305 currently treats DB failures the same as !belongs, so transient/data-layer issues get misreported as ownership errors instead of actual verification failures.

🔧 Suggested fix
 		belongs, err := s.agentSessionDAO.BelongsToAgent(id, agentID)
-		if err != nil || !belongs {
+		if err != nil {
+			res.Errors = append(res.Errors, fmt.Sprintf("failed to verify ownership for session %s: %v", id, err))
+			continue
+		}
+		if !belongs {
 			res.Errors = append(res.Errors, fmt.Sprintf("the agent doesn't own the session %s", id))
 			continue
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
belongs, err := s.agentSessionDAO.BelongsToAgent(id, agentID)
if err != nil || !belongs {
res.Errors = append(res.Errors, fmt.Sprintf("the agent doesn't own the session %s", id))
continue
belongs, err := s.agentSessionDAO.BelongsToAgent(id, agentID)
if err != nil {
res.Errors = append(res.Errors, fmt.Sprintf("failed to verify ownership for session %s: %v", id, err))
continue
}
if !belongs {
res.Errors = append(res.Errors, fmt.Sprintf("the agent doesn't own the session %s", id))
continue
}
🤖 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 `@internal/service/agent.go` around lines 304 - 307, The current check
conflates DAO errors and ownership falsehoods for
s.agentSessionDAO.BelongsToAgent; change the logic to first handle a non-nil err
returned by BelongsToAgent (e.g. append a distinct error like "failed to verify
ownership for session %s: <err>" or record it as an internal/DAO error) and only
when err == nil evaluate belongs and append the existing "the agent doesn't own
the session %s" message; update the block around
s.agentSessionDAO.BelongsToAgent, belongs, and err to separate these two cases
so transient DB failures are not reported as ownership failures.

@hunnyboy1217 hunnyboy1217 force-pushed the feat/go-agent-session-download-logs-api branch from 6d87f69 to 37f704f Compare June 5, 2026 04:27
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 `@internal/handler/agent.go`:
- Around line 420-423: The code treats both service errors and permission
denials the same when calling h.agentService.CheckCanvasAccess(user.ID,
agentID); change the logic to first check if err != nil and return an
internal/server error response (e.g., http.StatusInternalServerError with an
appropriate error code and log the err) and only if err == nil and ok == false
return the existing permission-denied response; apply this fix to every call
site that invokes CheckCanvasAccess (the calls using
h.agentService.CheckCanvasAccess(user.ID, agentID) so the handler returns a 5xx
on service/DB/tenant errors and a 403/permission response when access is
explicitly false).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54b3ae1a-84cb-4b4d-98ef-e53d136b2950

📥 Commits

Reviewing files that changed from the base of the PR and between 6d87f69 and 37f704f.

📒 Files selected for processing (5)
  • internal/dao/agent_session.go
  • internal/entity/canvas.go
  • internal/handler/agent.go
  • internal/router/router.go
  • internal/service/agent.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/entity/canvas.go
  • internal/router/router.go
  • internal/dao/agent_session.go
  • internal/service/agent.go

Comment thread internal/handler/agent.go
Comment on lines +420 to +423
ok, err := h.agentService.CheckCanvasAccess(user.ID, agentID)
if err != nil || !ok {
c.JSON(http.StatusOK, gin.H{"code": common.CodeOperatingError, "data": false, "message": "Make sure you have permission to access the agent."})
return
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Split access-check errors from real permission denials.

Lines 420, 493, 531, and 627 all treat CheckCanvasAccess(... ) failures and !ok the same way. That service already returns (false, err) for DAO/tenant lookup failures, so DB or tenant-service outages get reported back as “no permission” instead of server errors.

Suggested fix
 	ok, err := h.agentService.CheckCanvasAccess(user.ID, agentID)
-	if err != nil || !ok {
+	if err != nil {
+		common.Warn("check canvas access failed", zap.String("agent_id", agentID), zap.Error(err))
+		c.JSON(http.StatusOK, gin.H{"code": common.CodeServerError, "data": false, "message": "Internal server error"})
+		return
+	}
+	if !ok {
 		c.JSON(http.StatusOK, gin.H{"code": common.CodeOperatingError, "data": false, "message": "Make sure you have permission to access the agent."})
 		return
 	}

Also applies to: 493-496, 531-534, 627-630

🤖 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 `@internal/handler/agent.go` around lines 420 - 423, The code treats both
service errors and permission denials the same when calling
h.agentService.CheckCanvasAccess(user.ID, agentID); change the logic to first
check if err != nil and return an internal/server error response (e.g.,
http.StatusInternalServerError with an appropriate error code and log the err)
and only if err == nil and ok == false return the existing permission-denied
response; apply this fix to every call site that invokes CheckCanvasAccess (the
calls using h.agentService.CheckCanvasAccess(user.ID, agentID) so the handler
returns a 5xx on service/DB/tenant errors and a 403/permission response when
access is explicitly false).

Ports the following Python endpoints from agent_api.py to the Go layer,
closing subtask infiniflow#15654:

- GET  /api/v1/agents/:agent_id                          (GetAgent)
- GET  /api/v1/agents/:agent_id/sessions                 (ListAgentSessions)
- GET  /api/v1/agents/:agent_id/sessions/:session_id     (GetAgentSession)
- DELETE /api/v1/agents/:agent_id/sessions/:session_id   (DeleteAgentSessionItem)
- DELETE /api/v1/agents/:agent_id/sessions               (DeleteAgentSessions – bulk)
- GET  /api/v1/agents/:agent_id/logs/:message_id         (GetAgentLogs – Redis)
- GET  /api/v1/agents/download                           (DownloadAgentFile)
- GET  /api/v1/agents/attachments/:attachment_id/download (DownloadAttachment)

New files:
  internal/dao/agent_session.go   – DAO for conversation table (agent view)

Changed files:
  internal/entity/canvas.go       – AgentSession entity (conversation table)
  internal/service/agent.go       – service methods for all new endpoints
  internal/handler/agent.go       – HTTP handlers for all new endpoints
  internal/router/router.go       – route registration

Ownership checks mirror Python:
  - canvas access (team permission or owner) gates session read/single-delete
  - canvas ownership gates bulk session delete
  - attachment download uses caller's tenant ID as the storage bucket
@hunnyboy1217 hunnyboy1217 force-pushed the feat/go-agent-session-download-logs-api branch from 37f704f to f5defcb Compare June 5, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Subtask]: Implement Agent session / download / logs / attachment APIs in Go

1 participant