@supaku/agentfactory-nextjs 0.4.1 → 0.4.3
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
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# @supaku/agentfactory-nextjs
|
|
2
|
+
|
|
3
|
+
Next.js route handlers, webhook processor, middleware, and OAuth for [AgentFactory](https://github.com/supaku/agentfactory). Drop-in API routes that turn a Next.js app into a full agent fleet server.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @supaku/agentfactory-nextjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or scaffold a complete project:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @supaku/create-agentfactory-app my-agent
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Configure routes
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// src/lib/config.ts
|
|
23
|
+
import { createAllRoutes, createDefaultLinearClientResolver } from '@supaku/agentfactory-nextjs'
|
|
24
|
+
|
|
25
|
+
export const routes = createAllRoutes({
|
|
26
|
+
linearClient: createDefaultLinearClientResolver(),
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Add webhook route
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// src/app/webhook/route.ts
|
|
34
|
+
import { routes } from '@/lib/config'
|
|
35
|
+
export const POST = routes.webhook.POST
|
|
36
|
+
export const GET = routes.webhook.GET
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. Add middleware
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// src/middleware.ts
|
|
43
|
+
import { createAgentFactoryMiddleware } from '@supaku/agentfactory-nextjs'
|
|
44
|
+
|
|
45
|
+
const { middleware } = createAgentFactoryMiddleware()
|
|
46
|
+
export { middleware }
|
|
47
|
+
|
|
48
|
+
export const config = {
|
|
49
|
+
matcher: ['/api/:path*', '/webhook', '/dashboard', '/sessions/:path*', '/'],
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## What's Included
|
|
54
|
+
|
|
55
|
+
`createAllRoutes()` generates 21+ route handlers from a single config:
|
|
56
|
+
|
|
57
|
+
| Route Group | Endpoints | Purpose |
|
|
58
|
+
|-------------|-----------|---------|
|
|
59
|
+
| **Webhook** | `POST /webhook` | Receive Linear events, dispatch agents |
|
|
60
|
+
| **Workers** | `/api/workers/*` | Worker registration, heartbeat, polling |
|
|
61
|
+
| **Sessions** | `/api/sessions/*` | Session management, status, activity |
|
|
62
|
+
| **Public** | `/api/public/*` | Public stats, session list |
|
|
63
|
+
| **Cleanup** | `/api/cleanup` | Orphaned resource cleanup |
|
|
64
|
+
| **OAuth** | `/callback` | Linear OAuth callback |
|
|
65
|
+
|
|
66
|
+
Each route file is a 2-line re-export:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { routes } from '@/lib/config'
|
|
70
|
+
export const GET = routes.sessions.list.GET
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const routes = createAllRoutes({
|
|
77
|
+
// Required: how to resolve a Linear API client
|
|
78
|
+
linearClient: createDefaultLinearClientResolver(),
|
|
79
|
+
|
|
80
|
+
// Optional: customize prompts, detection, priority
|
|
81
|
+
generatePrompt: (identifier, workType, mentionContext) => string,
|
|
82
|
+
detectWorkTypeFromPrompt: (prompt, validWorkTypes) => AgentWorkType | undefined,
|
|
83
|
+
getPriority: (workType) => number,
|
|
84
|
+
|
|
85
|
+
// Optional: auto-trigger QA/acceptance
|
|
86
|
+
autoTrigger: {
|
|
87
|
+
enableAutoQA: true,
|
|
88
|
+
enableAutoAcceptance: false,
|
|
89
|
+
autoQARequireAgentWorked: true,
|
|
90
|
+
autoAcceptanceRequireAgentWorked: true,
|
|
91
|
+
autoQAProjects: [],
|
|
92
|
+
autoAcceptanceProjects: [],
|
|
93
|
+
autoQAExcludeLabels: [],
|
|
94
|
+
autoAcceptanceExcludeLabels: [],
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// Optional: OAuth
|
|
98
|
+
oauth: { clientId: '...', clientSecret: '...' },
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Middleware
|
|
103
|
+
|
|
104
|
+
Handles API key auth, rate limiting, and webhook signature verification:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const { middleware } = createAgentFactoryMiddleware({
|
|
108
|
+
routes: {
|
|
109
|
+
public: ['/api/public/', '/dashboard', '/'],
|
|
110
|
+
protected: ['/api/sessions', '/api/workers'],
|
|
111
|
+
webhook: '/webhook',
|
|
112
|
+
},
|
|
113
|
+
rateLimits: {
|
|
114
|
+
public: { max: 60, windowMs: 60_000 },
|
|
115
|
+
webhook: { max: 10, windowMs: 1_000 },
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
| Variable | Required | Description |
|
|
123
|
+
|----------|----------|-------------|
|
|
124
|
+
| `LINEAR_ACCESS_TOKEN` | Yes | Linear API key |
|
|
125
|
+
| `LINEAR_WEBHOOK_SECRET` | For webhooks | Webhook signature verification |
|
|
126
|
+
| `REDIS_URL` | For distributed | Redis connection URL |
|
|
127
|
+
|
|
128
|
+
## Related Packages
|
|
129
|
+
|
|
130
|
+
| Package | Description |
|
|
131
|
+
|---------|-------------|
|
|
132
|
+
| [@supaku/agentfactory](https://www.npmjs.com/package/@supaku/agentfactory) | Core orchestrator |
|
|
133
|
+
| [@supaku/agentfactory-linear](https://www.npmjs.com/package/@supaku/agentfactory-linear) | Linear integration |
|
|
134
|
+
| [@supaku/agentfactory-server](https://www.npmjs.com/package/@supaku/agentfactory-server) | Redis infrastructure |
|
|
135
|
+
| [@supaku/agentfactory-cli](https://www.npmjs.com/package/@supaku/agentfactory-cli) | CLI tools |
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Middleware Factory
|
|
2
|
+
* Middleware Factory — Edge Runtime Compatible
|
|
3
3
|
*
|
|
4
4
|
* Creates a Next.js middleware function that handles authentication,
|
|
5
5
|
* rate limiting, and security for AgentFactory API routes.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* IMPORTANT: This module runs in the Edge Runtime. It MUST NOT import
|
|
8
|
+
* from @supaku/agentfactory-server (which uses Node.js crypto/ioredis).
|
|
9
|
+
* All utilities are inlined for Edge compatibility.
|
|
9
10
|
*/
|
|
10
11
|
import { NextRequest, NextResponse } from 'next/server';
|
|
11
12
|
import type { MiddlewareConfig } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/middleware/factory.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/middleware/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AA0GlD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,CAAC,EAAE,gBAAgB;0BAO3C,WAAW,KAAG,YAAY,GAAG,SAAS;;;;EA8GpE"}
|
|
@@ -1,14 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Middleware Factory
|
|
2
|
+
* Middleware Factory — Edge Runtime Compatible
|
|
3
3
|
*
|
|
4
4
|
* Creates a Next.js middleware function that handles authentication,
|
|
5
5
|
* rate limiting, and security for AgentFactory API routes.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* IMPORTANT: This module runs in the Edge Runtime. It MUST NOT import
|
|
8
|
+
* from @supaku/agentfactory-server (which uses Node.js crypto/ioredis).
|
|
9
|
+
* All utilities are inlined for Edge compatibility.
|
|
9
10
|
*/
|
|
10
11
|
import { NextResponse } from 'next/server';
|
|
11
|
-
|
|
12
|
+
const RATE_LIMITS = {
|
|
13
|
+
public: { limit: 60, windowMs: 60_000 },
|
|
14
|
+
webhook: { limit: 10, windowMs: 1_000 },
|
|
15
|
+
dashboard: { limit: 30, windowMs: 60_000 },
|
|
16
|
+
};
|
|
17
|
+
const caches = new Map();
|
|
18
|
+
function checkRateLimit(type, key) {
|
|
19
|
+
const config = RATE_LIMITS[type];
|
|
20
|
+
let cache = caches.get(type);
|
|
21
|
+
if (!cache) {
|
|
22
|
+
cache = new Map();
|
|
23
|
+
caches.set(type, cache);
|
|
24
|
+
}
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const windowStart = now - config.windowMs;
|
|
27
|
+
let entry = cache.get(key);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
entry = { timestamps: [], lastAccess: now };
|
|
30
|
+
}
|
|
31
|
+
entry.timestamps = entry.timestamps.filter((ts) => ts > windowStart);
|
|
32
|
+
entry.lastAccess = now;
|
|
33
|
+
const allowed = entry.timestamps.length < config.limit;
|
|
34
|
+
if (allowed)
|
|
35
|
+
entry.timestamps.push(now);
|
|
36
|
+
cache.set(key, entry);
|
|
37
|
+
// LRU eviction at 10k entries
|
|
38
|
+
if (cache.size > 10_000) {
|
|
39
|
+
const entries = Array.from(cache.entries()).sort((a, b) => a[1].lastAccess - b[1].lastAccess);
|
|
40
|
+
const toRemove = Math.ceil(10_000 * 0.1);
|
|
41
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
42
|
+
cache.delete(entries[i][0]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const remaining = Math.max(0, config.limit - entry.timestamps.length);
|
|
46
|
+
const oldestTs = entry.timestamps[0];
|
|
47
|
+
const resetIn = oldestTs ? Math.max(0, oldestTs + config.windowMs - now) : 0;
|
|
48
|
+
return { allowed, remaining, resetIn, limit: config.limit };
|
|
49
|
+
}
|
|
50
|
+
// === Edge-compatible utilities ===
|
|
51
|
+
function getClientIP(headers) {
|
|
52
|
+
return (headers.get('x-forwarded-for')?.split(',')[0].trim() ||
|
|
53
|
+
headers.get('cf-connecting-ip') ||
|
|
54
|
+
headers.get('x-real-ip') ||
|
|
55
|
+
'unknown');
|
|
56
|
+
}
|
|
57
|
+
function buildRateLimitHeaders(result) {
|
|
58
|
+
return {
|
|
59
|
+
'X-RateLimit-Limit': result.limit.toString(),
|
|
60
|
+
'X-RateLimit-Remaining': result.remaining.toString(),
|
|
61
|
+
'X-RateLimit-Reset': Math.ceil(result.resetIn / 1000).toString(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Timing-safe string comparison using XOR (Edge-compatible).
|
|
66
|
+
* Does NOT use Node.js crypto.timingSafeEqual.
|
|
67
|
+
*/
|
|
68
|
+
function timingSafeEqual(a, b) {
|
|
69
|
+
if (a.length !== b.length)
|
|
70
|
+
return false;
|
|
71
|
+
let result = 0;
|
|
72
|
+
for (let i = 0; i < a.length; i++) {
|
|
73
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
74
|
+
}
|
|
75
|
+
return result === 0;
|
|
76
|
+
}
|
|
77
|
+
// === Route defaults ===
|
|
12
78
|
const DEFAULT_PUBLIC_ROUTES = ['/api/public/', '/dashboard', '/'];
|
|
13
79
|
const DEFAULT_PROTECTED_ROUTES = ['/api/sessions', '/api/workers'];
|
|
14
80
|
const DEFAULT_SESSION_PAGES = ['/sessions/'];
|
|
@@ -90,7 +156,7 @@ export function createAgentFactoryMiddleware(userConfig) {
|
|
|
90
156
|
return new NextResponse(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
91
157
|
}
|
|
92
158
|
const token = authHeader.slice(7);
|
|
93
|
-
if (!
|
|
159
|
+
if (!timingSafeEqual(token, workerApiKey)) {
|
|
94
160
|
return new NextResponse(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
95
161
|
}
|
|
96
162
|
return NextResponse.next();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supaku/agentfactory-nextjs",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Next.js API route handlers for AgentFactory — webhook processor, worker/session management, public stats",
|
|
6
6
|
"author": "Supaku (https://supaku.com)",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"LICENSE"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@supaku/agentfactory": "0.4.
|
|
47
|
-
"@supaku/agentfactory-server": "0.4.
|
|
48
|
-
"@supaku/agentfactory
|
|
46
|
+
"@supaku/agentfactory-linear": "0.4.3",
|
|
47
|
+
"@supaku/agentfactory-server": "0.4.3",
|
|
48
|
+
"@supaku/agentfactory": "0.4.3"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"next": ">=14.0.0"
|