Skip to content

Commit 509ce58

Browse files
GenerQAQclaude
andcommitted
fix(api): address PR #460 code review issues (75-score)
- Return 404 instead of 500 when GetByPath fails in DownloadArtifact - Thread UserKEK through learning space creation so default skills are encrypted - Update docs for encryption-aware SDK behavior (download fallback, raw_content) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0c30e12 commit 509ce58

6 files changed

Lines changed: 38 additions & 8 deletions

File tree

docs/content/docs/(guides)/store/(features)/skill.mdx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,20 +130,38 @@ for (const fileInfo of skillDetails.fileIndex || []) {
130130
result = client.skills.get_file(skill_id=skill.id, file_path="SKILL.md")
131131
print(result.content.raw)
132132

133-
# Binary files return a URL instead
133+
# Binary files return a URL or base64-encoded content
134134
result = client.skills.get_file(skill_id=skill.id, file_path="images/diagram.png")
135-
print(result.url)
135+
if result.raw_content:
136+
# Encryption enabled — binary content returned as base64
137+
import base64
138+
data = base64.b64decode(result.raw_content)
139+
elif result.url:
140+
# No encryption — presigned URL for direct download
141+
print(result.url)
136142
```
137143

138144
```typescript title="TypeScript"
139145
const fileResult = await client.skills.getFile({ skillId: skill.id, filePath: "SKILL.md" });
140146
console.log(fileResult.content?.raw);
141147

142-
// Binary files return a URL instead
148+
// Binary files return a URL or base64-encoded content
143149
const imageResult = await client.skills.getFile({ skillId: skill.id, filePath: "images/diagram.png" });
144-
console.log(imageResult.url);
150+
if (imageResult.raw_content) {
151+
// Encryption enabled — binary content returned as base64
152+
const buffer = Buffer.from(imageResult.raw_content, 'base64');
153+
} else if (imageResult.url) {
154+
// No encryption — presigned URL for direct download
155+
console.log(imageResult.url);
156+
}
145157
```
146158
</CodeGroup>
159+
160+
The SDK uses a 3-way fallback when downloading skill files:
161+
1. **`content.raw`** — text content, returned inline
162+
2. **`raw_content`** — base64-encoded binary content (when envelope encryption is enabled)
163+
3. **`url`** — presigned S3 URL for direct download (when encryption is off)
164+
147165
</Step>
148166

149167
<Step title="Delete skill">

docs/content/docs/(guides)/tool/(features)/disk_tools.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ while (true) {
135135
{"filename": "report.pdf", "file_path": "/", "expire": 3600}
136136
```
137137

138+
<Note>
139+
When **envelope encryption** is enabled on the project, presigned URLs cannot be generated because files are encrypted at rest with a per-project key. In this case the tool returns a message directing the LLM to use the API download endpoint instead.
140+
</Note>
141+
138142
### grep_disk
139143

140144
Search file contents with regex:

docs/content/docs/(guides)/tool/(features)/skill_tools.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ Returns: Skill ID, description, and file index with MIME types.
133133

134134
Returns: File content (text) or presigned URL (binary).
135135

136+
<Note>
137+
When **envelope encryption** is enabled, binary files are returned as base64-encoded content (`raw_content` field) instead of a presigned URL, since encrypted objects cannot be served directly from S3. The tool presents the content inline to the LLM regardless of delivery method.
138+
</Note>
139+
136140
<Warning>
137141
**Read-only:** These tools can only read skill content. To execute skill scripts, use [Sandbox Tools](/tool/bash_tools#mounting-agent-skills).
138142
</Warning>

src/server/api/go/internal/modules/handler/artifact.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ func (h *ArtifactHandler) GetArtifact(c *gin.Context) {
252252

253253
artifact, err := h.svc.GetByPath(c.Request.Context(), diskID, filePath, filename)
254254
if err != nil {
255-
c.JSON(http.StatusInternalServerError, serializer.DBErr("", err))
255+
c.JSON(http.StatusNotFound, serializer.DBErr("artifact not found", err))
256256
return
257257
}
258258

@@ -330,7 +330,7 @@ func (h *ArtifactHandler) DownloadArtifact(c *gin.Context) {
330330

331331
artifact, err := h.svc.GetByPath(c.Request.Context(), diskID, filePath, filename)
332332
if err != nil {
333-
c.JSON(http.StatusInternalServerError, serializer.DBErr("", err))
333+
c.JSON(http.StatusNotFound, serializer.DBErr("artifact not found", err))
334334
return
335335
}
336336

src/server/api/go/internal/modules/handler/learning_space.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/gin-gonic/gin"
1010
"github.com/google/uuid"
11+
"github.com/memodb-io/Acontext/internal/middleware"
1112
"github.com/memodb-io/Acontext/internal/modules/model"
1213
"github.com/memodb-io/Acontext/internal/modules/serializer"
1314
"github.com/memodb-io/Acontext/internal/modules/service"
@@ -112,6 +113,7 @@ func (h *LearningSpaceHandler) Create(c *gin.Context) {
112113
ProjectID: project.ID,
113114
UserID: userID,
114115
Meta: req.Meta,
116+
UserKEK: middleware.GetUserKEK(c),
115117
})
116118
if err != nil {
117119
h.handleErr(c, err)

src/server/api/go/internal/modules/service/learning_space.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type CreateLearningSpaceInput struct {
5454
ProjectID uuid.UUID
5555
UserID *uuid.UUID
5656
Meta map[string]interface{}
57+
UserKEK []byte // optional: for envelope encryption of default skill files
5758
}
5859

5960
type UpdateLearningSpaceInput struct {
@@ -155,7 +156,7 @@ func (s *learningSpaceService) Create(ctx context.Context, in CreateLearningSpac
155156
if err := s.lsRepo.Create(ctx, ls); err != nil {
156157
return nil, fmt.Errorf("create learning space: %w", err)
157158
}
158-
if err := s.initDefaultSkills(ctx, ls); err != nil {
159+
if err := s.initDefaultSkills(ctx, ls, in.UserKEK); err != nil {
159160
return nil, err
160161
}
161162
return ls, nil
@@ -164,7 +165,7 @@ func (s *learningSpaceService) Create(ctx context.Context, in CreateLearningSpac
164165
// initDefaultSkills creates the default skills from embedded templates and
165166
// links them to the given learning space. On failure it performs best-effort
166167
// cleanup of any resources already created (skills, disks, the space itself).
167-
func (s *learningSpaceService) initDefaultSkills(ctx context.Context, ls *model.LearningSpace) (retErr error) {
168+
func (s *learningSpaceService) initDefaultSkills(ctx context.Context, ls *model.LearningSpace, userKEK []byte) (retErr error) {
168169
var createdSkills []*model.AgentSkills
169170
defer func() {
170171
if retErr == nil {
@@ -195,6 +196,7 @@ func (s *learningSpaceService) initDefaultSkills(ctx context.Context, ls *model.
195196
ProjectID: ls.ProjectID,
196197
UserID: ls.UserID,
197198
Content: content,
199+
UserKEK: userKEK,
198200
})
199201
if err != nil {
200202
return fmt.Errorf("create skill from template %s: %w", tmplPath, err)

0 commit comments

Comments
 (0)