Skip to content

Commit 8abf722

Browse files
authored
Merge pull request #37 from BjornMelin/feat/ai-chat-attachments
feat: ai chat attachments and file uploads + Vercel blob handling
2 parents 7b477bf + fad2b83 commit 8abf722

42 files changed

Lines changed: 2765 additions & 706 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ bun run fetch:models # Update AI model catalog (requires AI_GATEWAY_AP
7171
- OpenAI-compatible base URL is `https://ai-gateway.vercel.sh/v1` (used for OpenAI SDK compatibility and `GET /models`).
7272
- `bun run fetch:models` writes a model catalog JSON (default: `docs/ai-gateway-models.json`).
7373

74+
## Project Chat Attachments
75+
76+
- Project chat supports **document attachments** (PDF/DOCX/PPTX/XLSX/TXT/MD) rendered using vendored AI Elements `attachments` (inline variant).
77+
- The UI uses **upload-before-send** via `POST /api/upload` (sync ingest by default) and sends hosted Blob URLs as AI SDK `FileUIPart` values.
78+
- The composer enforces `maxFileSize = budgets.maxUploadBytes` (default 25 MB) and `maxFiles = 5` per message.
79+
- Follow-ups during active durable chat sessions use `POST /api/chat/:runId` and accept optional `files` alongside `message` (`messageId` required).
80+
- Document file parts are persisted in UI message history; not passed to the model directly; grounding relies on ingestion + retrieval (see `docs/architecture/spec/SPEC-0029-chat-attachments.md`).
81+
7482
## Documentation standards
7583

7684
- Product requirements: `PRD.md` is the canonical PRD; keep it aligned with `docs/specs/requirements.md`.

PRD.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Product Requirements Document (PRD) — ai-agent-builder
22

3-
**Version**: 0.2.1
4-
**Date**: 2026-02-06
3+
**Version**: 0.2.2
4+
**Date**: 2026-02-10
55
**Owner**: Bjorn Melin
66

77
## Executive summary
@@ -62,14 +62,16 @@ plan” without redoing analysis.
6262
3. User uploads files (PDF/DOCX/PPTX/XLSX/TXT/MD).
6363
4. System extracts text and structure, chunks content, and indexes it for
6464
retrieval.
65-
5. User chats with the project knowledge base (streaming responses).
65+
5. User chats with the project knowledge base (streaming responses) and can
66+
attach additional documents directly in chat (docs-only scope).
6667
6. User starts a durable run that performs research and generates artifacts
6768
(PRD/specs/ADRs/security/roadmap/prompts).
6869
7. User exports the latest artifacts and citations as a deterministic zip.
6970

7071
### Journey B: Iterate on an existing project
7172

72-
1. User uploads additional material and/or adds instructions in chat.
73+
1. User uploads additional material, attaches documents, and adds instructions
74+
in chat.
7375
2. User regenerates or refines a subset of artifacts.
7476
3. System versions artifacts and updates export output deterministically.
7577

@@ -127,6 +129,7 @@ Primary spec/ADR references:
127129
### Epic 4: Chat with retrieval augmentation
128130

129131
- Project-scoped chat with streaming UI. (FR-008, PR-001)
132+
- Chat messages can include document attachments (uploaded/ingested before send) and render as part of chat history. (FR-003, FR-008, FR-019)
130133
- Retrieval-augmented responses using project KB. (FR-019)
131134
- Agent mode selection per chat. (FR-009)
132135

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ trail).
1616
## What it does
1717

1818
- Ingests source material: PDFs, slides, docs, markdown, spreadsheets.
19+
- Project chat supports attaching documents directly in the composer (upload-before-send via `POST /api/upload`) and in follow-ups during active durable sessions.
1920
- Supports project and global search across projects, uploads, chunks, artifacts, and runs.
2021
- Enforces per-user project ownership for all project-scoped reads/writes.
2122
- Applies server-side search guardrails (strict query validation + rate limiting).
@@ -66,6 +67,7 @@ flowchart LR
6667
- Provisioning + deploy automation: [`docs/architecture/spec/SPEC-0018-infrastructure-provisioning-and-secrets-for-target-apps.md`](./docs/architecture/spec/SPEC-0018-infrastructure-provisioning-and-secrets-for-target-apps.md)
6768
- Sandbox verification jobs: [`docs/architecture/spec/SPEC-0019-sandbox-build-test-and-ci-execution.md`](./docs/architecture/spec/SPEC-0019-sandbox-build-test-and-ci-execution.md)
6869
- Workspace + search UX: [`docs/architecture/spec/SPEC-0020-project-workspace-and-search.md`](./docs/architecture/spec/SPEC-0020-project-workspace-and-search.md)
70+
- Project chat attachments: [`docs/architecture/spec/SPEC-0029-chat-attachments.md`](./docs/architecture/spec/SPEC-0029-chat-attachments.md)
6971
- Agent Skills (progressive disclosure): [`docs/architecture/spec/SPEC-0027-agent-skills-runtime-integration.md`](./docs/architecture/spec/SPEC-0027-agent-skills-runtime-integration.md)
7072
- Skills registry installs: [`docs/architecture/spec/SPEC-0028-skills-registry-ui-and-bundled-installs.md`](./docs/architecture/spec/SPEC-0028-skills-registry-ui-and-bundled-installs.md)
7173
- GitOps + deploy ADRs:

docs/architecture/adr/ADR-0026-orchestration-vercel-workflow-devkit-for-interactive-runs.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Version: 0.1
66
Date: 2026-02-03
77
Supersedes: []
88
Superseded-by: []
9-
Related: [ADR-0005, ADR-0006, ADR-0011, ADR-0021]
9+
Related: [ADR-0005, ADR-0006, ADR-0011, ADR-0021, ADR-0030]
1010
Tags: [architecture, reliability, workflows]
1111
References:
1212
- [Vercel Workflow](https://vercel.com/docs/workflow)
@@ -168,7 +168,8 @@ flowchart LR
168168
WF --> VECTOR[(Upstash Vector)]
169169
WF --> REDIS[(Upstash Redis)]
170170
171-
Upload[POST /api/upload<br/>enqueue to QStash] --> Q[QStash<br/>POST /api/jobs/ingest-file]
171+
UploadToken[POST /api/upload<br/>Blob token exchange] --> Register[POST /api/upload/register<br/>register + ingest (or enqueue)]
172+
Register --> Q[QStash<br/>POST /api/jobs/ingest-file]
172173
Q --> Ingest[POST /api/jobs/ingest-file]
173174
Ingest --> DB
174175
Ingest --> VECTOR
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
---
2+
ADR: 0030
3+
Title: Chat attachments via Vercel Blob client uploads + /api/upload/register (hosted URLs + inline AI Elements)
4+
Status: Implemented
5+
Version: 0.1
6+
Date: 2026-02-10
7+
Supersedes: []
8+
Superseded-by: []
9+
Related: [ADR-0011, ADR-0009, ADR-0006, ADR-0026]
10+
Tags: [chat, attachments, ai-elements, ai-sdk, upload, ingestion, workflow]
11+
Related-Requirements: [FR-003, FR-008, FR-019, PR-001, NFR-008, NFR-010, IR-006]
12+
References:
13+
- [AI Elements attachments](https://elements.ai-sdk.dev/components/attachments.md)
14+
- [AI SDK useChat attachments](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot#attachments)
15+
- [AI SDK FileUIPart](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message#fileuipart)
16+
---
17+
18+
## Status
19+
20+
Implemented — 2026-02-10.
21+
22+
## Context
23+
24+
Project Chat is the primary UX for interacting with a project knowledge base.
25+
We already support:
26+
27+
- uploading documents to a project (stored in Blob, ingested, indexed)
28+
- multi-turn durable chat sessions (Workflow DevKit)
29+
- AI Elements primitives for chat UI
30+
31+
We need first-class **document attachments** in chat so users can:
32+
33+
1. attach a file in the chat composer
34+
2. have it ingested for RAG grounding
35+
3. see the attachment in message history
36+
4. attach files in follow-up messages during an active durable chat run
37+
38+
## Decision Drivers
39+
40+
- Reuse the existing, validated upload + ingestion pipeline:
41+
- `POST /api/upload` for Vercel Blob client upload token exchange
42+
- `POST /api/upload/register` for registration, dedupe, and ingestion
43+
- Avoid base64/data URL message payloads for performance and reliability.
44+
- Preserve resume-safe chat history (stream markers + persisted UI messages).
45+
- Keep model prompts clean; rely on ingestion + retrieval for documents.
46+
- Minimize new endpoints and contracts; strict TypeScript + Zod validation.
47+
48+
## Alternatives Considered
49+
50+
### A. Embed attachments directly as data URLs in chat requests
51+
52+
Pros:
53+
54+
- Single request; no separate upload call.
55+
56+
Cons:
57+
58+
- Large payloads, memory pressure, and brittle streaming on non-trivial docs.
59+
- Harder to persist and replay reliably.
60+
61+
### B. Create a dedicated chat upload endpoint (`/api/chat/upload`)
62+
63+
Pros:
64+
65+
- Chat-specific contract could include `messageId`, status, etc.
66+
67+
Cons:
68+
69+
- Duplicates the existing allowlist/ingest/dedupe pipeline.
70+
- Adds long-term maintenance surface area for minimal value.
71+
72+
### C. Use Vercel Blob client uploads + `POST /api/upload/register` and send hosted file URLs as `FileUIPart` (**Chosen**)
73+
74+
Pros:
75+
76+
- One canonical ingestion path for the whole app.
77+
- Hosted URLs keep chat payloads small; `FileUIPart.url` supports hosted URLs.[^ai-sdk-fileuipart]
78+
- Clean separation: upload/ingest first, then chat.
79+
- Works for follow-ups by extending `POST /api/chat/:runId` contract.
80+
81+
Cons:
82+
83+
- Sync ingest can add latency; may need a hybrid/async ingest UX later.
84+
- Blob URLs are public; requires care around lifecycle/cleanup (future).
85+
86+
### D. Stream multipart upload through the chat streaming endpoint
87+
88+
Pros:
89+
90+
- Single action; can stream progress inline.
91+
92+
Cons:
93+
94+
- High complexity (multipart streaming + retries + partial failure handling).
95+
- Not needed for docs-only scope.
96+
97+
## Decision Framework (must be ≥ 9.0)
98+
99+
Weights:
100+
101+
- Solution leverage: 35%
102+
- Application value: 30%
103+
- Maintenance and cognitive load: 25%
104+
- Architectural adaptability: 10%
105+
106+
| Option | Leverage | Value | Maintenance | Adaptability | Weighted total |
107+
| --- | ---: | ---: | ---: | ---: | ---: |
108+
| A. Data URLs in chat | 6.8 | 7.4 | 5.8 | 7.2 | 6.79 |
109+
| B. Dedicated chat upload endpoint | 7.2 | 8.1 | 7.0 | 8.0 | 7.46 |
110+
| C. Blob client uploads + `/api/upload/register` + hosted URLs | 9.4 | 9.2 | 8.9 | 9.0 | **9.19** |
111+
| D. Multipart streaming upload | 6.4 | 8.0 | 4.2 | 8.6 | 6.63 |
112+
113+
## Decision
114+
115+
Implement chat attachments as:
116+
117+
1. Composer uses vendored AI Elements `PromptInput` attachments + `Attachments` inline UI.[^ai-elements-attachments]
118+
2. Client uploads attachment bytes via `@vercel/blob/client upload()` using `POST /api/upload` as the token exchange endpoint.
119+
3. Client registers and ingests the uploaded blobs via `POST /api/upload/register` (sync ingest by default).
120+
4. Client sends the chat message using hosted file URLs (no data URLs) as `FileUIPart[]` alongside text.
121+
5. Extend durable follow-ups: `POST /api/chat/:runId` accepts `files?: FileUIPart[]` and resumes the workflow hook with attachments.
122+
6. Persist file parts in UI message history and include them in `data-workflow` user-message markers for resume-safe replay.
123+
7. Do not pass document file parts to the model directly; strip file parts when building model messages and append a filename note, relying on ingestion + retrieval (SPEC-0004).
124+
125+
Implementation details and contracts are specified in:
126+
127+
- [SPEC-0029](../spec/SPEC-0029-chat-attachments.md)
128+
129+
## Consequences
130+
131+
### Positive outcomes
132+
133+
- Users can attach documents in chat and see them in history.
134+
- Attachments in follow-ups work during active durable sessions.
135+
- Upload/ingestion remains canonical and deduped.
136+
- Chat payloads remain small and stream-resume-safe.
137+
138+
### Negative outcomes / risks
139+
140+
- Sync ingest can add noticeable latency for large docs.
141+
- Mitigation: keep strict upload budgets; consider a hybrid or async ingest UX if needed.
142+
- Public blob URLs can be shared out-of-band.
143+
- Mitigation: keep app access private; consider signed URLs or authenticated proxying in a future hardening pass.
144+
- Orphaned uploads if the user abandons mid-flow.
145+
- Mitigation (future): add cleanup/GC for unattached uploads or reference counting by project.
146+
147+
## References
148+
149+
[^ai-elements-attachments]: <https://elements.ai-sdk.dev/components/attachments.md>
150+
[^ai-sdk-fileuipart]: <https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message#fileuipart>

docs/architecture/adr/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- [ADR-0027](./ADR-0027-preview-resource-governance-for-bot-branches-vercel-neon.md) — Preview resource governance for bot branches (Vercel + Neon) (Implemented)
2828
- [ADR-0028](./ADR-0028-agent-skills-progressive-disclosure-hybrid-fs-db.md) — Agent Skills progressive disclosure (hybrid filesystem + DB) (Implemented)
2929
- [ADR-0029](./ADR-0029-skills-registry-integration-ui-installs-and-bundled-db-files.md) — Skills registry integration (skills.sh) with UI installs + bundled DB skill files (Implemented)
30+
- [ADR-0030](./ADR-0030-chat-attachments-reuse-upload-pipeline.md) — Chat attachments reuse /api/upload pipeline (hosted URLs + inline AI Elements) (Implemented)
3031

3132
## Implementation & deployment automation
3233

docs/architecture/api.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ See:
2222
## Upload & ingestion (background jobs via QStash)
2323

2424
- `POST /api/upload`
25-
- stores originals in Vercel Blob
26-
- writes metadata in Neon Postgres
27-
- may enqueue async ingestion jobs via Upstash QStash (`async=true`)
25+
- Vercel Blob client upload token exchange (`@vercel/blob/client upload()` `handleUploadUrl`)
26+
- issues scoped client tokens after authenticating and authorizing the project
27+
- `POST /api/upload/register`
28+
- registers already-uploaded blobs for a project
29+
- writes metadata in Neon Postgres (idempotent by sha256)
30+
- ingests inline by default; may enqueue async ingestion jobs via Upstash QStash (`async=true`)
2831
- `POST /api/jobs/ingest-file` (QStash-signed)
2932
- executes extract → chunk → embed → index asynchronously
3033
- validates `storageKey` is an HTTPS Vercel Blob URL under

docs/architecture/data-model.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,32 @@ Stores non-secret resource identity + metadata.
146146
- `started_at`, `ended_at`
147147
- `metadata` (JSON; promotion info, commit SHA)
148148

149+
### `chat_threads`
150+
151+
Project-scoped chat threads backed by Workflow DevKit run IDs.
152+
153+
- `id`
154+
- `project_id`
155+
- `mode` (agent mode id)
156+
- `title`
157+
- `status` (running/waiting/succeeded/failed/canceled)
158+
- `workflow_run_id` (unique; used for resumable stream + follow-ups)
159+
- `last_activity_at`
160+
- `ended_at` (nullable)
161+
- `created_at`, `updated_at`
162+
163+
### `chat_messages`
164+
165+
Persisted UI messages for a thread.
166+
167+
- `id` (AI SDK UI message id; stable across replay)
168+
- `thread_id`
169+
- `role` (user/assistant/system)
170+
- `parts` (JSON array)
171+
- Stores AI SDK UI parts (text, tool parts, reasoning, etc.).
172+
- May include document attachments as `FileUIPart` parts (hosted Blob URLs).
173+
- `created_at`
174+
149175
## Vector indexing
150176

151177
Namespaces:

docs/architecture/spec/SPEC-0003-upload-ingestion-pipeline.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
spec: SPEC-0003
33
title: Upload + ingestion pipeline
4-
version: 0.4.0
5-
date: 2026-02-03
4+
version: 0.4.1
5+
date: 2026-02-10
66
owners: ["Bjorn Melin"]
77
status: Implemented
88
related_requirements: ["FR-003", "FR-004", "FR-005", "FR-006", "FR-007", "PR-003", "IR-006", "IR-005", "IR-001"]
@@ -18,6 +18,10 @@ See [SPEC-0021](./SPEC-0021-full-stack-finalization-fluid-compute-neon-upstash-a
1818
for the cross-cutting “finalization” plan that integrates ingestion with
1919
durable runs, retrieval, and the workspace UI.
2020

21+
See [SPEC-0029](./SPEC-0029-chat-attachments.md) for the Project Chat document
22+
attachments flow that uses Vercel Blob client uploads and registers/ingests via
23+
`POST /api/upload/register`.
24+
2125
## Context
2226

2327
Users upload pitch decks, PDFs, docs, and spreadsheets. The system must preserve originals, extract text reliably, and build a vector index to support grounded reasoning.
@@ -89,8 +93,9 @@ Requirement IDs are defined in [docs/specs/requirements.md](/docs/specs/requirem
8993

9094
### Architecture overview
9195

92-
- Upload route stores file in Blob and writes metadata in Neon.
93-
- Upload route processes independent files in parallel; async ingestion enqueues
96+
- Upload registration route writes metadata in Neon and ingests from Blob.
97+
- Upload registration processes independent files sequentially (bounded memory);
98+
async ingestion enqueues
9499
QStash jobs with per-file deduplication ids and labels.
95100
([QStash deduplication](https://upstash.com/docs/qstash/features/deduplication),
96101
[QStash publish API](https://upstash.com/docs/qstash/api-refence/messages/publish-a-message))
@@ -105,7 +110,9 @@ Requirement IDs are defined in [docs/specs/requirements.md](/docs/specs/requirem
105110

106111
### File-level contracts
107112

108-
- `src/app/api/upload/route.ts`: accepts uploads, stores originals, writes DB metadata.
113+
- `src/app/api/upload/route.ts`: Vercel Blob client upload token exchange (scoped client tokens).
114+
- `src/app/api/upload/register/route.ts`: registers uploaded blobs, writes DB metadata, and ingests (or enqueues async ingest).
115+
- `src/app/(app)/projects/[projectId]/chat/chat-client.tsx`: uploads chat attachments via Vercel Blob client uploads + `POST /api/upload/register` (see SPEC-0029).
109116
- `src/app/api/jobs/ingest-file/route.ts`: QStash-signed async ingestion worker (optional; used for larger inputs).
110117
- `src/lib/ingest/extract/*`: extraction adapters per file type; must preserve stable refs.
111118
- `src/lib/ingest/chunk/*`: deterministic chunking rules (stable chunk ids).
@@ -130,7 +137,7 @@ Requirement IDs are defined in [docs/specs/requirements.md](/docs/specs/requirem
130137
## Testing
131138

132139
- Unit: chunking determinism and hashing
133-
- Unit: request-level route handler tests for `/api/upload` and
140+
- Unit: request-level route handler tests for `/api/upload`, `/api/upload/register`, and
134141
`/api/jobs/ingest-file` error handling and async ingestion fallbacks.
135142
- Integration: upload → extract → index → query
136143

@@ -147,6 +154,7 @@ Requirement IDs are defined in [docs/specs/requirements.md](/docs/specs/requirem
147154
## Key files
148155

149156
- `src/app/api/upload/route.ts`
157+
- `src/app/api/upload/register/route.ts`
150158
- `src/app/api/jobs/ingest-file/route.ts`
151159
- `src/lib/ingest/extract`
152160
- `src/lib/ingest/chunk`

docs/architecture/spec/SPEC-0004-chat-retrieval-augmentation.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
spec: SPEC-0004
33
title: Chat + retrieval augmentation
4-
version: 0.4.0
5-
date: 2026-02-07
4+
version: 0.4.1
5+
date: 2026-02-10
66
owners: ["Bjorn Melin"]
77
status: Implemented
88
related_requirements: ["FR-008", "FR-009", "FR-019", "PR-001", "PR-002"]
@@ -24,6 +24,9 @@ for the canonical streaming + resumption API contracts and Workflow DevKit integ
2424
See [SPEC-0023](./SPEC-0023-ai-elements-workspace-ui-and-interaction-model.md)
2525
for the AI Elements-based workspace UI interaction model.
2626

27+
See [SPEC-0029](./SPEC-0029-chat-attachments.md) for the Project Chat document
28+
attachments UX and contracts.
29+
2730
## Context
2831

2932
Users iterate on specs and artifacts via chat. Chat must stream, persist, and use RAG to ground answers in uploads and generated artifacts.
@@ -106,7 +109,7 @@ Requirement IDs are defined in [docs/specs/requirements.md](/docs/specs/requirem
106109

107110
- `src/app/(app)/projects/[projectId]/chat/page.tsx`: UI for streaming chat and message history.
108111
- `src/app/api/chat/route.ts`: server streaming endpoint; must persist messages/tool calls.
109-
- `src/app/api/chat/[runId]/route.ts`: resume endpoint for durable chat sessions.
112+
- `src/app/api/chat/[runId]/route.ts`: resume endpoint for durable chat sessions (supports follow-up attachments).
110113
- `src/lib/data/chat.server.ts`: chat threads + messages DAL (Neon + Drizzle).
111114
- `src/workflows/chat/project-chat.workflow.ts`: durable chat workflow orchestration and persistence.
112115
- `src/lib/ai/tools/retrieval.server.ts`: retrieval tools (uploads + artifacts);

0 commit comments

Comments
 (0)