@chatcops/server 0.4.0 → 0.6.0
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 +35 -7
- package/dist/adapters/ai-sdk.d.ts +12 -0
- package/dist/adapters/ai-sdk.d.ts.map +1 -0
- package/dist/adapters/ai-sdk.js +27 -0
- package/dist/adapters/ai-sdk.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +3 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +11 -58
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/express.d.ts +6 -2
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +109 -26
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/vercel.d.ts.map +1 -1
- package/dist/adapters/vercel.js +7 -60
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/client-ip.d.ts +36 -0
- package/dist/client-ip.d.ts.map +1 -0
- package/dist/client-ip.js +62 -0
- package/dist/client-ip.js.map +1 -0
- package/dist/config.d.ts +110 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/handler.d.ts +53 -2
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +410 -127
- package/dist/handler.js.map +1 -1
- package/dist/http.d.ts +66 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +262 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/rate-limit.d.ts +5 -0
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +5 -0
- package/dist/rate-limit.js.map +1 -1
- package/dist/security/origin-policy.d.ts +19 -0
- package/dist/security/origin-policy.d.ts.map +1 -0
- package/dist/security/origin-policy.js +58 -0
- package/dist/security/origin-policy.js.map +1 -0
- package/dist/security/origin.d.ts +5 -0
- package/dist/security/origin.d.ts.map +1 -0
- package/dist/security/origin.js +47 -0
- package/dist/security/origin.js.map +1 -0
- package/dist/security/session-token.d.ts +65 -0
- package/dist/security/session-token.d.ts.map +1 -0
- package/dist/security/session-token.js +338 -0
- package/dist/security/session-token.js.map +1 -0
- package/dist/security/sessions.d.ts +49 -0
- package/dist/security/sessions.d.ts.map +1 -0
- package/dist/security/sessions.js +159 -0
- package/dist/security/sessions.js.map +1 -0
- package/dist/security/startup-checks.d.ts +44 -0
- package/dist/security/startup-checks.d.ts.map +1 -0
- package/dist/security/startup-checks.js +134 -0
- package/dist/security/startup-checks.js.map +1 -0
- package/dist/security/turnstile.d.ts +24 -0
- package/dist/security/turnstile.d.ts.map +1 -0
- package/dist/security/turnstile.js +93 -0
- package/dist/security/turnstile.js.map +1 -0
- package/dist/visitor-context.d.ts +20 -0
- package/dist/visitor-context.d.ts.map +1 -0
- package/dist/visitor-context.js +60 -0
- package/dist/visitor-context.js.map +1 -0
- package/dist/webhook.d.ts +48 -2
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +159 -31
- package/dist/webhook.js.map +1 -1
- package/package.json +22 -5
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ app.all('/chat', chatcopsMiddleware({
|
|
|
25
25
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
26
26
|
},
|
|
27
27
|
systemPrompt: 'You are a helpful assistant for our website.',
|
|
28
|
-
|
|
28
|
+
allowedOrigins: ['*'],
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
app.listen(3001);
|
|
@@ -39,7 +39,7 @@ import { chatcopsVercelHandler } from '@chatcops/server';
|
|
|
39
39
|
const handler = chatcopsVercelHandler({
|
|
40
40
|
provider: { type: 'openai', apiKey: process.env.OPENAI_API_KEY },
|
|
41
41
|
systemPrompt: 'You are a helpful assistant.',
|
|
42
|
-
|
|
42
|
+
allowedOrigins: ['*'],
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
export const POST = (req: Request) => handler(req);
|
|
@@ -57,7 +57,7 @@ function getHandler(env: Env) {
|
|
|
57
57
|
handler ??= chatcopsCloudflareHandler({
|
|
58
58
|
provider: { type: 'claude', apiKey: env.ANTHROPIC_API_KEY },
|
|
59
59
|
systemPrompt: 'You are a helpful assistant.',
|
|
60
|
-
|
|
60
|
+
allowedOrigins: ['*'],
|
|
61
61
|
});
|
|
62
62
|
return handler;
|
|
63
63
|
}
|
|
@@ -80,7 +80,7 @@ const handler = createChatHandler({
|
|
|
80
80
|
provider: {
|
|
81
81
|
type: 'claude', // 'claude' | 'openai' | 'gemini'
|
|
82
82
|
apiKey: 'sk-...',
|
|
83
|
-
model: 'claude-haiku-
|
|
83
|
+
model: 'claude-haiku-5-5',
|
|
84
84
|
},
|
|
85
85
|
|
|
86
86
|
// System prompt (required)
|
|
@@ -115,7 +115,7 @@ const handler = createChatHandler({
|
|
|
115
115
|
conversationStore: new RedisConversationStore(redisClient),
|
|
116
116
|
|
|
117
117
|
// CORS origin (optional, default: '*')
|
|
118
|
-
|
|
118
|
+
allowedOrigins: ['*'],
|
|
119
119
|
});
|
|
120
120
|
```
|
|
121
121
|
|
|
@@ -153,7 +153,7 @@ const result = limiter.check('127.0.0.1');
|
|
|
153
153
|
|
|
154
154
|
## Webhooks
|
|
155
155
|
|
|
156
|
-
HMAC-SHA256 signed, with
|
|
156
|
+
HMAC-SHA256 signed, with retries (up to 3 attempts).
|
|
157
157
|
|
|
158
158
|
```typescript
|
|
159
159
|
import { WebhookDispatcher } from '@chatcops/server';
|
|
@@ -165,7 +165,15 @@ const dispatcher = new WebhookDispatcher([
|
|
|
165
165
|
await dispatcher.dispatch('lead:captured', { email: 'user@example.com' });
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
Each delivery carries `X-ChatCops-Signature: t=<seconds>,v1=<hmac>`, signed over the raw body. Check it on the receiving side with `verifyWebhook`:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { verifyWebhook } from '@chatcops/server';
|
|
172
|
+
|
|
173
|
+
const valid = await verifyWebhook({ body: rawBody, signature: request.headers.get('X-ChatCops-Signature'), secret: 'my-secret' });
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The body is `{ id, type, created, data }`; `id` stays the same across retries. 429 and 5xx responses are retried, redirects aren't followed, and URLs must be https on a public host unless the webhook sets `allowPrivateNetwork: true`. See the server setup docs for the full format.
|
|
169
177
|
|
|
170
178
|
## Custom Conversation Store
|
|
171
179
|
|
|
@@ -202,6 +210,26 @@ const handler = createChatHandler({
|
|
|
202
210
|
});
|
|
203
211
|
```
|
|
204
212
|
|
|
213
|
+
## Custom integrations
|
|
214
|
+
|
|
215
|
+
`createChatHandler(config)` returns `handleChat(body, clientIp, options)`, an async generator of SSE `data` payloads. The built-in adapters use it; call it directly for other runtimes. Pass `options.signal` and abort it when the visitor disconnects: the provider request is cancelled, nothing more is yielded, and the partial reply isn't saved.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const abort = new AbortController();
|
|
219
|
+
const stream = new ReadableStream({
|
|
220
|
+
async start(controller) {
|
|
221
|
+
for await (const chunk of handler.handleChat(body, clientIp, { signal: abort.signal })) {
|
|
222
|
+
if (abort.signal.aborted) break;
|
|
223
|
+
controller.enqueue(new TextEncoder().encode(`data: ${chunk}\n\n`));
|
|
224
|
+
}
|
|
225
|
+
if (!abort.signal.aborted) controller.close();
|
|
226
|
+
},
|
|
227
|
+
cancel() {
|
|
228
|
+
abort.abort(); // the runtime cancels the response when the client goes away
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
205
233
|
## License
|
|
206
234
|
|
|
207
235
|
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatCopsServerConfig } from '../config.js';
|
|
2
|
+
/**
|
|
3
|
+
* A Fetch API handler that `useChat` from the AI SDK can talk to: it reads
|
|
4
|
+
* the AI SDK's request and answers in its UI message stream. Use it as a
|
|
5
|
+
* Next.js route handler or anywhere that takes `(request) => Response`.
|
|
6
|
+
*
|
|
7
|
+
* The server keeps the conversation (keyed by the chat `id`) and reads only
|
|
8
|
+
* the last user message, unlike AI SDK routes that take the whole history
|
|
9
|
+
* from the client.
|
|
10
|
+
*/
|
|
11
|
+
export declare function chatcopsAiSdkHandler(config: ChatCopsServerConfig): (req: Request) => Promise<Response>;
|
|
12
|
+
//# sourceMappingURL=ai-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../../src/adapters/ai-sdk.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,IAIvC,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAMzD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cloudflareClientIp, fetchClientIp } from '../client-ip.js';
|
|
2
|
+
import { createChatHandler } from '../handler.js';
|
|
3
|
+
import { fetchChatResponse } from '../http.js';
|
|
4
|
+
import { originPolicy } from '../security/origin-policy.js';
|
|
5
|
+
/**
|
|
6
|
+
* A Fetch API handler that `useChat` from the AI SDK can talk to: it reads
|
|
7
|
+
* the AI SDK's request and answers in its UI message stream. Use it as a
|
|
8
|
+
* Next.js route handler or anywhere that takes `(request) => Response`.
|
|
9
|
+
*
|
|
10
|
+
* The server keeps the conversation (keyed by the chat `id`) and reads only
|
|
11
|
+
* the last user message, unlike AI SDK routes that take the whole history
|
|
12
|
+
* from the client.
|
|
13
|
+
*/
|
|
14
|
+
export function chatcopsAiSdkHandler(config) {
|
|
15
|
+
const chat = createChatHandler(config);
|
|
16
|
+
const origins = originPolicy(config);
|
|
17
|
+
return function handler(req) {
|
|
18
|
+
return fetchChatResponse(chat, origins, req, {
|
|
19
|
+
clientIp: (request) => (onWorkers() ? cloudflareClientIp(request, config.clientIp) : fetchClientIp(request, config.clientIp)),
|
|
20
|
+
aiSdk: true,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function onWorkers() {
|
|
25
|
+
return globalThis.navigator?.userAgent === 'Cloudflare-Workers';
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=ai-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../../src/adapters/ai-sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG5D;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA4B;IAC/D,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAErC,OAAO,SAAS,OAAO,CAAC,GAAY;QAClC,OAAO,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YAC3C,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC7H,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS;IAChB,OAAQ,UAAqD,CAAC,SAAS,EAAE,SAAS,KAAK,oBAAoB,CAAC;AAC9G,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { ChatCopsServerConfig } from '../config.js';
|
|
2
|
-
export declare function chatcopsCloudflareHandler(config: ChatCopsServerConfig): (request: Request
|
|
2
|
+
export declare function chatcopsCloudflareHandler(config: ChatCopsServerConfig): (request: Request, _env?: unknown, ctx?: {
|
|
3
|
+
waitUntil?(promise: Promise<unknown>): void;
|
|
4
|
+
}) => Promise<Response>;
|
|
3
5
|
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,oBAAoB,IAOlE,SAAS,OAAO,EAChB,OAAO,OAAO,EACd,MAAM;IAAE,SAAS,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,KACpD,OAAO,CAAC,QAAQ,CAAC,CAMrB"}
|
|
@@ -1,63 +1,16 @@
|
|
|
1
|
+
import { cloudflareClientIp } from '../client-ip.js';
|
|
1
2
|
import { createChatHandler } from '../handler.js';
|
|
3
|
+
import { fetchChatResponse } from '../http.js';
|
|
4
|
+
import { originPolicy } from '../security/origin-policy.js';
|
|
2
5
|
export function chatcopsCloudflareHandler(config) {
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
return async function handler(request) {
|
|
15
|
-
// Handle CORS preflight
|
|
16
|
-
if (request.method === 'OPTIONS') {
|
|
17
|
-
return new Response(null, {
|
|
18
|
-
status: 204,
|
|
19
|
-
headers: {
|
|
20
|
-
'Access-Control-Allow-Origin': corsOrigin,
|
|
21
|
-
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
22
|
-
'Access-Control-Allow-Headers': 'Content-Type',
|
|
23
|
-
'Access-Control-Max-Age': '86400',
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (request.method !== 'POST') {
|
|
28
|
-
return errorResponse(405, 'Method not allowed');
|
|
29
|
-
}
|
|
30
|
-
const clientIp = request.headers.get('cf-connecting-ip') ?? 'unknown';
|
|
31
|
-
let body;
|
|
32
|
-
try {
|
|
33
|
-
body = await request.json();
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return errorResponse(400, 'Invalid JSON body');
|
|
37
|
-
}
|
|
38
|
-
const { readable, writable } = new TransformStream();
|
|
39
|
-
const writer = writable.getWriter();
|
|
40
|
-
const encoder = new TextEncoder();
|
|
41
|
-
(async () => {
|
|
42
|
-
try {
|
|
43
|
-
for await (const chunk of handleChat(body, clientIp)) {
|
|
44
|
-
await writer.write(encoder.encode(`data: ${chunk}\n\n`));
|
|
45
|
-
}
|
|
46
|
-
await writer.write(encoder.encode('data: [DONE]\n\n'));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
await writer.write(encoder.encode(`data: ${JSON.stringify({ error: 'internal_error' })}\n\n`));
|
|
50
|
-
}
|
|
51
|
-
finally {
|
|
52
|
-
await writer.close();
|
|
53
|
-
}
|
|
54
|
-
})();
|
|
55
|
-
return new Response(readable, {
|
|
56
|
-
headers: {
|
|
57
|
-
'Content-Type': 'text/event-stream',
|
|
58
|
-
'Cache-Control': 'no-cache',
|
|
59
|
-
'Access-Control-Allow-Origin': corsOrigin,
|
|
60
|
-
},
|
|
6
|
+
const chat = createChatHandler(config);
|
|
7
|
+
const origins = originPolicy(config);
|
|
8
|
+
// Workers call fetch(request, env, ctx); ctx.waitUntil keeps lead webhooks
|
|
9
|
+
// running after a visitor disconnects.
|
|
10
|
+
return function handler(request, _env, ctx) {
|
|
11
|
+
return fetchChatResponse(chat, origins, request, {
|
|
12
|
+
clientIp: (req) => cloudflareClientIp(req, config.clientIp),
|
|
13
|
+
...(ctx?.waitUntil ? { waitUntil: (promise) => ctx.waitUntil?.(promise) } : {}),
|
|
61
14
|
});
|
|
62
15
|
};
|
|
63
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG5D,MAAM,UAAU,yBAAyB,CAAC,MAA4B;IACpE,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAErC,2EAA2E;IAC3E,uCAAuC;IACvC,OAAO,SAAS,OAAO,CACrB,OAAgB,EAChB,IAAc,EACd,GAAqD;QAErD,OAAO,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE;YAC/C,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC;YAC3D,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,OAAyB,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClG,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Request, Response, NextFunction } from 'express';
|
|
2
2
|
import type { ChatCopsServerConfig } from '../config.js';
|
|
3
|
-
export declare function chatcopsMiddleware(config: ChatCopsServerConfig): (req: Request, res: Response,
|
|
4
|
-
|
|
3
|
+
export declare function chatcopsMiddleware(config: ChatCopsServerConfig): (req: Request, res: Response, _next: NextFunction) => Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Analytics stats as JSON. With a storage that has an event sink, they cover
|
|
6
|
+
* every instance, for `?from=` and `?to=` (milliseconds) or the last 30 days.
|
|
7
|
+
*/
|
|
8
|
+
export declare function chatcopsAnalyticsHandler(config: ChatCopsServerConfig): (req: Request, res: Response) => Promise<void>;
|
|
5
9
|
//# sourceMappingURL=express.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAezD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,oBAAoB,IAIvB,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,OAAO,YAAY,mBAuEvF;AAaD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,oBAAoB,IAIrD,KAAK,OAAO,EAAE,KAAK,QAAQ,mBA8B1C"}
|
package/dist/adapters/express.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { errorBody } from '@chatcops/core';
|
|
2
|
+
import { expressClientIp } from '../client-ip.js';
|
|
1
3
|
import { createChatHandler } from '../handler.js';
|
|
4
|
+
import { PROTOCOL_HEADER, chatReply, isSessionPath } from '../http.js';
|
|
5
|
+
import { originPolicy } from '../security/origin-policy.js';
|
|
2
6
|
const chatHandlers = new WeakMap();
|
|
3
7
|
function getOrCreateChatHandler(config) {
|
|
4
8
|
let handler = chatHandlers.get(config);
|
|
@@ -9,49 +13,128 @@ function getOrCreateChatHandler(config) {
|
|
|
9
13
|
return handler;
|
|
10
14
|
}
|
|
11
15
|
export function chatcopsMiddleware(config) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const chat = getOrCreateChatHandler(config);
|
|
17
|
+
const origins = originPolicy(config);
|
|
18
|
+
return async function chatcopsHandler(req, res, _next) {
|
|
19
|
+
const header = req.headers?.origin;
|
|
20
|
+
const origin = typeof header === 'string' ? header : null;
|
|
21
|
+
const setHeaders = (headers) => {
|
|
22
|
+
for (const [name, value] of Object.entries(headers))
|
|
23
|
+
res.setHeader(name, value);
|
|
24
|
+
};
|
|
25
|
+
const sendError = (status, error) => {
|
|
26
|
+
setHeaders(origins.headers(origin));
|
|
27
|
+
res.status(status).json({ error });
|
|
28
|
+
};
|
|
19
29
|
// Handle CORS preflight
|
|
20
30
|
if (req.method === 'OPTIONS') {
|
|
21
|
-
|
|
22
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
23
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
24
|
-
res.setHeader('Access-Control-Max-Age', '86400');
|
|
31
|
+
setHeaders(origins.preflightHeaders(origin));
|
|
25
32
|
res.status(204).end();
|
|
26
33
|
return;
|
|
27
34
|
}
|
|
28
35
|
if (req.method !== 'POST') {
|
|
29
|
-
sendError(
|
|
36
|
+
sendError(405, 'Method not allowed');
|
|
30
37
|
return;
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if (!origins.allows(origin)) {
|
|
40
|
+
sendError(403, 'origin_not_allowed');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// `app.use('/api/chat', chatcopsMiddleware(...))` sends /api/chat/session here too.
|
|
44
|
+
if (isSessionPath(req.path ?? '')) {
|
|
45
|
+
setHeaders(origins.headers(origin));
|
|
46
|
+
if (!chat.startSession) {
|
|
47
|
+
res.status(404).json(errorBody('invalid_request', { message: 'Session tokens are off. Set `sessionSecrets` to turn them on.' }));
|
|
48
|
+
return;
|
|
40
49
|
}
|
|
41
|
-
|
|
50
|
+
const reply = await chat.startSession(req.body, origin, expressClientIp(req));
|
|
51
|
+
res.status(reply.status).json(reply.body);
|
|
52
|
+
return;
|
|
42
53
|
}
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
const clientIp = expressClientIp(req);
|
|
55
|
+
// Stop the reply when the visitor goes away before it's done.
|
|
56
|
+
const abort = new AbortController();
|
|
57
|
+
res.on('close', () => {
|
|
58
|
+
if (!res.writableFinished)
|
|
59
|
+
abort.abort();
|
|
60
|
+
});
|
|
61
|
+
const protocolHeader = req.headers?.[PROTOCOL_HEADER];
|
|
62
|
+
const authorization = req.headers?.authorization;
|
|
63
|
+
const reply = await chatReply(chat, req.body, clientIp, {
|
|
64
|
+
signal: abort.signal,
|
|
65
|
+
protocolHeader: typeof protocolHeader === 'string' ? protocolHeader : null,
|
|
66
|
+
authorization: typeof authorization === 'string' ? authorization : null,
|
|
67
|
+
origin,
|
|
68
|
+
});
|
|
69
|
+
if (reply.kind === 'json') {
|
|
70
|
+
setHeaders({ ...origins.headers(origin), ...reply.headers });
|
|
71
|
+
res.status(reply.status).json(reply.body);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setHeaders({ ...reply.headers, ...origins.headers(origin) });
|
|
75
|
+
try {
|
|
76
|
+
for await (const frame of reply.frames) {
|
|
77
|
+
if (abort.signal.aborted)
|
|
78
|
+
break;
|
|
79
|
+
res.write(frame);
|
|
80
|
+
}
|
|
45
81
|
}
|
|
46
82
|
finally {
|
|
47
83
|
res.end();
|
|
48
84
|
}
|
|
49
85
|
};
|
|
50
86
|
}
|
|
87
|
+
function constantTimeEqual(a, b) {
|
|
88
|
+
if (a.length !== b.length) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
let mismatch = 0;
|
|
92
|
+
for (let i = 0; i < a.length; i++) {
|
|
93
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
94
|
+
}
|
|
95
|
+
return mismatch === 0;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Analytics stats as JSON. With a storage that has an event sink, they cover
|
|
99
|
+
* every instance, for `?from=` and `?to=` (milliseconds) or the last 30 days.
|
|
100
|
+
*/
|
|
51
101
|
export function chatcopsAnalyticsHandler(config) {
|
|
52
|
-
const {
|
|
53
|
-
|
|
54
|
-
|
|
102
|
+
const { analyticsStats } = getOrCreateChatHandler(config);
|
|
103
|
+
let warned = false;
|
|
104
|
+
return async (req, res) => {
|
|
105
|
+
if (config.analyticsToken) {
|
|
106
|
+
const auth = req.headers?.authorization;
|
|
107
|
+
const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : undefined;
|
|
108
|
+
if (!token || !constantTimeEqual(token, config.analyticsToken)) {
|
|
109
|
+
res.setHeader('WWW-Authenticate', 'Bearer');
|
|
110
|
+
res.status(401).json({ error: 'unauthorized' });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (!warned) {
|
|
115
|
+
warned = true;
|
|
116
|
+
console.warn('[chatcops] The analytics endpoint is public and anyone can read your stats. Set `analyticsToken` in your ChatCopsServerConfig to require a bearer token.');
|
|
117
|
+
}
|
|
118
|
+
const from = timeParam(req.query?.from);
|
|
119
|
+
const to = timeParam(req.query?.to);
|
|
120
|
+
if (from === null || to === null) {
|
|
121
|
+
res.status(400).json({ error: 'invalid_range' });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
res.json(await analyticsStats({ from, to }));
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error('[chatcops] Reading analytics failed:', err instanceof Error ? err.message : String(err));
|
|
129
|
+
res.status(500).json({ error: 'internal_error' });
|
|
130
|
+
}
|
|
55
131
|
};
|
|
56
132
|
}
|
|
133
|
+
/** A `from` or `to` query value: unset, a time in milliseconds, or `null` when it isn't one. */
|
|
134
|
+
function timeParam(value) {
|
|
135
|
+
if (value === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
const time = typeof value === 'string' && value.trim() !== '' ? Number(value) : Number.NaN;
|
|
138
|
+
return Number.isFinite(time) ? time : null;
|
|
139
|
+
}
|
|
57
140
|
//# sourceMappingURL=express.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { chatcopsMiddleware, chatcopsAnalyticsHandler } from './express.js';
|
|
2
2
|
export { chatcopsVercelHandler } from './vercel.js';
|
|
3
3
|
export { chatcopsCloudflareHandler } from './cloudflare.js';
|
|
4
|
+
export { chatcopsAiSdkHandler } from './ai-sdk.js';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { chatcopsMiddleware, chatcopsAnalyticsHandler } from './express.js';
|
|
2
2
|
export { chatcopsVercelHandler } from './vercel.js';
|
|
3
3
|
export { chatcopsCloudflareHandler } from './cloudflare.js';
|
|
4
|
+
export { chatcopsAiSdkHandler } from './ai-sdk.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,IAIxC,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAGzD"}
|
package/dist/adapters/vercel.js
CHANGED
|
@@ -1,65 +1,12 @@
|
|
|
1
|
+
import { fetchClientIp } from '../client-ip.js';
|
|
1
2
|
import { createChatHandler } from '../handler.js';
|
|
3
|
+
import { fetchChatResponse } from '../http.js';
|
|
4
|
+
import { originPolicy } from '../security/origin-policy.js';
|
|
2
5
|
export function chatcopsVercelHandler(config) {
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
return
|
|
7
|
-
status,
|
|
8
|
-
headers: {
|
|
9
|
-
'Content-Type': 'application/json',
|
|
10
|
-
'Access-Control-Allow-Origin': corsOrigin,
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
return async function handler(req) {
|
|
15
|
-
// Handle CORS preflight
|
|
16
|
-
if (req.method === 'OPTIONS') {
|
|
17
|
-
return new Response(null, {
|
|
18
|
-
status: 204,
|
|
19
|
-
headers: {
|
|
20
|
-
'Access-Control-Allow-Origin': corsOrigin,
|
|
21
|
-
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
22
|
-
'Access-Control-Allow-Headers': 'Content-Type',
|
|
23
|
-
'Access-Control-Max-Age': '86400',
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (req.method !== 'POST') {
|
|
28
|
-
return errorResponse(405, 'Method not allowed');
|
|
29
|
-
}
|
|
30
|
-
const clientIp = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown';
|
|
31
|
-
let body;
|
|
32
|
-
try {
|
|
33
|
-
body = await req.json();
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return errorResponse(400, 'Invalid JSON body');
|
|
37
|
-
}
|
|
38
|
-
const encoder = new TextEncoder();
|
|
39
|
-
const stream = new ReadableStream({
|
|
40
|
-
async start(controller) {
|
|
41
|
-
try {
|
|
42
|
-
for await (const chunk of handleChat(body, clientIp)) {
|
|
43
|
-
controller.enqueue(encoder.encode(`data: ${chunk}\n\n`));
|
|
44
|
-
}
|
|
45
|
-
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: 'internal_error' })}\n\n`));
|
|
49
|
-
}
|
|
50
|
-
finally {
|
|
51
|
-
controller.close();
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
return new Response(stream, {
|
|
56
|
-
headers: {
|
|
57
|
-
'Content-Type': 'text/event-stream',
|
|
58
|
-
'Cache-Control': 'no-cache',
|
|
59
|
-
'Connection': 'keep-alive',
|
|
60
|
-
'Access-Control-Allow-Origin': corsOrigin,
|
|
61
|
-
},
|
|
62
|
-
});
|
|
6
|
+
const chat = createChatHandler(config);
|
|
7
|
+
const origins = originPolicy(config);
|
|
8
|
+
return function handler(req) {
|
|
9
|
+
return fetchChatResponse(chat, origins, req, { clientIp: (request) => fetchClientIp(request, config.clientIp) });
|
|
63
10
|
};
|
|
64
11
|
}
|
|
65
12
|
//# sourceMappingURL=vercel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG5D,MAAM,UAAU,qBAAqB,CAAC,MAA4B;IAChE,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAErC,OAAO,SAAS,OAAO,CAAC,GAAY;QAClC,OAAO,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The visitor's IP, for per-IP rate limits and budgets. Only headers a
|
|
3
|
+
* platform sets itself are read, never ones a visitor can send. When the IP
|
|
4
|
+
* can't be told, per-IP limits are skipped for that request (with one
|
|
5
|
+
* warning) instead of every visitor sharing one bucket.
|
|
6
|
+
*/
|
|
7
|
+
/** Reads the visitor's IP from a request, for hosts the adapter doesn't know. */
|
|
8
|
+
export type ClientIpResolver = (request: Request) => string | null | undefined;
|
|
9
|
+
export declare function cleanIp(value: string | null | undefined): string | undefined;
|
|
10
|
+
/** Vercel overwrites these headers, so a visitor can't set them there. */
|
|
11
|
+
export declare function onVercel(): boolean;
|
|
12
|
+
export declare function warnOnce(key: string, message: string): void;
|
|
13
|
+
/** For tests. */
|
|
14
|
+
export declare function resetClientIpWarnings(): void;
|
|
15
|
+
/** The fetch handler for Vercel and other Fetch API runtimes. */
|
|
16
|
+
export declare function fetchClientIp(request: Request, resolver: ClientIpResolver | undefined): string | undefined;
|
|
17
|
+
/** Cloudflare sets CF-Connecting-IP on every request that reaches a Worker. */
|
|
18
|
+
export declare function cloudflareClientIp(request: Request, resolver: ClientIpResolver | undefined): string | undefined;
|
|
19
|
+
interface ExpressLikeRequest {
|
|
20
|
+
ip?: string;
|
|
21
|
+
socket?: {
|
|
22
|
+
remoteAddress?: string;
|
|
23
|
+
};
|
|
24
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
25
|
+
app?: {
|
|
26
|
+
get?(setting: string): unknown;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Express's `req.ip` follows its `trust proxy` setting. Behind a proxy with
|
|
31
|
+
* that setting off, every visitor gets the proxy's address and shares one
|
|
32
|
+
* rate limit and budget, so that case gets a warning.
|
|
33
|
+
*/
|
|
34
|
+
export declare function expressClientIp(req: ExpressLikeRequest): string | undefined;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=client-ip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-ip.d.ts","sourceRoot":"","sources":["../src/client-ip.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAQ/E,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG5E;AAED,0EAA0E;AAC1E,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAID,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI3D;AAED,iBAAiB;AACjB,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW1G;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO/G;AAED,UAAU,kBAAkB;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACxD,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAQ3E"}
|