@knownagents/sdk 2.0.0 → 2.2.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 CHANGED
@@ -22,131 +22,92 @@ import { KnownAgents } from "@knownagents/sdk"
22
22
  const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN")
23
23
  ```
24
24
 
25
- ## How To Set Up Agent & LLM Analytics ([Full Docs](https://knownagents.com/docs/analytics))
26
-
27
- Get realtime insight into the hidden ecosystem of [AI agents and other bots](https://knownagents.com/agents) browsing your website, REST APIs, and [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) servers. Measure human traffic coming from AI chat and search platforms like ChatGPT, Perplexity, and Gemini.
28
-
29
- ### Track Pageview and REST Calls
30
-
31
- To collect pageview and REST API analytics, call `trackPageviewOrRESTCall` with the incoming Node.js request and response.
32
-
33
- ```ts
34
- knownAgents.trackPageviewOrRESTCall(request, response)
35
- ```
36
-
37
- For REST APIs that return [ACP (Agentic Commerce Protocol)](https://www.agenticcommerce.dev/) or [UCP (Universal Commerce Protocol)](https://ucp.dev/) commerce metadata, include the response body:
38
-
39
- ```ts
40
- knownAgents.trackPageviewOrRESTCall(request, response, {
41
- restACPResponseBody: acpResponseBody
42
- })
43
- ```
25
+ To batch visits, set the visit event queue size and flush interval:
44
26
 
45
27
  ```ts
46
- knownAgents.trackPageviewOrRESTCall(request, response, {
47
- restUCPResponseBody: ucpResponseBody
28
+ const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN", {
29
+ flushQueueSize: 1000,
30
+ flushIntervalInMilliseconds: 10000
48
31
  })
49
32
  ```
50
33
 
51
- The SDK waits for the response to finish, then sends status, headers, duration, and any commerce metadata. Sensitive headers are filtered automatically.
34
+ The entire visit event queue is uploaded when it reaches `flushQueueSize` or when the flush interval elapses after the first visit event enters an empty queue.
52
35
 
53
- #### Use Middleware if Possible
36
+ ## Send Visit Events ([Agent Analytics](https://knownagents.com/products/agent-analytics), [LLM Referral Tracking](https://knownagents.com/products/llm-referral-tracking))
54
37
 
55
- If your framework supports middleware, call `trackPageviewOrRESTCall` there so pageviews and REST calls are tracked from a single place. [MCP](https://modelcontextprotocol.io/) requests are skipped automatically.
38
+ Get realtime insight into how [AI agents and other bots](https://knownagents.com/agents) browse your web pages, call your [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) endpoints, and purchase products through your [ACP (Agentic Commerce Protocol)](https://www.agenticcommerce.dev/) and [UCP (Universal Commerce Protocol)](https://ucp.dev/) shopping flows. Measure human traffic from AI chat and search platforms like ChatGPT, Claude, and Gemini.
56
39
 
57
- ```ts
58
- import express from "express"
59
- import { KnownAgents } from "@knownagents/sdk"
40
+ ### Pageviews and REST Calls
60
41
 
61
- const app = express()
62
- const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN")
42
+ Call `trackPageviewOrRESTCall` with the incoming request and outgoing response. If you can, do this in middleware. This method skips MCP calls automatically.
63
43
 
64
- app.use((request, response, next) => {
65
- knownAgents.trackPageviewOrRESTCall(request, response)
66
- next()
67
- })
68
-
69
- app.get("/", (request, response) => {
70
- response.send("Hello, world!")
71
- })
72
-
73
- app.listen(3000, () => console.log("Server running on port 3000"))
44
+ ```ts
45
+ knownAgents.trackPageviewOrRESTCall(request, response)
74
46
  ```
75
47
 
76
- ### Track MCP Calls
77
-
78
- For [MCP](https://modelcontextprotocol.io/) servers that use `StreamableHTTPServerTransport`, call `trackMCPCall` after connecting the transport and before the transport handles the request.
79
-
80
- For MCP calls that return [ACP](https://www.agenticcommerce.dev/) or [UCP](https://ucp.dev/) commerce metadata, the SDK captures the relevant currency and total amount automatically.
48
+ ### MCP Calls (Optional)
81
49
 
82
- Use this order in your MCP HTTP handler:
50
+ For MCP servers that use `StreamableHTTPServerTransport`, call `trackMCPCall` after connecting the transport and before the transport handles the request.
83
51
 
84
52
  ```ts
53
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
85
54
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
86
55
 
56
+ // ...
57
+
58
+ const mcpServer = new McpServer({
59
+ name: SERVER_NAME,
60
+ version: SERVER_VERSION,
61
+ })
62
+
87
63
  const transport = new StreamableHTTPServerTransport({
88
64
  sessionIdGenerator: undefined
89
65
  })
90
66
 
67
+ // ...
68
+
91
69
  await mcpServer.connect(transport)
70
+
92
71
  knownAgents.trackMCPCall(request, response, transport)
72
+
93
73
  await transport.handleRequest(request, response, parsedBody)
94
74
  ```
95
75
 
96
- Use `trackVisits` instead if the same transport instance handles concurrent requests.
76
+ ### Agentic Commerce Interactions (Optional)
97
77
 
98
- ### Batching
99
-
100
- For high-traffic websites, batch multiple visits together and send them periodically (e.g. every 30 seconds) using `trackVisits`. `trackVisits` can also batch MCP visits; if you do that, include the MCP fields in each `VisitRequest` and do not also call `trackMCPCall` for the same requests. Here's an Express example:
78
+ For REST implementations of ACP and UCP, include the response body in `trackPageviewOrRESTCall`.
101
79
 
102
80
  ```ts
103
- import express from "express"
104
- import { KnownAgents, type VisitRequest } from "@knownagents/sdk"
81
+ // For ACP calls ...
105
82
 
106
- const app = express()
107
- const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN")
108
- const visits: VisitRequest[] = []
109
-
110
- app.use((request, response, next) => {
111
- const created = new Date()
112
-
113
- response.once("finish", () => {
114
- visits.push({
115
- request_path: request.url,
116
- request_method: request.method,
117
- request_headers: request.headers,
118
- response_status_code: response.statusCode,
119
- response_headers: response.getHeaders(),
120
- response_duration_in_milliseconds: Date.now() - created.getTime(),
121
- created: created.toISOString()
122
- })
123
- })
124
-
125
- next()
83
+ knownAgents.trackPageviewOrRESTCall(request, response, {
84
+ acpResponseBody: acpResponseBody
126
85
  })
86
+ ```
127
87
 
128
- setInterval(() => {
129
- const batch = visits.splice(0)
88
+ ```ts
89
+ // For UCP calls ...
130
90
 
131
- if (batch.length > 0) {
132
- knownAgents.trackVisits(batch)
133
- }
134
- }, 30000)
91
+ knownAgents.trackPageviewOrRESTCall(request, response, {
92
+ ucpResponseBody: ucpResponseBody
93
+ })
135
94
  ```
136
95
 
96
+ `trackMCPCall` will track MCP implementations of ACP and UCP automatically.
97
+
137
98
  ### Test Your Integration
138
99
 
139
100
  - Open your project's settings page
140
101
  - Click **Send a Test Visit**
141
102
  - Click **Realtime**
142
103
 
143
- If your website is correctly connected, you should see visits from the Known Agent in the realtime timeline within a few seconds.
104
+ If your website is correctly connected, you should see visits from a test agent in the realtime timeline within a few seconds.
144
105
 
145
- ## How To Set Up Automatic Robots.txt ([Full Docs](https://knownagents.com/docs/robots-txt))
106
+ ## Set Up Automatic Robots.txt ([Automatic Robots.txt](https://knownagents.com/products/automatic-robots-txt))
146
107
 
147
- Protect sensitive content from unwanted access and scraping. Generate a continuously updating robots.txt that stays up to date with [all current and future bots](https://knownagents.com/agents) in the specified categories automatically.
108
+ Serve a robots.txt that continuously updates with rules for new [crawlers, scrapers, AI agents, and other bots](https://knownagents.com/agents) as they're discovered.
148
109
 
149
- Use the `generateRobotsTXT` function. Select which `AgentType`s you want to block, and a string specifying which URLs are disallowed (e.g. `"/"` to disallow all paths).
110
+ Use the `generateRobotsTXT` method. Select which `AgentType`s you want to block, and a string specifying which URLs are disallowed (e.g. `"/"` to disallow all paths).
150
111
 
151
112
  ```ts
152
113
  import { AgentType } from "@knownagents/sdk"
@@ -159,29 +120,19 @@ const robotsTxt = await knownAgents.generateRobotsTXT([
159
120
  ], "/")
160
121
  ```
161
122
 
162
- The return value is a plain text robots.txt string. Generate a `robotsTxt` periodically (e.g. once per day), then cache and serve it from your website's `/robots.txt` endpoint.
163
-
164
- ## How To Use Agent Identification ([Full Docs](https://knownagents.com/docs/identification))
123
+ The return value is a plain text robots.txt string. Generate a `robotsTxt` periodically (e.g. once per day, using a cron job). Then, serve it from your website's `/robots.txt` endpoint.
165
124
 
166
- Use the `identifyAgent` and `identifyAgents` functions to identify and verify [AI agents and bots](https://knownagents.com/agents) from network requests using Web Bot Auth (HTTP message signatures), IP matching, or other available methods. This can be useful for implementing access policies based on verified agent identity or enriching your own datasets.
125
+ ## Use the Agent Identification API ([Agent Identification API](https://knownagents.com/products/agent-identification-api))
167
126
 
168
- ### Identify a Single Request
127
+ Identify and verify [AI agents and other bots](https://knownagents.com/agents) in your own products, from incoming network requests. Authentication uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods. This API can be useful for implementing access policies based on verified agent identity or enriching your own datasets.
169
128
 
170
- Call `identifyAgent` with the incoming request.
129
+ Call `identifyAgent` to identify a single incoming request:
171
130
 
172
131
  ```ts
173
132
  const identification = await knownAgents.identifyAgent(request)
174
-
175
- if (identification.result === "verified") {
176
- handleVerifiedAgent(identification)
177
- } else if (identification.result === "verification_failed") {
178
- handleFailedVerification(identification)
179
- }
180
133
  ```
181
134
 
182
- ### Identify Multiple Requests
183
-
184
- Use `identifyAgents` to identify multiple requests at once:
135
+ Call `identifyAgents` to identify multiple requests at once:
185
136
 
186
137
  ```ts
187
138
  const identifications = await knownAgents.identifyAgents([
@@ -196,19 +147,19 @@ const identifications = await knownAgents.identifyAgents([
196
147
  ])
197
148
  ```
198
149
 
199
- The functions return an object (or array of objects) with the following fields:
150
+ These methods return an object (or array of objects) with the following fields:
200
151
 
201
152
  - `id`: The identifier from the request (if provided)
202
153
  - `result`: The identification result:
203
- - `"verified"`: The agent is identified and verified
204
- - `"verification_failed"`: The agent was identified but could not be verified
205
- - `"unknown_agent"`: The agent is not in our database
206
- - `"not_verifiable"`: The agent cannot be verified (no verification method available)
154
+ - `"verified"`: The agent was identified and verified
155
+ - `"verification_failed"`: The agent was identified but failed verification (it was spoofed)
156
+ - `"not_verifiable"`: The agent was identified but could not be verified (no method available)
157
+ - `"unknown_agent"`: Not an agent, or the agent is not in the database
207
158
  - `agent_id`: The unique ID of the agent (if identified)
208
- - `agent_token`: The name of the agent (e.g. `"Googlebot"`) (if identified)
159
+ - `agent_token`: The name of the agent (e.g. `"Claude-User"`) (if identified)
209
160
  - `agent_url`: The documentation URL of the agent (if identified)
210
- - `agent_type_name`: The type of agent (e.g. `"AI Agent"`) (if identified)
211
- - `operator_name`: The company behind the agent (e.g. `"Google"`) (if identified)
161
+ - `agent_type_name`: The type of agent (e.g. `"AI Assistant"`) (if identified)
162
+ - `operator_name`: The company operating the agent (e.g. `"Anthropic"`) (if identified)
212
163
 
213
164
  ## Requirements
214
165
 
@@ -1,21 +1,27 @@
1
1
  import type { IncomingMessage, ServerResponse } from "http";
2
2
  import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
- import type { AgentType, IdentificationRequest, IdentificationResult, TrackPageviewOrRESTCallOptions, VisitRequest } from "./types";
3
+ import type { AgentType, IdentificationRequest, IdentificationResult, KnownAgentsOptions, TrackPageviewOrRESTCallOptions, VisitRequest } from "./types";
4
4
  /**
5
5
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
6
6
  */
7
7
  export declare class KnownAgents {
8
8
  private readonly accessToken;
9
+ private readonly flushIntervalInMilliseconds;
10
+ private readonly flushQueueSize;
11
+ private readonly visitQueue;
12
+ private flushTimeout;
13
+ private pendingFlushPromise;
9
14
  /**
10
15
  * Creates a new instance of the Known Agents client.
11
16
  *
12
17
  * @param accessToken - Your project's access token.
18
+ * @param options - Optional batching configuration.
13
19
  */
14
- constructor(accessToken: string);
20
+ constructor(accessToken: string, options?: KnownAgentsOptions);
15
21
  /**
16
22
  * Tracks a single non-MCP pageview or REST API call for analytics.
17
- * The visit is sent after the response finishes so status, headers, and duration are included.
18
- * This method is non-blocking and handles errors internally.
23
+ * The visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
24
+ * This method is non-blocking and handles upload errors internally.
19
25
  *
20
26
  * @param request - The incoming Node.js HTTP request.
21
27
  * @param response - The outgoing Node.js HTTP response.
@@ -26,7 +32,7 @@ export declare class KnownAgents {
26
32
  * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
27
33
  * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
28
34
  * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
29
- * This method is non-blocking and handles errors internally.
35
+ * This method is non-blocking and handles upload errors internally.
30
36
  *
31
37
  * @param request - The incoming Node.js HTTP request.
32
38
  * @param response - The outgoing Node.js HTTP response.
@@ -34,13 +40,20 @@ export declare class KnownAgents {
34
40
  */
35
41
  trackMCPCall(request: IncomingMessage, response: ServerResponse, transport: StreamableHTTPServerTransport): void;
36
42
  /**
37
- * Tracks multiple visits in a batch for analytics.
38
- * This method is non-blocking and handles errors internally.
39
- * Recommended for high-traffic websites - batch visits and send periodically (e.g. every 30 seconds).
43
+ * Adds multiple visits to the visit event queue for analytics.
44
+ * This method is non-blocking and handles upload errors internally.
45
+ * All visit events are uploaded together when the queue size reaches the configured threshold or the flush interval elapses.
40
46
  *
41
47
  * @param requests - An array of visits.
42
48
  */
43
49
  trackVisits(requests: VisitRequest[]): void;
50
+ /**
51
+ * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
52
+ * This method handles upload errors internally.
53
+ *
54
+ * @returns A promise that resolves after all pending upload attempts finish.
55
+ */
56
+ flush(): Promise<void>;
44
57
  /**
45
58
  * Generates a robots.txt file that disallows the specified agent types.
46
59
  * The generated file stays up-to-date with all current and future agents in the specified categories.
@@ -70,5 +83,7 @@ export declare class KnownAgents {
70
83
  * @throws {Error} If the API call fails or returns a non-200 status.
71
84
  */
72
85
  identifyAgents(requests: IdentificationRequest[]): Promise<IdentificationResult[]>;
86
+ private scheduleFlush;
87
+ private sendVisits;
73
88
  }
74
89
  //# sourceMappingURL=KnownAgents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KnownAgents.d.ts","sourceRoot":"","sources":["../src/KnownAgents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAavG,OAAO,KAAK,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,8BAA8B,EAC9B,YAAY,EACf,MAAM,SAAS,CAAA;AAqChB;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC;;;;OAIG;gBACS,WAAW,EAAE,MAAM;IAI/B;;;;;;;;OAQG;IACH,uBAAuB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,8BAA8B,GAAG,IAAI;IAsC3H;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,6BAA6B,GAAG,IAAI;IAuEhH;;;;;;OAMG;IACH,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IA6B3C;;;;;;;;;OASG;IACG,iBAAiB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBzF;;;;;;;OAOG;IACG,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAmB5E;;;;;;;OAOG;IACG,cAAc,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;CAsB3F"}
1
+ {"version":3,"file":"KnownAgents.d.ts","sourceRoot":"","sources":["../src/KnownAgents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAGvG,OAAO,KAAK,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,8BAA8B,EAC9B,YAAY,EACf,MAAM,SAAS,CAAA;AAKhB;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAQ;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,mBAAmB,CAAmC;IAE9D;;;;;OAKG;gBACS,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAMjE;;;;;;;;OAQG;IACH,uBAAuB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,8BAA8B,GAAG,IAAI;IAoC3H;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,6BAA6B,GAAG,IAAI;IAoEhH;;;;;;OAMG;IACH,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IA2B3C;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB;;;;;;;;;OASG;IACG,iBAAiB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBzF;;;;;;;OAOG;IACG,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAmB5E;;;;;;;OAOG;IACG,cAAc,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAsBxF,OAAO,CAAC,aAAa;YAaP,UAAU;CAmB3B"}
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KnownAgents = void 0;
4
- const commerce_1 = require("./commerce");
5
4
  const headers_1 = require("./headers");
6
5
  const paths_1 = require("./paths");
6
+ const DEFAULT_FLUSH_INTERVAL_IN_MILLISECONDS = 10000;
7
+ const DEFAULT_FLUSH_QUEUE_SIZE = 1;
7
8
  /**
8
9
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
9
10
  */
@@ -12,14 +13,19 @@ class KnownAgents {
12
13
  * Creates a new instance of the Known Agents client.
13
14
  *
14
15
  * @param accessToken - Your project's access token.
16
+ * @param options - Optional batching configuration.
15
17
  */
16
- constructor(accessToken) {
18
+ constructor(accessToken, options = {}) {
19
+ this.visitQueue = [];
20
+ this.pendingFlushPromise = Promise.resolve();
17
21
  this.accessToken = accessToken;
22
+ this.flushIntervalInMilliseconds = options.flushIntervalInMilliseconds ?? DEFAULT_FLUSH_INTERVAL_IN_MILLISECONDS;
23
+ this.flushQueueSize = options.flushQueueSize ?? DEFAULT_FLUSH_QUEUE_SIZE;
18
24
  }
19
25
  /**
20
26
  * Tracks a single non-MCP pageview or REST API call for analytics.
21
- * The visit is sent after the response finishes so status, headers, and duration are included.
22
- * This method is non-blocking and handles errors internally.
27
+ * The visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
28
+ * This method is non-blocking and handles upload errors internally.
23
29
  *
24
30
  * @param request - The incoming Node.js HTTP request.
25
31
  * @param response - The outgoing Node.js HTTP response.
@@ -42,10 +48,8 @@ class KnownAgents {
42
48
  response_status_code: response?.statusCode,
43
49
  response_headers: response.getHeaders(),
44
50
  response_duration_in_milliseconds: responseDurationInMilliseconds,
45
- acp_response_currency: options?.restACPResponseBody ? (0, commerce_1.getRESTACPCurrencyIfPossible)(options?.restACPResponseBody) : undefined,
46
- acp_response_total_amount: options?.restACPResponseBody ? (0, commerce_1.getRESTACPTotalAmountIfPossible)(options?.restACPResponseBody) : undefined,
47
- ucp_response_currency: options?.restUCPResponseBody ? (0, commerce_1.getRESTUCPCurrencyIfPossible)(options?.restUCPResponseBody) : undefined,
48
- ucp_response_total_amount: options?.restUCPResponseBody ? (0, commerce_1.getRESTUCPTotalAmountIfPossible)(options?.restUCPResponseBody) : undefined,
51
+ acp_response_body: options?.acpResponseBody,
52
+ ucp_response_body: options?.ucpResponseBody,
49
53
  created: created.toISOString()
50
54
  };
51
55
  this.trackVisits([visitRequest]);
@@ -61,17 +65,16 @@ class KnownAgents {
61
65
  * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
62
66
  * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
63
67
  * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
64
- * This method is non-blocking and handles errors internally.
68
+ * This method is non-blocking and handles upload errors internally.
65
69
  *
66
70
  * @param request - The incoming Node.js HTTP request.
67
71
  * @param response - The outgoing Node.js HTTP response.
68
72
  * @param transport - The MCP streamable HTTP transport handling the request.
69
73
  */
70
74
  trackMCPCall(request, response, transport) {
71
- const isMCPCall = (0, headers_1.getIsMCPCall)(request.headers);
72
75
  const path = request.url;
73
76
  const method = request.method;
74
- if (!isMCPCall || !path || (0, paths_1.getIsPathExcluded)(path) || !method) {
77
+ if (!path || (0, paths_1.getIsPathExcluded)(path) || !method) {
75
78
  return;
76
79
  }
77
80
  const created = new Date();
@@ -111,10 +114,8 @@ class KnownAgents {
111
114
  mcp_response_result_is_error: mcpResponseBody.result?.isError,
112
115
  mcp_response_error_code: mcpResponseBody.error?.code,
113
116
  mcp_response_error_message: mcpResponseBody.error?.message,
114
- acp_response_currency: (0, commerce_1.getMCPACPCurrencyIfPossible)(request, mcpResponseBody),
115
- acp_response_total_amount: (0, commerce_1.getMCPACPTotalAmountIfPossible)(request, mcpResponseBody),
116
- ucp_response_currency: (0, commerce_1.getMCPUCPCurrencyIfPossible)(request, mcpResponseBody),
117
- ucp_response_total_amount: (0, commerce_1.getMCPUCPTotalAmountIfPossible)(request, mcpResponseBody),
117
+ acp_response_body: (0, headers_1.getIsACPCall)(request.headers) ? mcpResponseBody.result?.structuredContent ?? mcpResponseBody.result : undefined,
118
+ ucp_response_body: (0, headers_1.getIsUCPCall)(request.headers) ? mcpResponseBody.result?.structuredContent ?? mcpResponseBody.result : undefined,
118
119
  created: created.toISOString()
119
120
  };
120
121
  this.trackVisits([visitRequest]);
@@ -127,9 +128,9 @@ class KnownAgents {
127
128
  }
128
129
  }
129
130
  /**
130
- * Tracks multiple visits in a batch for analytics.
131
- * This method is non-blocking and handles errors internally.
132
- * Recommended for high-traffic websites - batch visits and send periodically (e.g. every 30 seconds).
131
+ * Adds multiple visits to the visit event queue for analytics.
132
+ * This method is non-blocking and handles upload errors internally.
133
+ * All visit events are uploaded together when the queue size reaches the configured threshold or the flush interval elapses.
133
134
  *
134
135
  * @param requests - An array of visits.
135
136
  */
@@ -148,16 +149,32 @@ class KnownAgents {
148
149
  if (filteredRequests.length === 0) {
149
150
  return;
150
151
  }
151
- fetch("https://api.knownagents.com/visits", {
152
- method: "POST",
153
- headers: {
154
- "Authorization": `Bearer ${this.accessToken}`,
155
- "Content-Type": "application/json"
156
- },
157
- body: JSON.stringify(filteredRequests)
158
- }).catch(error => {
159
- console.error(`Known Agents failed to track visits: ${error.message}`);
152
+ this.visitQueue.push(...filteredRequests);
153
+ if (this.visitQueue.length >= this.flushQueueSize) {
154
+ void this.flush();
155
+ return;
156
+ }
157
+ this.scheduleFlush();
158
+ }
159
+ /**
160
+ * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
161
+ * This method handles upload errors internally.
162
+ *
163
+ * @returns A promise that resolves after all pending upload attempts finish.
164
+ */
165
+ flush() {
166
+ if (this.flushTimeout) {
167
+ clearTimeout(this.flushTimeout);
168
+ this.flushTimeout = undefined;
169
+ }
170
+ const requests = this.visitQueue.splice(0);
171
+ if (requests.length === 0) {
172
+ return this.pendingFlushPromise;
173
+ }
174
+ this.pendingFlushPromise = this.pendingFlushPromise.then(() => {
175
+ return this.sendVisits(requests);
160
176
  });
177
+ return this.pendingFlushPromise;
161
178
  }
162
179
  /**
163
180
  * Generates a robots.txt file that disallows the specified agent types.
@@ -243,5 +260,34 @@ class KnownAgents {
243
260
  throw new Error(`Known Agents failed to identify agents: ${response.status} ${response.statusText}`);
244
261
  }
245
262
  }
263
+ scheduleFlush() {
264
+ if (this.flushTimeout) {
265
+ return;
266
+ }
267
+ this.flushTimeout = setTimeout(() => {
268
+ this.flushTimeout = undefined;
269
+ void this.flush();
270
+ }, this.flushIntervalInMilliseconds);
271
+ this.flushTimeout.unref();
272
+ }
273
+ async sendVisits(requests) {
274
+ try {
275
+ const response = await fetch("https://api.knownagents.com/visits", {
276
+ method: "POST",
277
+ headers: {
278
+ "Authorization": `Bearer ${this.accessToken}`,
279
+ "Content-Type": "application/json"
280
+ },
281
+ body: JSON.stringify(requests)
282
+ });
283
+ if (!response.ok) {
284
+ throw new Error(`${response.status} ${response.statusText}`);
285
+ }
286
+ }
287
+ catch (error) {
288
+ const message = error instanceof Error ? error.message : String(error);
289
+ console.error(`Known Agents failed to track visits: ${message}`);
290
+ }
291
+ }
246
292
  }
247
293
  exports.KnownAgents = KnownAgents;
package/dist/headers.d.ts CHANGED
@@ -2,5 +2,5 @@ import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
2
2
  export declare function getFilteredHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers;
3
3
  export declare function getIsMCPCall(headers: IncomingHttpHeaders): boolean;
4
4
  export declare function getIsUCPCall(headers: IncomingHttpHeaders): boolean;
5
- export declare function getIsProbablyACPCall(headers: IncomingHttpHeaders): boolean;
5
+ export declare function getIsACPCall(headers: IncomingHttpHeaders): boolean;
6
6
  //# sourceMappingURL=headers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AA2BpE,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUvH;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAI1E"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AA2BpE,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUvH;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE"}
package/dist/headers.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getFilteredHeaders = getFilteredHeaders;
4
4
  exports.getIsMCPCall = getIsMCPCall;
5
5
  exports.getIsUCPCall = getIsUCPCall;
6
- exports.getIsProbablyACPCall = getIsProbablyACPCall;
6
+ exports.getIsACPCall = getIsACPCall;
7
7
  const filteredHeaderNames = [
8
8
  "authorization",
9
9
  "proxy-authorization",
@@ -47,8 +47,8 @@ function getIsUCPCall(headers) {
47
47
  return key.toLowerCase() === "ucp-agent";
48
48
  });
49
49
  }
50
- function getIsProbablyACPCall(headers) {
51
- return Object.keys(headers).some((key) => {
50
+ function getIsACPCall(headers) {
51
+ return !getIsUCPCall(headers) && Object.keys(headers).some((key) => {
52
52
  return key.toLowerCase() === "api-version";
53
53
  });
54
54
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { KnownAgents } from "./KnownAgents";
2
- export { AgentType, type IdentificationRequest, type IdentificationResult, type RESTACPResponseBody, type RESTUCPResponseBody, type TrackPageviewOrRESTCallOptions, type VisitRequest } from "./types";
2
+ export { AgentType, type IdentificationRequest, type IdentificationResult, type KnownAgentsOptions, type TrackPageviewOrRESTCallOptions, type VisitRequest } from "./types";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACH,SAAS,EACT,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,YAAY,EACpB,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACH,SAAS,EACT,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,8BAA8B,EACnC,KAAK,YAAY,EACpB,MAAM,SAAS,CAAA"}
package/dist/types.d.ts CHANGED
@@ -1,40 +1,25 @@
1
1
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
2
2
  /**
3
- * ACP response details from a REST API call.
3
+ * Batching configuration for the Known Agents client.
4
4
  */
5
- export interface RESTACPResponseBody {
6
- /** The response currency, usually an ISO 4217 code. */
7
- currency?: string;
8
- /** ACP totals returned by the REST endpoint. The "total" item is sent to Known Agents. */
9
- totals?: {
10
- type?: string;
11
- amount?: number;
12
- }[];
13
- }
14
- /**
15
- * UCP response details from a REST API call.
16
- */
17
- export interface RESTUCPResponseBody {
18
- /** The response currency, usually an ISO 4217 code. */
19
- currency?: string;
20
- /** UCP totals returned by the REST endpoint. The "total" item is sent to Known Agents. */
21
- totals?: {
22
- type?: string;
23
- amount?: number;
24
- }[];
5
+ export interface KnownAgentsOptions {
6
+ /** The maximum time a partial visit event queue waits before upload. Defaults to 10,000 milliseconds. */
7
+ flushIntervalInMilliseconds?: number;
8
+ /** The number of visit events in the queue that triggers an upload. Defaults to 1. */
9
+ flushQueueSize?: number;
25
10
  }
26
11
  /**
27
12
  * Additional metadata to include when tracking a pageview or REST API call.
28
13
  */
29
14
  export interface TrackPageviewOrRESTCallOptions {
30
- /** ACP response body from the REST endpoint. */
31
- restACPResponseBody?: RESTACPResponseBody;
32
- /** UCP response body from the REST endpoint. */
33
- restUCPResponseBody?: RESTUCPResponseBody;
15
+ /** The ACP response body. */
16
+ acpResponseBody?: Record<string, unknown>;
17
+ /** The UCP response body. */
18
+ ucpResponseBody?: Record<string, unknown>;
34
19
  }
35
20
  /**
36
21
  * A normalized visit payload for the Known Agents API.
37
- * Use this with `trackVisits()` when batching requests yourself.
22
+ * Use this with `trackVisits()` to manually add visit events to the queue.
38
23
  */
39
24
  export interface VisitRequest {
40
25
  /** The URL path and query string of the request (e.g. "/about?foo=bar") */
@@ -63,14 +48,10 @@ export interface VisitRequest {
63
48
  mcp_response_error_code?: number;
64
49
  /** The MCP response error message */
65
50
  mcp_response_error_message?: string;
66
- /** The ACP response currency */
67
- acp_response_currency?: string;
68
- /** The ACP response "total" amount */
69
- acp_response_total_amount?: number;
70
- /** The UCP response currency */
71
- ucp_response_currency?: string;
72
- /** The UCP response "total" amount */
73
- ucp_response_total_amount?: number;
51
+ /** The ACP response body */
52
+ acp_response_body?: Record<string, unknown>;
53
+ /** The UCP response body */
54
+ ucp_response_body?: Record<string, unknown>;
74
55
  /** The timestamp when the visit occurred (ISO 8601 format) */
75
56
  created?: string;
76
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AAEpE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0FAA0F;IAC1F,MAAM,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;KAClB,EAAE,CAAA;CACN;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0FAA0F;IAC1F,MAAM,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;KAClB,EAAE,CAAA;CACN;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAA;IACpB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,eAAe,EAAE,mBAAmB,CAAA;IACpC,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,mBAAmB,CAAA;IACtC,4CAA4C;IAC5C,iCAAiC,CAAC,EAAE,MAAM,CAAA;IAC1C,sCAAsC;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,kCAAkC;IAClC,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,qCAAqC;IACrC,+BAA+B,CAAC,EAAE,MAAM,CAAA;IACxC,kDAAkD;IAClD,4BAA4B,CAAC,EAAE,OAAO,CAAA;IACtC,kCAAkC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,qCAAqC;IACrC,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,gCAAgC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,sCAAsC;IACtC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,gCAAgC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,sCAAsC;IACtC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,4FAA4F;IAC5F,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,eAAe,EAAE,mBAAmB,CAAA;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,oDAAoD;IACpD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;;;;OAMG;IACH,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,eAAe,GAAG,gBAAgB,CAAA;IAC/E,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,oBAAY,SAAS;IACjB,OAAO,aAAa;IACpB,WAAW,iBAAiB;IAC5B,aAAa,oBAAoB;IACjC,cAAc,qBAAqB;IACnC,aAAa,oBAAoB;IACjC,eAAe,sBAAsB;IACrC,QAAQ,aAAa;IACrB,cAAc,oBAAoB;IAClC,eAAe,qBAAqB;IACpC,OAAO,YAAY;IACnB,oBAAoB,0BAA0B;IAC9C,OAAO,YAAY;IACnB,UAAU,gBAAgB;IAC1B,mBAAmB,0BAA0B;IAC7C,eAAe,qBAAqB;IACpC,mBAAmB,0BAA0B;CAChD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AAEpE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,yGAAyG;IACzG,2BAA2B,CAAC,EAAE,MAAM,CAAA;IACpC,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C,6BAA6B;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACzC,6BAA6B;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAA;IACpB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,eAAe,EAAE,mBAAmB,CAAA;IACpC,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,mBAAmB,CAAA;IACtC,4CAA4C;IAC5C,iCAAiC,CAAC,EAAE,MAAM,CAAA;IAC1C,sCAAsC;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,kCAAkC;IAClC,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,qCAAqC;IACrC,+BAA+B,CAAC,EAAE,MAAM,CAAA;IACxC,kDAAkD;IAClD,4BAA4B,CAAC,EAAE,OAAO,CAAA;IACtC,kCAAkC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,qCAAqC;IACrC,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,4BAA4B;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC3C,4BAA4B;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC3C,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,4FAA4F;IAC5F,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,eAAe,EAAE,mBAAmB,CAAA;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,oDAAoD;IACpD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;;;;OAMG;IACH,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,eAAe,GAAG,gBAAgB,CAAA;IAC/E,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,oBAAY,SAAS;IACjB,OAAO,aAAa;IACpB,WAAW,iBAAiB;IAC5B,aAAa,oBAAoB;IACjC,cAAc,qBAAqB;IACnC,aAAa,oBAAoB;IACjC,eAAe,sBAAsB;IACrC,QAAQ,aAAa;IACrB,cAAc,oBAAoB;IAClC,eAAe,qBAAqB;IACpC,OAAO,YAAY;IACnB,oBAAoB,0BAA0B;IAC9C,OAAO,YAAY;IACnB,UAAU,gBAAgB;IAC1B,mBAAmB,0BAA0B;IAC7C,eAAe,qBAAqB;IACpC,mBAAmB,0BAA0B;CAChD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@knownagents/sdk",
3
- "version": "2.0.0",
4
- "description": "The official Node.js SDK for Known Agents: AI agent analytics (bot traffic), LLM referral tracking, automatic robots.txt, agent verification",
3
+ "version": "2.2.0",
4
+ "description": "The official Node.js SDK for Known Agents: AI agent analytics (bot traffic), LLM referral tracking, automatic robots.txt, agent identification",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,7 +32,7 @@
32
32
  "web scraping",
33
33
  "ai blocking",
34
34
  "bot blocking",
35
- "agent verification",
35
+ "agent identification",
36
36
  "bot verification",
37
37
  "verify ai agents",
38
38
  "web bot auth",
@@ -1,79 +1,46 @@
1
1
  import type { IncomingMessage, ServerResponse } from "http"
2
2
  import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
3
- import {
4
- getMCPACPCurrencyIfPossible,
5
- getMCPACPTotalAmountIfPossible,
6
- getMCPUCPCurrencyIfPossible,
7
- getMCPUCPTotalAmountIfPossible,
8
- getRESTACPCurrencyIfPossible,
9
- getRESTACPTotalAmountIfPossible,
10
- getRESTUCPCurrencyIfPossible,
11
- getRESTUCPTotalAmountIfPossible
12
- } from "./commerce"
13
- import { getFilteredHeaders, getIsMCPCall } from "./headers"
3
+ import { getFilteredHeaders, getIsACPCall, getIsMCPCall, getIsUCPCall } from "./headers"
14
4
  import { getIsPathExcluded } from "./paths"
15
5
  import type {
16
6
  AgentType,
17
7
  IdentificationRequest,
18
8
  IdentificationResult,
9
+ KnownAgentsOptions,
19
10
  TrackPageviewOrRESTCallOptions,
20
11
  VisitRequest
21
12
  } from "./types"
22
13
 
23
- interface MCPRequestBody {
24
- jsonrpc: string
25
- method: string
26
- params?: {
27
- name?: string
28
- clientInfo?: {
29
- name?: string
30
- version?: string
31
- }
32
- }
33
- }
34
-
35
- interface MCPResponseBody {
36
- jsonrpc: string
37
- result?: {
38
- isError?: boolean
39
- currency?: string
40
- totals?: {
41
- type?: string
42
- amount?: number
43
- }[]
44
- structuredContent?: {
45
- currency?: string
46
- totals?: {
47
- type?: string
48
- amount?: number
49
- }[]
50
- }
51
- }
52
- error?: {
53
- code?: number
54
- message?: string
55
- }
56
- }
14
+ const DEFAULT_FLUSH_INTERVAL_IN_MILLISECONDS = 10000
15
+ const DEFAULT_FLUSH_QUEUE_SIZE = 1
57
16
 
58
17
  /**
59
18
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
60
19
  */
61
20
  export class KnownAgents {
62
21
  private readonly accessToken: string
22
+ private readonly flushIntervalInMilliseconds: number
23
+ private readonly flushQueueSize: number
24
+ private readonly visitQueue: VisitRequest[] = []
25
+ private flushTimeout: NodeJS.Timeout | undefined
26
+ private pendingFlushPromise: Promise<void> = Promise.resolve()
63
27
 
64
28
  /**
65
29
  * Creates a new instance of the Known Agents client.
66
- *
30
+ *
67
31
  * @param accessToken - Your project's access token.
32
+ * @param options - Optional batching configuration.
68
33
  */
69
- constructor(accessToken: string) {
34
+ constructor(accessToken: string, options: KnownAgentsOptions = {}) {
70
35
  this.accessToken = accessToken
36
+ this.flushIntervalInMilliseconds = options.flushIntervalInMilliseconds ?? DEFAULT_FLUSH_INTERVAL_IN_MILLISECONDS
37
+ this.flushQueueSize = options.flushQueueSize ?? DEFAULT_FLUSH_QUEUE_SIZE
71
38
  }
72
39
 
73
40
  /**
74
41
  * Tracks a single non-MCP pageview or REST API call for analytics.
75
- * The visit is sent after the response finishes so status, headers, and duration are included.
76
- * This method is non-blocking and handles errors internally.
42
+ * The visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
43
+ * This method is non-blocking and handles upload errors internally.
77
44
  *
78
45
  * @param request - The incoming Node.js HTTP request.
79
46
  * @param response - The outgoing Node.js HTTP response.
@@ -100,10 +67,8 @@ export class KnownAgents {
100
67
  response_status_code: response?.statusCode,
101
68
  response_headers: response.getHeaders(),
102
69
  response_duration_in_milliseconds: responseDurationInMilliseconds,
103
- acp_response_currency: options?.restACPResponseBody ? getRESTACPCurrencyIfPossible(options?.restACPResponseBody) : undefined,
104
- acp_response_total_amount: options?.restACPResponseBody ? getRESTACPTotalAmountIfPossible(options?.restACPResponseBody) : undefined,
105
- ucp_response_currency: options?.restUCPResponseBody ? getRESTUCPCurrencyIfPossible(options?.restUCPResponseBody) : undefined,
106
- ucp_response_total_amount: options?.restUCPResponseBody ? getRESTUCPTotalAmountIfPossible(options?.restUCPResponseBody) : undefined,
70
+ acp_response_body: options?.acpResponseBody,
71
+ ucp_response_body: options?.ucpResponseBody,
107
72
  created: created.toISOString()
108
73
  }
109
74
 
@@ -121,35 +86,34 @@ export class KnownAgents {
121
86
  * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
122
87
  * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
123
88
  * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
124
- * This method is non-blocking and handles errors internally.
89
+ * This method is non-blocking and handles upload errors internally.
125
90
  *
126
91
  * @param request - The incoming Node.js HTTP request.
127
92
  * @param response - The outgoing Node.js HTTP response.
128
93
  * @param transport - The MCP streamable HTTP transport handling the request.
129
94
  */
130
95
  trackMCPCall(request: IncomingMessage, response: ServerResponse, transport: StreamableHTTPServerTransport): void {
131
- const isMCPCall = getIsMCPCall(request.headers)
132
96
  const path = request.url
133
97
  const method = request.method
134
98
 
135
- if (!isMCPCall || !path || getIsPathExcluded(path) || !method) {
99
+ if (!path || getIsPathExcluded(path) || !method) {
136
100
  return
137
101
  }
138
102
 
139
103
  const created = new Date()
140
104
 
141
- let mcpRequestBody: MCPRequestBody | undefined
142
- let mcpResponseBody: MCPResponseBody | undefined
105
+ let mcpRequestBody: any
106
+ let mcpResponseBody: any
143
107
 
144
108
  const originalTransportOnMessage = transport.onmessage
145
109
  transport.onmessage = (message, extra): void => {
146
- mcpRequestBody = message as MCPRequestBody
110
+ mcpRequestBody = message
147
111
  originalTransportOnMessage?.(message, extra)
148
112
  }
149
-
113
+
150
114
  const originalTransportSend = transport.send
151
115
  transport.send = async (message, options): Promise<void> => {
152
- mcpResponseBody = message as MCPResponseBody
116
+ mcpResponseBody = message
153
117
  await originalTransportSend.call(transport, message, options)
154
118
  }
155
119
 
@@ -181,10 +145,8 @@ export class KnownAgents {
181
145
  mcp_response_result_is_error: mcpResponseBody.result?.isError,
182
146
  mcp_response_error_code: mcpResponseBody.error?.code,
183
147
  mcp_response_error_message: mcpResponseBody.error?.message,
184
- acp_response_currency: getMCPACPCurrencyIfPossible(request, mcpResponseBody),
185
- acp_response_total_amount: getMCPACPTotalAmountIfPossible(request, mcpResponseBody),
186
- ucp_response_currency: getMCPUCPCurrencyIfPossible(request, mcpResponseBody),
187
- ucp_response_total_amount: getMCPUCPTotalAmountIfPossible(request, mcpResponseBody),
148
+ acp_response_body: getIsACPCall(request.headers) ? mcpResponseBody.result?.structuredContent ?? mcpResponseBody.result : undefined,
149
+ ucp_response_body: getIsUCPCall(request.headers) ? mcpResponseBody.result?.structuredContent ?? mcpResponseBody.result : undefined,
188
150
  created: created.toISOString()
189
151
  }
190
152
 
@@ -197,11 +159,11 @@ export class KnownAgents {
197
159
  response.once("finish", sendVisit)
198
160
  }
199
161
  }
200
-
162
+
201
163
  /**
202
- * Tracks multiple visits in a batch for analytics.
203
- * This method is non-blocking and handles errors internally.
204
- * Recommended for high-traffic websites - batch visits and send periodically (e.g. every 30 seconds).
164
+ * Adds multiple visits to the visit event queue for analytics.
165
+ * This method is non-blocking and handles upload errors internally.
166
+ * All visit events are uploaded together when the queue size reaches the configured threshold or the flush interval elapses.
205
167
  *
206
168
  * @param requests - An array of visits.
207
169
  */
@@ -222,18 +184,41 @@ export class KnownAgents {
222
184
  return
223
185
  }
224
186
 
225
- fetch("https://api.knownagents.com/visits", {
226
- method: "POST",
227
- headers: {
228
- "Authorization": `Bearer ${this.accessToken}`,
229
- "Content-Type": "application/json"
230
- },
231
- body: JSON.stringify(filteredRequests)
232
- }).catch(error => {
233
- console.error(`Known Agents failed to track visits: ${error.message}`)
187
+ this.visitQueue.push(...filteredRequests)
188
+
189
+ if (this.visitQueue.length >= this.flushQueueSize) {
190
+ void this.flush()
191
+ return
192
+ }
193
+
194
+ this.scheduleFlush()
195
+ }
196
+
197
+ /**
198
+ * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
199
+ * This method handles upload errors internally.
200
+ *
201
+ * @returns A promise that resolves after all pending upload attempts finish.
202
+ */
203
+ flush(): Promise<void> {
204
+ if (this.flushTimeout) {
205
+ clearTimeout(this.flushTimeout)
206
+ this.flushTimeout = undefined
207
+ }
208
+
209
+ const requests = this.visitQueue.splice(0)
210
+
211
+ if (requests.length === 0) {
212
+ return this.pendingFlushPromise
213
+ }
214
+
215
+ this.pendingFlushPromise = this.pendingFlushPromise.then(() => {
216
+ return this.sendVisits(requests)
234
217
  })
218
+
219
+ return this.pendingFlushPromise
235
220
  }
236
-
221
+
237
222
  /**
238
223
  * Generates a robots.txt file that disallows the specified agent types.
239
224
  * The generated file stays up-to-date with all current and future agents in the specified categories.
@@ -263,7 +248,7 @@ export class KnownAgents {
263
248
  throw new Error(`Known Agents failed to generate robots.txt: ${response.status} ${response.statusText}`)
264
249
  }
265
250
  }
266
-
251
+
267
252
  /**
268
253
  * Identifies and verifies a single agent from an HTTP request.
269
254
  * Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
@@ -321,4 +306,36 @@ export class KnownAgents {
321
306
  }
322
307
  }
323
308
 
309
+ private scheduleFlush(): void {
310
+ if (this.flushTimeout) {
311
+ return
312
+ }
313
+
314
+ this.flushTimeout = setTimeout(() => {
315
+ this.flushTimeout = undefined
316
+ void this.flush()
317
+ }, this.flushIntervalInMilliseconds)
318
+
319
+ this.flushTimeout.unref()
320
+ }
321
+
322
+ private async sendVisits(requests: VisitRequest[]): Promise<void> {
323
+ try {
324
+ const response = await fetch("https://api.knownagents.com/visits", {
325
+ method: "POST",
326
+ headers: {
327
+ "Authorization": `Bearer ${this.accessToken}`,
328
+ "Content-Type": "application/json"
329
+ },
330
+ body: JSON.stringify(requests)
331
+ })
332
+
333
+ if (!response.ok) {
334
+ throw new Error(`${response.status} ${response.statusText}`)
335
+ }
336
+ } catch (error) {
337
+ const message = error instanceof Error ? error.message : String(error)
338
+ console.error(`Known Agents failed to track visits: ${message}`)
339
+ }
340
+ }
324
341
  }
package/src/headers.ts CHANGED
@@ -49,8 +49,8 @@ export function getIsUCPCall(headers: IncomingHttpHeaders): boolean {
49
49
  })
50
50
  }
51
51
 
52
- export function getIsProbablyACPCall(headers: IncomingHttpHeaders): boolean {
53
- return Object.keys(headers).some((key) => {
52
+ export function getIsACPCall(headers: IncomingHttpHeaders): boolean {
53
+ return !getIsUCPCall(headers) && Object.keys(headers).some((key) => {
54
54
  return key.toLowerCase() === "api-version"
55
55
  })
56
56
  }
package/src/index.ts CHANGED
@@ -3,8 +3,7 @@ export {
3
3
  AgentType,
4
4
  type IdentificationRequest,
5
5
  type IdentificationResult,
6
- type RESTACPResponseBody,
7
- type RESTUCPResponseBody,
6
+ type KnownAgentsOptions,
8
7
  type TrackPageviewOrRESTCallOptions,
9
8
  type VisitRequest
10
9
  } from "./types"
package/src/types.ts CHANGED
@@ -1,44 +1,28 @@
1
1
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http"
2
2
 
3
3
  /**
4
- * ACP response details from a REST API call.
4
+ * Batching configuration for the Known Agents client.
5
5
  */
6
- export interface RESTACPResponseBody {
7
- /** The response currency, usually an ISO 4217 code. */
8
- currency?: string
9
- /** ACP totals returned by the REST endpoint. The "total" item is sent to Known Agents. */
10
- totals?: {
11
- type?: string
12
- amount?: number
13
- }[]
14
- }
15
-
16
- /**
17
- * UCP response details from a REST API call.
18
- */
19
- export interface RESTUCPResponseBody {
20
- /** The response currency, usually an ISO 4217 code. */
21
- currency?: string
22
- /** UCP totals returned by the REST endpoint. The "total" item is sent to Known Agents. */
23
- totals?: {
24
- type?: string
25
- amount?: number
26
- }[]
6
+ export interface KnownAgentsOptions {
7
+ /** The maximum time a partial visit event queue waits before upload. Defaults to 10,000 milliseconds. */
8
+ flushIntervalInMilliseconds?: number
9
+ /** The number of visit events in the queue that triggers an upload. Defaults to 1. */
10
+ flushQueueSize?: number
27
11
  }
28
12
 
29
13
  /**
30
14
  * Additional metadata to include when tracking a pageview or REST API call.
31
15
  */
32
16
  export interface TrackPageviewOrRESTCallOptions {
33
- /** ACP response body from the REST endpoint. */
34
- restACPResponseBody?: RESTACPResponseBody
35
- /** UCP response body from the REST endpoint. */
36
- restUCPResponseBody?: RESTUCPResponseBody
17
+ /** The ACP response body. */
18
+ acpResponseBody?: Record<string, unknown>
19
+ /** The UCP response body. */
20
+ ucpResponseBody?: Record<string, unknown>
37
21
  }
38
22
 
39
23
  /**
40
24
  * A normalized visit payload for the Known Agents API.
41
- * Use this with `trackVisits()` when batching requests yourself.
25
+ * Use this with `trackVisits()` to manually add visit events to the queue.
42
26
  */
43
27
  export interface VisitRequest {
44
28
  /** The URL path and query string of the request (e.g. "/about?foo=bar") */
@@ -67,14 +51,10 @@ export interface VisitRequest {
67
51
  mcp_response_error_code?: number
68
52
  /** The MCP response error message */
69
53
  mcp_response_error_message?: string
70
- /** The ACP response currency */
71
- acp_response_currency?: string
72
- /** The ACP response "total" amount */
73
- acp_response_total_amount?: number
74
- /** The UCP response currency */
75
- ucp_response_currency?: string
76
- /** The UCP response "total" amount */
77
- ucp_response_total_amount?: number
54
+ /** The ACP response body */
55
+ acp_response_body?: Record<string, unknown>
56
+ /** The UCP response body */
57
+ ucp_response_body?: Record<string, unknown>
78
58
  /** The timestamp when the visit occurred (ISO 8601 format) */
79
59
  created?: string
80
60
  }
@@ -1,28 +0,0 @@
1
- import type { IncomingMessage } from "http";
2
- import type { RESTACPResponseBody, RESTUCPResponseBody } from "./types";
3
- interface MCPCommerceResponseBody {
4
- result?: {
5
- currency?: string;
6
- totals?: {
7
- type?: string;
8
- amount?: number;
9
- }[];
10
- structuredContent?: {
11
- currency?: string;
12
- totals?: {
13
- type?: string;
14
- amount?: number;
15
- }[];
16
- };
17
- };
18
- }
19
- export declare function getRESTACPCurrencyIfPossible(restACPResponseBody: RESTACPResponseBody): string | undefined;
20
- export declare function getRESTACPTotalAmountIfPossible(restACPResponseBody: RESTACPResponseBody): number | undefined;
21
- export declare function getMCPACPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined;
22
- export declare function getMCPACPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined;
23
- export declare function getRESTUCPCurrencyIfPossible(restUCPResponseBody: RESTUCPResponseBody): string | undefined;
24
- export declare function getRESTUCPTotalAmountIfPossible(restUCPResponseBody: RESTUCPResponseBody): number | undefined;
25
- export declare function getMCPUCPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined;
26
- export declare function getMCPUCPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined;
27
- export {};
28
- //# sourceMappingURL=commerce.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../src/commerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAEvE,UAAU,uBAAuB;IAC7B,MAAM,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,CAAC,EAAE;YACL,IAAI,CAAC,EAAE,MAAM,CAAA;YACb,MAAM,CAAC,EAAE,MAAM,CAAA;SAClB,EAAE,CAAA;QACH,iBAAiB,CAAC,EAAE;YAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;YACjB,MAAM,CAAC,EAAE;gBACL,IAAI,CAAC,EAAE,MAAM,CAAA;gBACb,MAAM,CAAC,EAAE,MAAM,CAAA;aAClB,EAAE,CAAA;SACN,CAAA;KACJ,CAAA;CACJ;AAED,wBAAgB,4BAA4B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAEzG;AAED,wBAAgB,+BAA+B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAI5G;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAGlI;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAOrI;AAED,wBAAgB,4BAA4B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAEzG;AAED,wBAAgB,+BAA+B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAI5G;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAGlI;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAOrI"}
package/dist/commerce.js DELETED
@@ -1,51 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getRESTACPCurrencyIfPossible = getRESTACPCurrencyIfPossible;
4
- exports.getRESTACPTotalAmountIfPossible = getRESTACPTotalAmountIfPossible;
5
- exports.getMCPACPCurrencyIfPossible = getMCPACPCurrencyIfPossible;
6
- exports.getMCPACPTotalAmountIfPossible = getMCPACPTotalAmountIfPossible;
7
- exports.getRESTUCPCurrencyIfPossible = getRESTUCPCurrencyIfPossible;
8
- exports.getRESTUCPTotalAmountIfPossible = getRESTUCPTotalAmountIfPossible;
9
- exports.getMCPUCPCurrencyIfPossible = getMCPUCPCurrencyIfPossible;
10
- exports.getMCPUCPTotalAmountIfPossible = getMCPUCPTotalAmountIfPossible;
11
- const headers_1 = require("./headers");
12
- function getRESTACPCurrencyIfPossible(restACPResponseBody) {
13
- return restACPResponseBody?.currency;
14
- }
15
- function getRESTACPTotalAmountIfPossible(restACPResponseBody) {
16
- return restACPResponseBody?.totals?.find((total) => {
17
- return total.type == "total";
18
- })?.amount;
19
- }
20
- function getMCPACPCurrencyIfPossible(request, mcpResponseBody) {
21
- const isProbablyACP = !(0, headers_1.getIsUCPCall)(request.headers) && (0, headers_1.getIsProbablyACPCall)(request.headers);
22
- return isProbablyACP ? mcpResponseBody.result?.currency : undefined;
23
- }
24
- function getMCPACPTotalAmountIfPossible(request, mcpResponseBody) {
25
- const isProbablyACP = !(0, headers_1.getIsUCPCall)(request.headers) && (0, headers_1.getIsProbablyACPCall)(request.headers);
26
- return isProbablyACP
27
- ? mcpResponseBody.result?.totals?.find((total) => {
28
- return total.type == "total";
29
- })?.amount
30
- : undefined;
31
- }
32
- function getRESTUCPCurrencyIfPossible(restUCPResponseBody) {
33
- return restUCPResponseBody?.currency;
34
- }
35
- function getRESTUCPTotalAmountIfPossible(restUCPResponseBody) {
36
- return restUCPResponseBody?.totals?.find((total) => {
37
- return total.type == "total";
38
- })?.amount;
39
- }
40
- function getMCPUCPCurrencyIfPossible(request, mcpResponseBody) {
41
- const isUCP = (0, headers_1.getIsUCPCall)(request.headers);
42
- return isUCP ? mcpResponseBody.result?.structuredContent?.currency : undefined;
43
- }
44
- function getMCPUCPTotalAmountIfPossible(request, mcpResponseBody) {
45
- const isUCP = (0, headers_1.getIsUCPCall)(request.headers);
46
- return isUCP
47
- ? mcpResponseBody.result?.structuredContent?.totals?.find((total) => {
48
- return total.type == "total";
49
- })?.amount
50
- : undefined;
51
- }
package/src/commerce.ts DELETED
@@ -1,68 +0,0 @@
1
- import type { IncomingMessage } from "http"
2
- import { getIsProbablyACPCall, getIsUCPCall } from "./headers"
3
- import type { RESTACPResponseBody, RESTUCPResponseBody } from "./types"
4
-
5
- interface MCPCommerceResponseBody {
6
- result?: {
7
- currency?: string
8
- totals?: {
9
- type?: string
10
- amount?: number
11
- }[]
12
- structuredContent?: {
13
- currency?: string
14
- totals?: {
15
- type?: string
16
- amount?: number
17
- }[]
18
- }
19
- }
20
- }
21
-
22
- export function getRESTACPCurrencyIfPossible(restACPResponseBody: RESTACPResponseBody): string | undefined {
23
- return restACPResponseBody?.currency
24
- }
25
-
26
- export function getRESTACPTotalAmountIfPossible(restACPResponseBody: RESTACPResponseBody): number | undefined {
27
- return restACPResponseBody?.totals?.find((total) => {
28
- return total.type == "total"
29
- })?.amount
30
- }
31
-
32
- export function getMCPACPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined {
33
- const isProbablyACP = !getIsUCPCall(request.headers) && getIsProbablyACPCall(request.headers)
34
- return isProbablyACP ? mcpResponseBody.result?.currency : undefined
35
- }
36
-
37
- export function getMCPACPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined {
38
- const isProbablyACP = !getIsUCPCall(request.headers) && getIsProbablyACPCall(request.headers)
39
- return isProbablyACP
40
- ? mcpResponseBody.result?.totals?.find((total) => {
41
- return total.type == "total"
42
- })?.amount
43
- : undefined
44
- }
45
-
46
- export function getRESTUCPCurrencyIfPossible(restUCPResponseBody: RESTUCPResponseBody): string | undefined {
47
- return restUCPResponseBody?.currency
48
- }
49
-
50
- export function getRESTUCPTotalAmountIfPossible(restUCPResponseBody: RESTUCPResponseBody): number | undefined {
51
- return restUCPResponseBody?.totals?.find((total) => {
52
- return total.type == "total"
53
- })?.amount
54
- }
55
-
56
- export function getMCPUCPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined {
57
- const isUCP = getIsUCPCall(request.headers)
58
- return isUCP ? mcpResponseBody.result?.structuredContent?.currency : undefined
59
- }
60
-
61
- export function getMCPUCPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined {
62
- const isUCP = getIsUCPCall(request.headers)
63
- return isUCP
64
- ? mcpResponseBody.result?.structuredContent?.totals?.find((total) => {
65
- return total.type == "total"
66
- })?.amount
67
- : undefined
68
- }