@fatagnus/convex-feedback 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +346 -4
- package/package.json +12 -5
- package/src/convex/_generated/api.ts +1 -0
- package/src/convex/agents/feedbackInterviewAgent.ts +6 -12
- package/src/convex/apiKeys.test.ts +79 -0
- package/src/convex/apiKeys.ts +223 -0
- package/src/convex/bugReports.ts +126 -1
- package/src/convex/feedback.ts +134 -1
- package/src/convex/http.test.ts +76 -0
- package/src/convex/http.ts +630 -0
- package/src/convex/index.ts +11 -0
- package/src/convex/prompts.test.ts +185 -0
- package/src/convex/prompts.ts +605 -0
- package/src/convex/schema.ts +52 -2
- package/src/convex/ticketNumbers.ts +4 -0
- package/src/convex/tsconfig.json +24 -0
- package/src/index.ts +33 -1
- package/src/types.ts +38 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @fatagnus/convex-feedback
|
|
2
2
|
|
|
3
|
-
Bug reports and feedback collection component for Convex applications with AI analysis and
|
|
3
|
+
Bug reports and feedback collection component for Convex applications with AI analysis, email notifications, and a REST API.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -12,6 +12,9 @@ Bug reports and feedback collection component for Convex applications with AI an
|
|
|
12
12
|
- 👥 **Support Teams** - Route notifications to the right teams based on type/severity
|
|
13
13
|
- 📸 **Screenshots** - Capture or upload screenshots with reports
|
|
14
14
|
- 🔄 **Real-time** - Leverages Convex for real-time updates
|
|
15
|
+
- 🎫 **Ticket Numbers** - Human-readable ticket IDs (BUG-2025-0001, FB-2025-0001)
|
|
16
|
+
- 🔑 **API Keys** - Secure API key management for external integrations
|
|
17
|
+
- 🌐 **REST API** - HTTP endpoints for external tools (Codebuff, CLI tools, etc.)
|
|
15
18
|
|
|
16
19
|
## Installation
|
|
17
20
|
|
|
@@ -57,7 +60,25 @@ app.use(feedback);
|
|
|
57
60
|
export default app;
|
|
58
61
|
```
|
|
59
62
|
|
|
60
|
-
### 2.
|
|
63
|
+
### 2. Register HTTP Routes (Optional)
|
|
64
|
+
|
|
65
|
+
To enable the REST API for external integrations:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// convex/http.ts
|
|
69
|
+
import { httpRouter } from "convex/server";
|
|
70
|
+
import { registerFeedbackRoutes } from "@fatagnus/convex-feedback";
|
|
71
|
+
|
|
72
|
+
const http = httpRouter();
|
|
73
|
+
|
|
74
|
+
// Register feedback API routes with optional prefix
|
|
75
|
+
registerFeedbackRoutes(http, { pathPrefix: "/feedback" });
|
|
76
|
+
|
|
77
|
+
// Your other routes...
|
|
78
|
+
export default http;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Configure Environment Variables
|
|
61
82
|
|
|
62
83
|
Set these environment variables in your Convex dashboard:
|
|
63
84
|
|
|
@@ -67,7 +88,7 @@ Set these environment variables in your Convex dashboard:
|
|
|
67
88
|
| `RESEND_API_KEY` | No | Resend API key for email notifications |
|
|
68
89
|
| `RESEND_FROM_EMAIL` | No | From email address (default: `bugs@resend.dev`) |
|
|
69
90
|
|
|
70
|
-
###
|
|
91
|
+
### 4. Add React Components
|
|
71
92
|
|
|
72
93
|
Wrap your app with the `BugReportProvider` and add the `BugReportButton`:
|
|
73
94
|
|
|
@@ -103,7 +124,293 @@ function App() {
|
|
|
103
124
|
}
|
|
104
125
|
```
|
|
105
126
|
|
|
106
|
-
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## HTTP REST API
|
|
130
|
+
|
|
131
|
+
The REST API allows external tools (like Codebuff, CLI tools, CI/CD pipelines) to interact with bug reports and feedback.
|
|
132
|
+
|
|
133
|
+
### Authentication
|
|
134
|
+
|
|
135
|
+
All endpoints require an API key in the `Authorization` header:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
Authorization: Bearer fb_your_api_key_here
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Base URL
|
|
142
|
+
|
|
143
|
+
Your Convex HTTP endpoint + the path prefix you configured:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
https://your-deployment.convex.site/feedback/api/...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Ticket Number Format
|
|
150
|
+
|
|
151
|
+
All tickets have human-readable IDs:
|
|
152
|
+
|
|
153
|
+
| Type | Format | Example |
|
|
154
|
+
|------|--------|----------|
|
|
155
|
+
| Bug Report | `BUG-YYYY-NNNN` | `BUG-2025-0001` |
|
|
156
|
+
| Feedback | `FB-YYYY-NNNN` | `FB-2025-0042` |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### API Key Management
|
|
161
|
+
|
|
162
|
+
API keys are managed via Convex mutations. The full key is only shown once at creation time.
|
|
163
|
+
|
|
164
|
+
#### Create a Key
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const result = await ctx.runMutation(api.feedback.apiKeys.create, {
|
|
168
|
+
name: "My Integration",
|
|
169
|
+
expiresAt: Date.now() + 90 * 24 * 60 * 60 * 1000, // 90 days (optional)
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log(result.key); // fb_a1b2c3d4e5f6g7h8... (save this!)
|
|
173
|
+
console.log(result.keyPrefix); // fb_a1b2c3d4
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### List Keys
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const keys = await ctx.runQuery(api.feedback.apiKeys.list, {});
|
|
180
|
+
// Returns array of { _id, keyPrefix, name, expiresAt, isRevoked, isExpired, createdAt }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Revoke a Key
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
await ctx.runMutation(api.feedback.apiKeys.revoke, {
|
|
187
|
+
keyPrefix: "fb_a1b2c3d4",
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Rotate a Key
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const newKey = await ctx.runMutation(api.feedback.apiKeys.rotate, {
|
|
195
|
+
keyPrefix: "fb_a1b2c3d4",
|
|
196
|
+
name: "Rotated Key", // optional
|
|
197
|
+
expiresAt: Date.now() + 90 * 24 * 60 * 60 * 1000, // optional
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Endpoints
|
|
204
|
+
|
|
205
|
+
#### GET `/api/items`
|
|
206
|
+
|
|
207
|
+
List bug reports and/or feedback items with optional filters.
|
|
208
|
+
|
|
209
|
+
**Query Parameters:**
|
|
210
|
+
|
|
211
|
+
| Parameter | Type | Default | Description |
|
|
212
|
+
|-----------|------|---------|-------------|
|
|
213
|
+
| `itemType` | `bug` \| `feedback` \| `all` | `all` | Filter by item type |
|
|
214
|
+
| `status` | string | - | Filter by status |
|
|
215
|
+
| `severity` | `low` \| `medium` \| `high` \| `critical` | - | Filter bugs by severity |
|
|
216
|
+
| `priority` | `nice_to_have` \| `important` \| `critical` | - | Filter feedback by priority |
|
|
217
|
+
| `type` | `feature_request` \| `change_request` \| `general` | - | Filter feedback by type |
|
|
218
|
+
| `limit` | number | `50` | Max items to return |
|
|
219
|
+
| `includeArchived` | `true` \| `false` | `false` | Include archived items |
|
|
220
|
+
|
|
221
|
+
**Bug Status Values:** `open`, `in-progress`, `resolved`, `closed`
|
|
222
|
+
|
|
223
|
+
**Feedback Status Values:** `open`, `under_review`, `planned`, `in_progress`, `completed`, `declined`
|
|
224
|
+
|
|
225
|
+
**Example:**
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# List all open bugs
|
|
229
|
+
curl -H "Authorization: Bearer fb_your_key" \
|
|
230
|
+
"https://your-site.convex.site/feedback/api/items?itemType=bug&status=open"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Response:**
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"bugs": [
|
|
238
|
+
{
|
|
239
|
+
"_id": "abc123",
|
|
240
|
+
"ticketNumber": "BUG-2025-0001",
|
|
241
|
+
"title": "Login button not working",
|
|
242
|
+
"severity": "high",
|
|
243
|
+
"status": "open",
|
|
244
|
+
...
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"feedback": [...]
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
#### GET `/api/items/{ticketNumber}`
|
|
254
|
+
|
|
255
|
+
Fetch a single bug report or feedback item by ticket number.
|
|
256
|
+
|
|
257
|
+
**Example:**
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
curl -H "Authorization: Bearer fb_your_key" \
|
|
261
|
+
"https://your-site.convex.site/feedback/api/items/BUG-2025-0001"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Response:**
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"type": "bug",
|
|
269
|
+
"_id": "abc123",
|
|
270
|
+
"ticketNumber": "BUG-2025-0001",
|
|
271
|
+
"title": "Login button not working",
|
|
272
|
+
"description": "When I click the login button, nothing happens",
|
|
273
|
+
"severity": "high",
|
|
274
|
+
"status": "open",
|
|
275
|
+
"aiSummary": "The login form submit handler is not properly bound",
|
|
276
|
+
"aiRootCauseAnalysis": "React event handler not attached correctly",
|
|
277
|
+
"aiSuggestedFix": "Check that the onClick handler is properly bound",
|
|
278
|
+
...
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
#### GET `/api/prompt/{ticketNumber}`
|
|
285
|
+
|
|
286
|
+
Fetch an AI-generated prompt for fixing/implementing a ticket. Designed for AI coding assistants like Codebuff.
|
|
287
|
+
|
|
288
|
+
**Query Parameters:**
|
|
289
|
+
|
|
290
|
+
| Parameter | Type | Default | Description |
|
|
291
|
+
|-----------|------|---------|-------------|
|
|
292
|
+
| `template` | `fix` \| `implement` \| `analyze` \| `codebuff` | `fix` (bugs) / `implement` (feedback) | Prompt template |
|
|
293
|
+
|
|
294
|
+
**Templates:**
|
|
295
|
+
|
|
296
|
+
| Template | Use Case |
|
|
297
|
+
|----------|----------|
|
|
298
|
+
| `fix` | Bug fix instructions |
|
|
299
|
+
| `implement` | Feature implementation instructions |
|
|
300
|
+
| `analyze` | Deep analysis prompt |
|
|
301
|
+
| `codebuff` | Codebuff-optimized structured format |
|
|
302
|
+
|
|
303
|
+
**Example:**
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Get a Codebuff-optimized prompt
|
|
307
|
+
curl -H "Authorization: Bearer fb_your_key" \
|
|
308
|
+
"https://your-site.convex.site/feedback/api/prompt/BUG-2025-0001?template=codebuff"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Response:**
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"ticketNumber": "BUG-2025-0001",
|
|
316
|
+
"type": "bug",
|
|
317
|
+
"template": "codebuff",
|
|
318
|
+
"prompt": "# Bug Fix Request: BUG-2025-0001\n\n## Title\nLogin button not working\n\n..."
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
#### PATCH `/api/items/{ticketNumber}/status`
|
|
325
|
+
|
|
326
|
+
Update the status of a bug report or feedback item.
|
|
327
|
+
|
|
328
|
+
**Request Body:**
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{ "status": "resolved" }
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Example:**
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
curl -X PATCH \
|
|
338
|
+
-H "Authorization: Bearer fb_your_key" \
|
|
339
|
+
-H "Content-Type: application/json" \
|
|
340
|
+
-d '{"status": "resolved"}' \
|
|
341
|
+
"https://your-site.convex.site/feedback/api/items/BUG-2025-0001/status"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Response:**
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{ "success": true, "ticketNumber": "BUG-2025-0001" }
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
#### PATCH `/api/items/{ticketNumber}/archive`
|
|
353
|
+
|
|
354
|
+
Archive or unarchive a bug report or feedback item.
|
|
355
|
+
|
|
356
|
+
**Request Body:**
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{ "archived": true }
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Example:**
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
curl -X PATCH \
|
|
366
|
+
-H "Authorization: Bearer fb_your_key" \
|
|
367
|
+
-H "Content-Type: application/json" \
|
|
368
|
+
-d '{"archived": true}' \
|
|
369
|
+
"https://your-site.convex.site/feedback/api/items/BUG-2025-0001/archive"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Response:**
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
{ "success": true, "ticketNumber": "BUG-2025-0001", "isArchived": true }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### Error Responses
|
|
381
|
+
|
|
382
|
+
| Status | Response |
|
|
383
|
+
|--------|----------|
|
|
384
|
+
| 400 | `{ "error": "Bad request", "message": "..." }` |
|
|
385
|
+
| 401 | `{ "error": "Unauthorized", "message": "Invalid or missing API key" }` |
|
|
386
|
+
| 404 | `{ "error": "Not found", "message": "Bug report BUG-2025-0001 not found" }` |
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
### Integration Examples
|
|
391
|
+
|
|
392
|
+
#### Codebuff Integration
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
curl -s -H "Authorization: Bearer fb_your_key" \
|
|
396
|
+
"https://your-site.convex.site/feedback/api/prompt/BUG-2025-0001?template=codebuff" \
|
|
397
|
+
| jq -r '.prompt' \
|
|
398
|
+
| codebuff
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### GitHub Actions
|
|
402
|
+
|
|
403
|
+
```yaml
|
|
404
|
+
- name: Get open bugs count
|
|
405
|
+
run: |
|
|
406
|
+
curl -s -H "Authorization: Bearer ${{ secrets.FEEDBACK_API_KEY }}" \
|
|
407
|
+
"${{ secrets.CONVEX_SITE_URL }}/feedback/api/items?itemType=bug&status=open" \
|
|
408
|
+
| jq '.bugs | length'
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Convex API Reference
|
|
107
414
|
|
|
108
415
|
### Bug Reports
|
|
109
416
|
|
|
@@ -251,6 +558,8 @@ await ctx.runMutation(api.feedback.supportTeams.update, {
|
|
|
251
558
|
});
|
|
252
559
|
```
|
|
253
560
|
|
|
561
|
+
---
|
|
562
|
+
|
|
254
563
|
## React Components
|
|
255
564
|
|
|
256
565
|
### BugReportProvider
|
|
@@ -407,6 +716,8 @@ function MyComponent() {
|
|
|
407
716
|
}
|
|
408
717
|
```
|
|
409
718
|
|
|
719
|
+
---
|
|
720
|
+
|
|
410
721
|
## AI Interview Mode
|
|
411
722
|
|
|
412
723
|
The AI Interview Mode provides a conversational experience to help users articulate their bug reports and feedback more effectively. Instead of filling out a form, users chat with an AI that asks clarifying questions and generates a well-structured report.
|
|
@@ -548,6 +859,8 @@ The AI can ask for input in three formats:
|
|
|
548
859
|
- Users can always toggle between "AI Interview" and "Quick Form" modes
|
|
549
860
|
- Screenshots and browser diagnostics are still captured automatically in interview mode
|
|
550
861
|
|
|
862
|
+
---
|
|
863
|
+
|
|
551
864
|
## AI Analysis
|
|
552
865
|
|
|
553
866
|
When `OPENROUTER_API_KEY` is configured, the component automatically analyzes submissions:
|
|
@@ -569,6 +882,8 @@ When `OPENROUTER_API_KEY` is configured, the component automatically analyzes su
|
|
|
569
882
|
- **Estimated Effort** - Low/Medium/High effort estimate
|
|
570
883
|
- **Suggested Priority** - AI-recommended priority level
|
|
571
884
|
|
|
885
|
+
---
|
|
886
|
+
|
|
572
887
|
## Email Notifications
|
|
573
888
|
|
|
574
889
|
When `RESEND_API_KEY` is configured, the component sends email notifications:
|
|
@@ -584,6 +899,8 @@ When `RESEND_API_KEY` is configured, the component sends email notifications:
|
|
|
584
899
|
- Routed based on feedback type or bug severity
|
|
585
900
|
- Rich HTML template with all diagnostics
|
|
586
901
|
|
|
902
|
+
---
|
|
903
|
+
|
|
587
904
|
## Schema
|
|
588
905
|
|
|
589
906
|
The component creates these tables:
|
|
@@ -593,6 +910,31 @@ The component creates these tables:
|
|
|
593
910
|
- `supportTeams` - Team configuration for notification routing
|
|
594
911
|
- `feedbackInputRequests` - Pending user input requests during AI interviews
|
|
595
912
|
- `interviewSessions` - Interview state and generated reports
|
|
913
|
+
- `ticketCounters` - Sequential counters for ticket numbers
|
|
914
|
+
- `apiKeys` - API keys for REST API authentication
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
## TypeScript Types
|
|
919
|
+
|
|
920
|
+
```typescript
|
|
921
|
+
import type {
|
|
922
|
+
BugReport,
|
|
923
|
+
Feedback,
|
|
924
|
+
BugSeverity,
|
|
925
|
+
BugStatus,
|
|
926
|
+
FeedbackType,
|
|
927
|
+
FeedbackPriority,
|
|
928
|
+
FeedbackStatus,
|
|
929
|
+
ApiKey,
|
|
930
|
+
ApiKeyCreated,
|
|
931
|
+
PromptTemplate,
|
|
932
|
+
RegisterFeedbackRoutesOptions,
|
|
933
|
+
InterviewContext,
|
|
934
|
+
} from '@fatagnus/convex-feedback';
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
---
|
|
596
938
|
|
|
597
939
|
## License
|
|
598
940
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fatagnus/convex-feedback",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Bug reports and feedback collection component for Convex applications with AI analysis and email notifications",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"./convex.config": "./src/convex/convex.config.ts",
|
|
40
40
|
"./convex/*": "./src/convex/*",
|
|
41
|
-
"./convex/agents/*": "./src/convex/agents/*"
|
|
41
|
+
"./convex/agents/*": "./src/convex/agents/*",
|
|
42
|
+
"./convex/http": "./src/convex/http.ts"
|
|
42
43
|
},
|
|
43
44
|
"files": [
|
|
44
45
|
"dist",
|
|
@@ -49,8 +50,12 @@
|
|
|
49
50
|
"scripts": {
|
|
50
51
|
"build": "tsc",
|
|
51
52
|
"dev": "tsc --watch",
|
|
52
|
-
"typecheck": "tsc --noEmit",
|
|
53
|
-
"
|
|
53
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
54
|
+
"typecheck:convex": "tsc --noEmit --project src/convex/tsconfig.json",
|
|
55
|
+
"postgenerate": "node -e \"const fs=require('fs');const f='src/convex/_generated/api.ts';if(fs.existsSync(f)){let c=fs.readFileSync(f,'utf8');if(!c.includes('@ts-nocheck')){c=c.replace('/* eslint-disable */','/* eslint-disable */\\n// @ts-nocheck - Circular reference errors in generated Convex component types are expected');fs.writeFileSync(f,c)}}\"",
|
|
56
|
+
"clean": "rm -rf dist",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest"
|
|
54
59
|
},
|
|
55
60
|
"peerDependencies": {
|
|
56
61
|
"@ai-sdk/openai-compatible": ">=0.1.0",
|
|
@@ -101,6 +106,8 @@
|
|
|
101
106
|
"devDependencies": {
|
|
102
107
|
"@types/node": "^20.0.0",
|
|
103
108
|
"@types/react": "^18.0.0",
|
|
104
|
-
"typescript": "^5.0.0"
|
|
109
|
+
"typescript": "^5.0.0",
|
|
110
|
+
"vitest": "^2.0.0",
|
|
111
|
+
"convex-test": "^0.0.36"
|
|
105
112
|
}
|
|
106
113
|
}
|
|
@@ -591,14 +591,12 @@ export const startBugInterview = action({
|
|
|
591
591
|
});
|
|
592
592
|
|
|
593
593
|
// Start the interview
|
|
594
|
+
// Note: Tools look up session by threadId, so we don't need to pass sessionId via customCtx
|
|
594
595
|
let result;
|
|
595
596
|
try {
|
|
596
597
|
result = await dynamicAgent.generateText(
|
|
597
598
|
ctx,
|
|
598
|
-
{
|
|
599
|
-
threadId,
|
|
600
|
-
customCtx: { sessionId },
|
|
601
|
-
},
|
|
599
|
+
{ threadId },
|
|
602
600
|
{ prompt: "Start the bug report interview. Greet the user briefly and ask what bug or issue they encountered." }
|
|
603
601
|
);
|
|
604
602
|
} catch (error) {
|
|
@@ -722,14 +720,12 @@ export const startFeedbackInterview = action({
|
|
|
722
720
|
});
|
|
723
721
|
|
|
724
722
|
// Start the interview
|
|
723
|
+
// Note: Tools look up session by threadId, so we don't need to pass sessionId via customCtx
|
|
725
724
|
let result;
|
|
726
725
|
try {
|
|
727
726
|
result = await dynamicAgent.generateText(
|
|
728
727
|
ctx,
|
|
729
|
-
{
|
|
730
|
-
threadId,
|
|
731
|
-
customCtx: { sessionId },
|
|
732
|
-
},
|
|
728
|
+
{ threadId },
|
|
733
729
|
{ prompt: "Start the feedback interview. Greet the user briefly and ask about their idea or suggestion." }
|
|
734
730
|
);
|
|
735
731
|
} catch (error) {
|
|
@@ -871,14 +867,12 @@ export const continueInterview = action({
|
|
|
871
867
|
});
|
|
872
868
|
|
|
873
869
|
// Continue the agent
|
|
870
|
+
// Note: Tools look up session by threadId, so we don't need to pass sessionId via customCtx
|
|
874
871
|
let result;
|
|
875
872
|
try {
|
|
876
873
|
result = await dynamicAgent.generateText(
|
|
877
874
|
ctx,
|
|
878
|
-
{
|
|
879
|
-
threadId: args.threadId,
|
|
880
|
-
customCtx: { sessionId: args.sessionId },
|
|
881
|
-
},
|
|
875
|
+
{ threadId: args.threadId },
|
|
882
876
|
{
|
|
883
877
|
prompt: `The user responded: ${args.response}
|
|
884
878
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for API Key utilities
|
|
3
|
+
*
|
|
4
|
+
* Note: Testing Convex component packages with convex-test requires special setup.
|
|
5
|
+
* These tests verify the key format and structure expectations.
|
|
6
|
+
* Full integration tests should be run in a real Convex deployment.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from "vitest";
|
|
9
|
+
|
|
10
|
+
describe("API Key Format", () => {
|
|
11
|
+
describe("Key Structure", () => {
|
|
12
|
+
it("key format should be fb_ followed by 32 hex chars", () => {
|
|
13
|
+
// Example of expected format
|
|
14
|
+
const validKeyPattern = /^fb_[a-f0-9]{32}$/;
|
|
15
|
+
const exampleKey = "fb_0123456789abcdef0123456789abcdef";
|
|
16
|
+
|
|
17
|
+
expect(exampleKey).toMatch(validKeyPattern);
|
|
18
|
+
expect(exampleKey.length).toBe(35); // "fb_" (3) + 32 hex chars
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("key prefix should be fb_ followed by 8 hex chars", () => {
|
|
22
|
+
const validPrefixPattern = /^fb_[a-f0-9]{8}$/;
|
|
23
|
+
const examplePrefix = "fb_01234567";
|
|
24
|
+
|
|
25
|
+
expect(examplePrefix).toMatch(validPrefixPattern);
|
|
26
|
+
expect(examplePrefix.length).toBe(11); // "fb_" (3) + 8 hex chars
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("key prefix should be the first 11 chars of the full key", () => {
|
|
30
|
+
const fullKey = "fb_0123456789abcdef0123456789abcdef";
|
|
31
|
+
const expectedPrefix = fullKey.slice(0, 11);
|
|
32
|
+
|
|
33
|
+
expect(expectedPrefix).toBe("fb_01234567");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("Key Hashing", () => {
|
|
38
|
+
it("SHA-256 hash should produce 64 hex chars", async () => {
|
|
39
|
+
const key = "fb_0123456789abcdef0123456789abcdef";
|
|
40
|
+
|
|
41
|
+
const encoder = new TextEncoder();
|
|
42
|
+
const data = encoder.encode(key);
|
|
43
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
44
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
45
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
46
|
+
|
|
47
|
+
expect(hashHex.length).toBe(64);
|
|
48
|
+
expect(hashHex).toMatch(/^[a-f0-9]{64}$/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("same key should always produce same hash", async () => {
|
|
52
|
+
const key = "fb_testkey123456789abcdef12345678";
|
|
53
|
+
|
|
54
|
+
const hash1 = await hashKey(key);
|
|
55
|
+
const hash2 = await hashKey(key);
|
|
56
|
+
|
|
57
|
+
expect(hash1).toBe(hash2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("different keys should produce different hashes", async () => {
|
|
61
|
+
const key1 = "fb_testkey123456789abcdef12345678";
|
|
62
|
+
const key2 = "fb_testkey123456789abcdef12345679";
|
|
63
|
+
|
|
64
|
+
const hash1 = await hashKey(key1);
|
|
65
|
+
const hash2 = await hashKey(key2);
|
|
66
|
+
|
|
67
|
+
expect(hash1).not.toBe(hash2);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Helper function matching the implementation in apiKeys.ts
|
|
73
|
+
async function hashKey(key: string): Promise<string> {
|
|
74
|
+
const encoder = new TextEncoder();
|
|
75
|
+
const data = encoder.encode(key);
|
|
76
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
77
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
78
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
79
|
+
}
|