@agentpulsehq/core 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +110 -2
- package/dist/adapters/express.d.ts +17 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +265 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +10 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +43 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/next.d.ts +56 -0
- package/dist/adapters/next.d.ts.map +1 -0
- package/dist/adapters/next.js +187 -0
- package/dist/adapters/next.js.map +1 -0
- package/dist/converter.d.ts +79 -0
- package/dist/converter.d.ts.map +1 -0
- package/dist/converter.js +689 -0
- package/dist/converter.js.map +1 -0
- package/dist/engine.d.ts +54 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +330 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +70 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +117 -0
- package/dist/limits.js.map +1 -0
- package/dist/redact.d.ts +20 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +86 -0
- package/dist/redact.js.map +1 -0
- package/dist/telemetry.d.ts +91 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +261 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rolandtor19
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,111 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agentpulsehq/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Detect AI agents, serve them clean Markdown, declare [Content Signals](https://contentsignals.org) and
|
|
4
|
+
send telemetry to AgentPulse. Adapters for Hono, Express (also Connect and `node:http`) and Next.js 16.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install @agentpulsehq/core
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Requires Node.js 22 or later. `next` is an optional peer dependency, needed only for `@agentpulsehq/core/next`.
|
|
11
|
+
|
|
12
|
+
## Hono
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Hono } from "hono";
|
|
16
|
+
import { createIngestClient } from "@agentpulsehq/core";
|
|
17
|
+
import { agentPulse } from "@agentpulsehq/core/hono";
|
|
18
|
+
|
|
19
|
+
const telemetry = createIngestClient({ writeKey: process.env.AGENTPULSE_WRITE_KEY! });
|
|
20
|
+
|
|
21
|
+
const app = new Hono();
|
|
22
|
+
app.use("*", agentPulse({ contentSignals: { search: true, aiInput: true, aiTrain: false }, onEvent: telemetry.onEvent }));
|
|
23
|
+
|
|
24
|
+
export default app;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
On serverless platforms, flush pending events at the end of each request with `waitUntil(telemetry.flush())`.
|
|
28
|
+
|
|
29
|
+
## Express
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import express from "express";
|
|
33
|
+
import { createIngestClient } from "@agentpulsehq/core";
|
|
34
|
+
import { agentPulse } from "@agentpulsehq/core/express";
|
|
35
|
+
|
|
36
|
+
const telemetry = createIngestClient({ writeKey: process.env.AGENTPULSE_WRITE_KEY! });
|
|
37
|
+
|
|
38
|
+
const app = express();
|
|
39
|
+
app.use(agentPulse({ onEvent: telemetry.onEvent }));
|
|
40
|
+
|
|
41
|
+
const server = app.listen(3000);
|
|
42
|
+
process.on("SIGTERM", () => server.close(() => void telemetry.shutdown()));
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Next.js 16
|
|
46
|
+
|
|
47
|
+
`proxy.ts` detects agents and rewrites their requests to a route handler that converts the page:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// proxy.ts
|
|
51
|
+
import { createAgentPulseProxy } from "@agentpulsehq/core/next";
|
|
52
|
+
import { telemetry } from "./lib/agentpulse";
|
|
53
|
+
|
|
54
|
+
export const proxy = createAgentPulseProxy({ onEvent: telemetry.onEvent });
|
|
55
|
+
|
|
56
|
+
export const config = {
|
|
57
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico|agentpulse-markdown).*)"]
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// app/agentpulse-markdown/[[...path]]/route.ts
|
|
63
|
+
import { createMarkdownRouteHandler } from "@agentpulsehq/core/next";
|
|
64
|
+
import { telemetry } from "../../../lib/agentpulse";
|
|
65
|
+
|
|
66
|
+
export const dynamic = "force-dynamic";
|
|
67
|
+
|
|
68
|
+
// internalOrigin: where this app is reachable from the server itself (never taken from the request).
|
|
69
|
+
export const GET = createMarkdownRouteHandler({
|
|
70
|
+
internalOrigin: process.env.AGENTPULSE_INTERNAL_ORIGIN!,
|
|
71
|
+
onEvent: telemetry.onEvent
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What it does
|
|
76
|
+
|
|
77
|
+
- **Markdown negotiation.** Requests that prefer `text/markdown` (RFC 9110 q-values) get the HTML page
|
|
78
|
+
converted to Markdown, with `x-markdown-tokens` / `x-original-tokens` estimates. Everyone else gets the
|
|
79
|
+
original HTML, with `Vary: Accept`.
|
|
80
|
+
- **Agent detection.** It matches the User-Agent against the catalog of documented agents in
|
|
81
|
+
[`@agentpulsehq/rules`](https://www.npmjs.com/package/@agentpulsehq/rules). With `AgentVerifier`, it
|
|
82
|
+
also checks the client IP against the ranges each operator publishes.
|
|
83
|
+
- **Content Signals.** Serve `contentSignalsRobotsTxt(signals)` from `/robots.txt`, which is where the
|
|
84
|
+
specification defines them. The `Content-Signal` response header is not standard, so it is opt-in
|
|
85
|
+
(`emitContentSignalHeader: true`).
|
|
86
|
+
- **Telemetry.** `IngestClient` batches events, retries with backoff and never throws into your app.
|
|
87
|
+
Paths are normalized and personal data is redacted before sending, and the client IP is never sent.
|
|
88
|
+
The write key belongs in a server environment variable: never ship it to a browser.
|
|
89
|
+
|
|
90
|
+
## Without AgentPulse Cloud
|
|
91
|
+
|
|
92
|
+
`htmlToMarkdown()` and the adapters work without an account. Leave out `onEvent`, or pass your own callback.
|
|
93
|
+
|
|
94
|
+
## AgentPulse Cloud (Early Access)
|
|
95
|
+
|
|
96
|
+
`IngestClient` sends events to `https://ingest.agentpulse.app/api/v1/ingest` by default, and you see them in
|
|
97
|
+
the dashboard at [app.agentpulse.app](https://app.agentpulse.app), where you also create the write key
|
|
98
|
+
(`apw_…`). Pass `endpoint` to send them to your own ingest instead.
|
|
99
|
+
|
|
100
|
+
## Related packages
|
|
101
|
+
|
|
102
|
+
- [`@agentpulsehq/rules`](https://www.npmjs.com/package/@agentpulsehq/rules): the agent catalog, detection
|
|
103
|
+
and IP verification this package builds on.
|
|
104
|
+
- [`@agentpulsehq/mcp`](https://www.npmjs.com/package/@agentpulsehq/mcp): an MCP server with your site's
|
|
105
|
+
AI-agent metrics.
|
|
106
|
+
- [`agentpulse-asgi`](https://pypi.org/project/agentpulse-asgi/) on PyPI: the same middleware for FastAPI
|
|
107
|
+
and Starlette.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { AgentPulseOptions } from "../types.js";
|
|
3
|
+
/** Options for the Express adapter: `getClientIp` receives Node's `IncomingMessage` (Express's `req`). */
|
|
4
|
+
export type ExpressAgentPulseOptions = AgentPulseOptions<IncomingMessage>;
|
|
5
|
+
type NextFunction = (err?: unknown) => void;
|
|
6
|
+
/**
|
|
7
|
+
* AgentPulse middleware for Express (also Connect and plain `node:http`).
|
|
8
|
+
*
|
|
9
|
+
* Mount it **after** compression middleware (`app.use(compression())` first) so it sees the
|
|
10
|
+
* uncompressed HTML; compressed bodies are passed through untouched.
|
|
11
|
+
*
|
|
12
|
+
* For `getClientIp`, use the socket address (`req.socket.remoteAddress`) or, behind a trusted
|
|
13
|
+
* proxy, Express's `req.ip` with `app.set("trust proxy", …)` configured.
|
|
14
|
+
*/
|
|
15
|
+
export declare function agentPulse(options?: ExpressAgentPulseOptions): (req: IncomingMessage, res: ServerResponse, next: NextFunction) => void;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAA2C,MAAM,WAAW,CAAC;AAE1G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,0GAA0G;AAC1G,MAAM,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;AAE1E,KAAK,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAM5C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,wBAA6B,IAG1B,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,KAAG,IAAI,CAW1G"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { createEngine, EMPTY_FIELDS } from "../engine.js";
|
|
2
|
+
/** Bytes buffered between the app's writes and the converter before `res.write` asks the app to wait. */
|
|
3
|
+
const HIGH_WATER_MARK = 64 * 1024;
|
|
4
|
+
/**
|
|
5
|
+
* AgentPulse middleware for Express (also Connect and plain `node:http`).
|
|
6
|
+
*
|
|
7
|
+
* Mount it **after** compression middleware (`app.use(compression())` first) so it sees the
|
|
8
|
+
* uncompressed HTML; compressed bodies are passed through untouched.
|
|
9
|
+
*
|
|
10
|
+
* For `getClientIp`, use the socket address (`req.socket.remoteAddress`) or, behind a trusted
|
|
11
|
+
* proxy, Express's `req.ip` with `app.set("trust proxy", …)` configured.
|
|
12
|
+
*/
|
|
13
|
+
export function agentPulse(options = {}) {
|
|
14
|
+
const engine = createEngine(options);
|
|
15
|
+
return function agentPulseMiddleware(req, res, next) {
|
|
16
|
+
const state = engine.inspect({
|
|
17
|
+
userAgent: firstHeader(req.headers["user-agent"]),
|
|
18
|
+
accept: firstHeader(req.headers.accept),
|
|
19
|
+
clientIp: engine.clientIp(req),
|
|
20
|
+
method: req.method ?? "GET",
|
|
21
|
+
url: requestUrl(req)
|
|
22
|
+
});
|
|
23
|
+
intercept(res, engine, state);
|
|
24
|
+
next();
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Patches `writeHead`/`write`/`end`. The decision is taken when headers are about to be sent:
|
|
29
|
+
* non-convertible responses go straight through; HTML that should become Markdown is diverted
|
|
30
|
+
* into a Web stream, run through the shared engine, and the result written to the real response.
|
|
31
|
+
*/
|
|
32
|
+
function intercept(res, engine, state) {
|
|
33
|
+
const original = { writeHead: res.writeHead, write: res.write, end: res.end };
|
|
34
|
+
let decided = false;
|
|
35
|
+
let sink;
|
|
36
|
+
let fields = EMPTY_FIELDS;
|
|
37
|
+
let emitted = false;
|
|
38
|
+
const emitOnce = () => {
|
|
39
|
+
if (emitted)
|
|
40
|
+
return;
|
|
41
|
+
emitted = true;
|
|
42
|
+
engine.emit(state, fields);
|
|
43
|
+
};
|
|
44
|
+
res.once("finish", emitOnce);
|
|
45
|
+
res.once("close", emitOnce);
|
|
46
|
+
/** Folds explicit writeHead headers into the response, then decides whether to transform. */
|
|
47
|
+
const decide = (statusCode, args) => {
|
|
48
|
+
decided = true;
|
|
49
|
+
const statusMessage = typeof args[0] === "string" ? args[0] : undefined;
|
|
50
|
+
const headers = typeof args[0] === "string" ? args[1] : args[0];
|
|
51
|
+
applyHeaders(res, headers);
|
|
52
|
+
engine.decorate({ get: (name) => firstHeader(res.getHeader(name)), set: (name, value) => res.setHeader(name, value) }, state);
|
|
53
|
+
const transform = engine.shouldTransform(state, {
|
|
54
|
+
status: statusCode,
|
|
55
|
+
contentType: firstHeader(res.getHeader("content-type")) ?? "",
|
|
56
|
+
contentEncoding: firstHeader(res.getHeader("content-encoding")),
|
|
57
|
+
hasBody: true
|
|
58
|
+
});
|
|
59
|
+
if (transform) {
|
|
60
|
+
sink = createSink(res, original, engine, state, statusCode, statusMessage, (result) => {
|
|
61
|
+
fields = result;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return statusMessage;
|
|
65
|
+
};
|
|
66
|
+
res.writeHead = function patchedWriteHead(statusCode, ...args) {
|
|
67
|
+
if (decided)
|
|
68
|
+
return callOriginal(original.writeHead, this, [statusCode, ...args]);
|
|
69
|
+
const statusMessage = decide(statusCode, args);
|
|
70
|
+
if (sink) {
|
|
71
|
+
this.statusCode = statusCode;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
// Headers passed to writeHead were folded into the response by decide().
|
|
75
|
+
return callOriginal(original.writeHead, this, statusMessage === undefined ? [statusCode] : [statusCode, statusMessage]);
|
|
76
|
+
};
|
|
77
|
+
res.write = function patchedWrite(...args) {
|
|
78
|
+
if (!decided)
|
|
79
|
+
decide(this.statusCode, []);
|
|
80
|
+
if (!sink)
|
|
81
|
+
return callOriginal(original.write, this, args);
|
|
82
|
+
const { chunk, encoding, callback } = writeArgs(args);
|
|
83
|
+
return sink.write(toBytes(chunk, encoding), callback);
|
|
84
|
+
};
|
|
85
|
+
res.end = function patchedEnd(...args) {
|
|
86
|
+
if (!decided)
|
|
87
|
+
decide(this.statusCode, []);
|
|
88
|
+
if (!sink)
|
|
89
|
+
return callOriginal(original.end, this, args);
|
|
90
|
+
const { chunk, encoding, callback } = writeArgs(args);
|
|
91
|
+
if (chunk !== undefined && chunk !== null)
|
|
92
|
+
sink.write(toBytes(chunk, encoding));
|
|
93
|
+
sink.end(callback);
|
|
94
|
+
return this;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Bridges the app's writes into a ReadableStream (with backpressure: `write` returns false and
|
|
99
|
+
* `drain` fires when the converter catches up), runs the engine and flushes its response.
|
|
100
|
+
*/
|
|
101
|
+
function createSink(res, original, engine, state, statusCode, statusMessage, onFields) {
|
|
102
|
+
let controller;
|
|
103
|
+
let closed = false;
|
|
104
|
+
let discarding = false;
|
|
105
|
+
let waitingForDrain = false;
|
|
106
|
+
let flushed = false;
|
|
107
|
+
let endCallback;
|
|
108
|
+
const releaseApp = () => {
|
|
109
|
+
if (waitingForDrain) {
|
|
110
|
+
waitingForDrain = false;
|
|
111
|
+
res.emit("drain");
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const body = new ReadableStream({
|
|
115
|
+
start(c) {
|
|
116
|
+
controller = c;
|
|
117
|
+
},
|
|
118
|
+
pull: releaseApp,
|
|
119
|
+
cancel() {
|
|
120
|
+
// The engine has read enough (truncation or cache hit): further app writes are discarded,
|
|
121
|
+
// and write() keeps returning false so well-behaved sources (pipe, pipeline) stop producing.
|
|
122
|
+
closed = true;
|
|
123
|
+
discarding = true;
|
|
124
|
+
}
|
|
125
|
+
}, new ByteLengthQueuingStrategy({ highWaterMark: HIGH_WATER_MARK }));
|
|
126
|
+
const htmlResponse = new Response(body, {
|
|
127
|
+
status: statusCode,
|
|
128
|
+
statusText: statusMessage,
|
|
129
|
+
headers: toWebHeaders(res)
|
|
130
|
+
});
|
|
131
|
+
engine
|
|
132
|
+
.transform(htmlResponse, state)
|
|
133
|
+
.then(async ({ response, fields }) => {
|
|
134
|
+
onFields(fields);
|
|
135
|
+
await flush(res, original, response);
|
|
136
|
+
callOriginal(original.end, res, endCallback ? [endCallback] : []);
|
|
137
|
+
flushed = true;
|
|
138
|
+
// Release anything still awaiting 'drain' (its writes are discarded): no dangling promises.
|
|
139
|
+
releaseApp();
|
|
140
|
+
})
|
|
141
|
+
.catch((err) => {
|
|
142
|
+
console.error("[AgentPulse] Failed to write the transformed response:", err);
|
|
143
|
+
res.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
write(bytes, callback) {
|
|
147
|
+
if (!closed && bytes.byteLength > 0)
|
|
148
|
+
controller.enqueue(bytes);
|
|
149
|
+
if (callback)
|
|
150
|
+
queueMicrotask(() => callback());
|
|
151
|
+
if (discarding || (!closed && (controller.desiredSize ?? 1) <= 0)) {
|
|
152
|
+
waitingForDrain = true;
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
end(callback) {
|
|
158
|
+
// The response may already be complete (truncated read): still honour the app's callback.
|
|
159
|
+
if (flushed) {
|
|
160
|
+
if (callback)
|
|
161
|
+
queueMicrotask(() => callback());
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
endCallback = callback;
|
|
165
|
+
if (!closed) {
|
|
166
|
+
closed = true;
|
|
167
|
+
controller.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Replaces the response headers with the engine's and streams its body through the real methods. */
|
|
173
|
+
async function flush(res, original, response) {
|
|
174
|
+
for (const name of res.getHeaderNames())
|
|
175
|
+
res.removeHeader(name);
|
|
176
|
+
response.headers.forEach((value, name) => {
|
|
177
|
+
if (name !== "set-cookie")
|
|
178
|
+
res.setHeader(name, value);
|
|
179
|
+
});
|
|
180
|
+
const cookies = response.headers.getSetCookie();
|
|
181
|
+
if (cookies.length)
|
|
182
|
+
res.setHeader("set-cookie", cookies);
|
|
183
|
+
callOriginal(original.writeHead, res, response.statusText ? [response.status, response.statusText] : [response.status]);
|
|
184
|
+
if (!response.body)
|
|
185
|
+
return;
|
|
186
|
+
const reader = response.body.getReader();
|
|
187
|
+
while (true) {
|
|
188
|
+
const { done, value } = await reader.read();
|
|
189
|
+
if (done)
|
|
190
|
+
break;
|
|
191
|
+
if (res.destroyed) {
|
|
192
|
+
await reader.cancel();
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
const ok = callOriginal(original.write, res, [value]);
|
|
196
|
+
if (!ok)
|
|
197
|
+
await new Promise((resolve) => res.once("drain", () => resolve()));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function toWebHeaders(res) {
|
|
201
|
+
const headers = new Headers();
|
|
202
|
+
for (const name of res.getHeaderNames()) {
|
|
203
|
+
const value = res.getHeader(name);
|
|
204
|
+
if (value === undefined)
|
|
205
|
+
continue;
|
|
206
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
207
|
+
headers.append(name, String(item));
|
|
208
|
+
}
|
|
209
|
+
return headers;
|
|
210
|
+
}
|
|
211
|
+
/** Applies headers given to `writeHead` (object or flat `[name, value, …]` array) via setHeader. */
|
|
212
|
+
function applyHeaders(res, headers) {
|
|
213
|
+
if (!headers || typeof headers !== "object")
|
|
214
|
+
return;
|
|
215
|
+
if (Array.isArray(headers)) {
|
|
216
|
+
const flat = headers.flat();
|
|
217
|
+
for (let i = 0; i + 1 < flat.length; i += 2)
|
|
218
|
+
res.setHeader(String(flat[i]), flat[i + 1]);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
222
|
+
if (value !== undefined)
|
|
223
|
+
res.setHeader(name, value);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function writeArgs(args) {
|
|
227
|
+
const [chunk, second, third] = args;
|
|
228
|
+
if (typeof chunk === "function")
|
|
229
|
+
return { chunk: undefined, encoding: undefined, callback: chunk };
|
|
230
|
+
if (typeof second === "function")
|
|
231
|
+
return { chunk, encoding: undefined, callback: second };
|
|
232
|
+
return {
|
|
233
|
+
chunk,
|
|
234
|
+
encoding: typeof second === "string" ? second : undefined,
|
|
235
|
+
callback: typeof third === "function" ? third : undefined
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function toBytes(chunk, encoding) {
|
|
239
|
+
if (chunk === undefined || chunk === null)
|
|
240
|
+
return new Uint8Array(0);
|
|
241
|
+
if (chunk instanceof Uint8Array)
|
|
242
|
+
return chunk;
|
|
243
|
+
return Buffer.from(String(chunk), encoding ?? "utf8");
|
|
244
|
+
}
|
|
245
|
+
function callOriginal(method, self, args) {
|
|
246
|
+
return method.apply(self, args);
|
|
247
|
+
}
|
|
248
|
+
function firstHeader(value) {
|
|
249
|
+
if (value === undefined)
|
|
250
|
+
return undefined;
|
|
251
|
+
return Array.isArray(value) ? value[0] : String(value);
|
|
252
|
+
}
|
|
253
|
+
/** Absolute URL for cache keys and telemetry (query stripped later). Express keeps the mount-free path in originalUrl. */
|
|
254
|
+
function requestUrl(req) {
|
|
255
|
+
const path = req.originalUrl ?? req.url ?? "/";
|
|
256
|
+
const encrypted = req.socket?.encrypted === true;
|
|
257
|
+
const host = firstHeader(req.headers.host) ?? "localhost";
|
|
258
|
+
try {
|
|
259
|
+
return new URL(path, `${encrypted ? "https" : "http"}://${host}`).toString();
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return `http://localhost${path.startsWith("/") ? path : "/"}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from "hono";
|
|
2
|
+
import type { AgentPulseOptions } from "../types.js";
|
|
3
|
+
/** Options for the Hono adapter: `getClientIp` receives Hono's `Context`. */
|
|
4
|
+
export type HonoAgentPulseOptions = AgentPulseOptions<Context>;
|
|
5
|
+
/**
|
|
6
|
+
* AgentPulse middleware for Hono. All detection, negotiation and security logic lives in the
|
|
7
|
+
* shared engine; this adapter only maps Hono's request/response to it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function agentPulse(options?: HonoAgentPulseOptions): MiddlewareHandler;
|
|
10
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,6EAA6E;AAC7E,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,qBAA0B,GAAG,iBAAiB,CAgCjF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createEngine } from "../engine.js";
|
|
2
|
+
/**
|
|
3
|
+
* AgentPulse middleware for Hono. All detection, negotiation and security logic lives in the
|
|
4
|
+
* shared engine; this adapter only maps Hono's request/response to it.
|
|
5
|
+
*/
|
|
6
|
+
export function agentPulse(options = {}) {
|
|
7
|
+
const engine = createEngine(options);
|
|
8
|
+
return async (c, next) => {
|
|
9
|
+
const state = engine.inspect({
|
|
10
|
+
userAgent: c.req.header("user-agent"),
|
|
11
|
+
accept: c.req.header("accept"),
|
|
12
|
+
clientIp: engine.clientIp(c),
|
|
13
|
+
method: c.req.method,
|
|
14
|
+
url: c.req.url
|
|
15
|
+
});
|
|
16
|
+
await next();
|
|
17
|
+
engine.decorate({ get: (name) => c.res.headers.get(name) ?? undefined, set: (name, value) => c.header(name, value) }, state);
|
|
18
|
+
let fields = { markdownDelivered: false };
|
|
19
|
+
const res = c.res;
|
|
20
|
+
if (engine.shouldTransform(state, {
|
|
21
|
+
status: res.status,
|
|
22
|
+
contentType: res.headers.get("content-type") ?? "",
|
|
23
|
+
contentEncoding: res.headers.get("content-encoding") ?? undefined,
|
|
24
|
+
hasBody: res.body !== null
|
|
25
|
+
})) {
|
|
26
|
+
const result = await engine.transform(res, state);
|
|
27
|
+
c.res = undefined; // Hono merges the old headers into a new response unless it is cleared first.
|
|
28
|
+
c.res = result.response;
|
|
29
|
+
fields = result.fields;
|
|
30
|
+
}
|
|
31
|
+
engine.emit(state, fields, (promise) => waitUntil(c, promise));
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** On edge runtimes, keep the isolate alive until telemetry is delivered; on Node it simply runs. */
|
|
35
|
+
function waitUntil(c, promise) {
|
|
36
|
+
try {
|
|
37
|
+
c.executionCtx.waitUntil(promise);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// No ExecutionContext (Node, Bun, tests): the promise runs detached.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAM5C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAAiC,EAAE;IAC5D,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAErC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC;YAC3B,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC;YACrC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC9B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC5B,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM;YACpB,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG;SACf,CAAC,CAAC;QAEH,MAAM,IAAI,EAAE,CAAC;QAEb,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAE7H,IAAI,MAAM,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QAClB,IAAI,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE;YAChC,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE;YAClD,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,SAAS;YACjE,OAAO,EAAE,GAAG,CAAC,IAAI,KAAK,IAAI;SAC3B,CAAC,EAAE,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC,8EAA8E;YACjG,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC;YACxB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,SAAS,SAAS,CAAC,CAAU,EAAE,OAAyB;IACtD,IAAI,CAAC;QACH,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;IACvE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest, type NextFetchEvent } from "next/server.js";
|
|
2
|
+
import type { AgentPulseOptions } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Next.js 16+ integration (App Router). Next's `proxy.ts` (formerly `middleware.ts`) runs before
|
|
5
|
+
* rendering and cannot read response bodies, so the work is split in two pieces that share the
|
|
6
|
+
* engine with the Hono and Express adapters (see docs/Brain "AgentPulse - Spike Next.js"):
|
|
7
|
+
*
|
|
8
|
+
* 1. `createAgentPulseProxy`: detects agents and rewrites Markdown requests to a Route Handler.
|
|
9
|
+
* 2. `createMarkdownRouteHandler`: fetches the page's HTML from the app itself, from a fixed
|
|
10
|
+
* internal origin, and converts it.
|
|
11
|
+
*/
|
|
12
|
+
/** Default path of the Markdown Route Handler (`app/agentpulse-markdown/[[...path]]/route.ts`). */
|
|
13
|
+
export declare const DEFAULT_MARKDOWN_ROUTE = "/agentpulse-markdown";
|
|
14
|
+
/** Marks the handler's internal HTML fetch so the proxy never rewrites it again (no loops). */
|
|
15
|
+
export declare const INTERNAL_HEADER = "x-agentpulse-internal";
|
|
16
|
+
/** Request headers forwarded to the internal fetch so the Markdown matches what this user would see. */
|
|
17
|
+
export declare const DEFAULT_FORWARDED_HEADERS: string[];
|
|
18
|
+
export interface NextProxyOptions extends AgentPulseOptions<NextRequest> {
|
|
19
|
+
/** Must match the Route Handler's folder. Default: `/agentpulse-markdown`. */
|
|
20
|
+
markdownRoute?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface NextMarkdownHandlerOptions extends AgentPulseOptions<Request> {
|
|
23
|
+
/**
|
|
24
|
+
* Where the app itself is reachable from the server, e.g. `http://127.0.0.1:3000`. **Required**
|
|
25
|
+
* and never derived from the request: using the Host header would let clients point the server
|
|
26
|
+
* at another host (SSRF).
|
|
27
|
+
*/
|
|
28
|
+
internalOrigin: string;
|
|
29
|
+
/** Must match the Route Handler's folder and the proxy option. Default: `/agentpulse-markdown`. */
|
|
30
|
+
markdownRoute?: string;
|
|
31
|
+
/** Request headers forwarded to the internal fetch (allow-list). Default: cookie, authorization, accept-language, user-agent. */
|
|
32
|
+
forwardHeaders?: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Cache-Control for Markdown responses. `"private"` (default) keeps shared caches (CDNs) from
|
|
35
|
+
* storing them: Next.js drops the proxy's `Vary: Accept` on HTML pages, so a CDN that ignores
|
|
36
|
+
* Vary could otherwise serve Markdown to browsers. `"inherit"` keeps the page's own header.
|
|
37
|
+
*/
|
|
38
|
+
markdownCacheControl?: "private" | "inherit";
|
|
39
|
+
/** fetch implementation for the internal request (tests). Default: global fetch. */
|
|
40
|
+
fetch?: typeof globalThis.fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `proxy.ts` handler. Rewrites requests that prefer Markdown to the Route Handler and adds
|
|
44
|
+
* `Vary` to the rest. HTML visits are reported to `onEvent` via `event.waitUntil`; Markdown
|
|
45
|
+
* visits are reported by the Route Handler (one event per request).
|
|
46
|
+
*/
|
|
47
|
+
export declare function createAgentPulseProxy(options?: NextProxyOptions): (request: NextRequest, event?: NextFetchEvent) => NextResponse;
|
|
48
|
+
/**
|
|
49
|
+
* GET handler for `app/agentpulse-markdown/[[...path]]/route.ts`. Fetches the page's HTML from
|
|
50
|
+
* `internalOrigin` and returns its Markdown through the shared engine (cache, rate limit, IP
|
|
51
|
+
* verification, sanitization, telemetry).
|
|
52
|
+
*/
|
|
53
|
+
export declare function createMarkdownRouteHandler(options: NextMarkdownHandlerOptions): (request: Request) => Promise<Response>;
|
|
54
|
+
/** Keeps freshness directives but forbids shared caches: drops public/s-maxage, adds private; no-store wins. */
|
|
55
|
+
export declare function privateCacheControl(value: string | null): string;
|
|
56
|
+
//# sourceMappingURL=next.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;GAQG;AAEH,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,+FAA+F;AAC/F,eAAO,MAAM,eAAe,0BAA0B,CAAC;AAEvD,wGAAwG;AACxG,eAAO,MAAM,yBAAyB,UAA+D,CAAC;AAEtG,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB,CAAC,WAAW,CAAC;IACtE,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,0BAA2B,SAAQ,iBAAiB,CAAC,OAAO,CAAC;IAC5E;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,mGAAmG;IACnG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iIAAiI;IACjI,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7C,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,gBAAqB,IAKlC,SAAS,WAAW,EAAE,QAAQ,cAAc,KAAG,YAAY,CAyB5F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,0BAA0B,IAQlD,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAmE/D;AAED,gHAAgH;AAChH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAWhE"}
|