@agentronics/sdk 0.6.0 → 0.7.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.
Files changed (53) hide show
  1. package/dist/chunk-4FLZQPCO.js +18 -0
  2. package/dist/chunk-4FLZQPCO.js.map +1 -0
  3. package/dist/{chunk-X7NFMFN6.js → chunk-6QQS26AP.js} +264 -69
  4. package/dist/chunk-6QQS26AP.js.map +1 -0
  5. package/dist/chunk-BPDWJCPY.js +2 -0
  6. package/dist/chunk-BPDWJCPY.js.map +1 -0
  7. package/dist/index.cjs +8 -7
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +57 -8
  10. package/dist/index.d.ts +57 -8
  11. package/dist/index.global.js +8 -7
  12. package/dist/index.global.js.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/{init-only-7f8_LQAI.d.ts → init-only-C80qhujz.d.ts} +227 -30
  16. package/dist/{init-only-Cwll8MGz.d.cts → init-only-s9E9bVC0.d.cts} +227 -30
  17. package/dist/init-only.cjs +8 -7
  18. package/dist/init-only.cjs.map +1 -1
  19. package/dist/init-only.d.cts +2 -2
  20. package/dist/init-only.d.ts +2 -2
  21. package/dist/init-only.global.js +8 -7
  22. package/dist/init-only.global.js.map +1 -1
  23. package/dist/init-only.js +1 -1
  24. package/dist/lite.cjs +1 -1
  25. package/dist/lite.cjs.map +1 -1
  26. package/dist/lite.d.cts +2 -2
  27. package/dist/lite.d.ts +2 -2
  28. package/dist/lite.global.js +1 -1
  29. package/dist/lite.global.js.map +1 -1
  30. package/dist/lite.js +1 -1
  31. package/dist/middleware-Dd2T772u.d.cts +429 -0
  32. package/dist/middleware-Dd2T772u.d.ts +429 -0
  33. package/dist/next.cjs +239 -64
  34. package/dist/next.cjs.map +1 -1
  35. package/dist/next.d.cts +2 -2
  36. package/dist/next.d.ts +2 -2
  37. package/dist/next.js +2 -2
  38. package/dist/server.cjs +361 -66
  39. package/dist/server.cjs.map +1 -1
  40. package/dist/server.d.cts +103 -308
  41. package/dist/server.d.ts +103 -308
  42. package/dist/server.js +93 -1
  43. package/dist/server.js.map +1 -1
  44. package/dist/webmcp-BgKzHeQ3.d.cts +97 -0
  45. package/dist/webmcp-BgKzHeQ3.d.ts +97 -0
  46. package/package.json +4 -4
  47. package/dist/chunk-LLD3Y2LZ.js +0 -2
  48. package/dist/chunk-LLD3Y2LZ.js.map +0 -1
  49. package/dist/chunk-X7NFMFN6.js.map +0 -1
  50. package/dist/chunk-Y7JYD5RA.js +0 -17
  51. package/dist/chunk-Y7JYD5RA.js.map +0 -1
  52. package/dist/webmcp-C4ikhM4g.d.cts +0 -12
  53. package/dist/webmcp-C4ikhM4g.d.ts +0 -12
package/dist/next.cjs CHANGED
@@ -6,9 +6,12 @@ var CRAWLER_SIGNATURES = [
6
6
  { name: "GPTBot", category: "ai", pattern: /GPTBot/i },
7
7
  { name: "OAI-SearchBot", category: "ai", pattern: /OAI-SearchBot/i },
8
8
  { name: "ChatGPT-User", category: "ai", pattern: /ChatGPT-User/i },
9
+ { name: "Claude-User", category: "ai", pattern: /Claude-User/i },
10
+ { name: "Claude-SearchBot", category: "ai", pattern: /Claude-SearchBot/i },
9
11
  { name: "ClaudeBot", category: "ai", pattern: /ClaudeBot/i },
10
12
  { name: "Claude-Web", category: "ai", pattern: /Claude-Web/i },
11
13
  { name: "anthropic-ai", category: "ai", pattern: /anthropic-ai/i },
14
+ { name: "Perplexity-User", category: "ai", pattern: /Perplexity-User/i },
12
15
  { name: "PerplexityBot", category: "ai", pattern: /PerplexityBot/i },
13
16
  { name: "Google-Extended", category: "ai", pattern: /Google-Extended/i },
14
17
  { name: "Applebot-Extended", category: "ai", pattern: /Applebot-Extended/i },
@@ -26,6 +29,96 @@ var CRAWLER_SIGNATURES = [
26
29
  { name: "Applebot", category: "search", pattern: /Applebot/i }
27
30
  ];
28
31
 
32
+ // src/server/ipRanges.ts
33
+ var PUBLISHED_IP_RANGES = {
34
+ GPTBot: ["https://openai.com/gptbot.json"],
35
+ "OAI-SearchBot": ["https://openai.com/searchbot.json"],
36
+ "ChatGPT-User": ["https://openai.com/chatgpt-user.json"],
37
+ PerplexityBot: ["https://www.perplexity.ai/perplexitybot.json"],
38
+ "Perplexity-User": ["https://www.perplexity.ai/perplexity-user.json"],
39
+ Googlebot: ["https://developers.google.com/static/crawling/ipranges/common-crawlers.json"],
40
+ Bingbot: ["https://www.bing.com/toolbox/bingbot.json"],
41
+ DuckDuckBot: ["https://duckduckgo.com/duckduckbot.json"],
42
+ CCBot: ["https://index.commoncrawl.org/ccbot.json"]
43
+ };
44
+ var TTL_MS = 24 * 36e5;
45
+ var FAILED_TTL_MS = 10 * 6e4;
46
+ var cache = /* @__PURE__ */ new Map();
47
+ async function loadUrl(url, fetcher) {
48
+ const hit = cache.get(url);
49
+ if (hit && hit.until > Date.now()) return hit.cidrs;
50
+ try {
51
+ const res = await fetcher(url, { signal: AbortSignal.timeout(5e3), redirect: "follow" });
52
+ if (!res.ok) throw new Error(String(res.status));
53
+ const body = await res.json();
54
+ const cidrs = (body.prefixes ?? []).flatMap((p) => [p.ipv4Prefix, p.ipv6Prefix]).filter((c) => typeof c === "string");
55
+ cache.set(url, { cidrs, until: Date.now() + TTL_MS });
56
+ return cidrs;
57
+ } catch {
58
+ const cidrs = hit?.cidrs ?? [];
59
+ cache.set(url, { cidrs, until: Date.now() + FAILED_TTL_MS });
60
+ return cidrs;
61
+ }
62
+ }
63
+ async function rangesFor(name, options = {}) {
64
+ const fixed = options.ipRanges?.[name];
65
+ if (fixed) return fixed;
66
+ const urls = PUBLISHED_IP_RANGES[name];
67
+ if (!urls) return null;
68
+ const fetcher = options.fetcher ?? fetch;
69
+ return (await Promise.all(urls.map((u) => loadUrl(u, fetcher)))).flat();
70
+ }
71
+ function v4ToBigInt(ip) {
72
+ const parts = ip.split(".");
73
+ if (parts.length !== 4) return null;
74
+ let n = 0n;
75
+ for (const p of parts) {
76
+ if (!/^\d{1,3}$/.test(p) || Number(p) > 255) return null;
77
+ n = n << 8n | BigInt(p);
78
+ }
79
+ return n;
80
+ }
81
+ function v6ToBigInt(ip) {
82
+ let s = ip.toLowerCase().replace(/^\[|\]$/g, "");
83
+ const zone = s.indexOf("%");
84
+ if (zone >= 0) s = s.slice(0, zone);
85
+ const v4 = s.match(/(\d+\.\d+\.\d+\.\d+)$/);
86
+ if (v4) {
87
+ const n2 = v4ToBigInt(v4[1]);
88
+ if (n2 === null) return null;
89
+ s = s.slice(0, -v4[1].length) + (n2 >> 16n & 0xffffn).toString(16) + ":" + (n2 & 0xffffn).toString(16);
90
+ }
91
+ const halves = s.split("::");
92
+ if (halves.length > 2) return null;
93
+ const head = halves[0] ? halves[0].split(":") : [];
94
+ const tail = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
95
+ const missing = 8 - head.length - tail.length;
96
+ if (halves.length === 1 ? missing !== 0 : missing < 1) return null;
97
+ const groups = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...tail];
98
+ let n = 0n;
99
+ for (const g of groups) {
100
+ if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
101
+ n = n << 16n | BigInt(parseInt(g, 16));
102
+ }
103
+ return n;
104
+ }
105
+ function ipInCidr(ip, cidr) {
106
+ const [range, bitsRaw] = cidr.split("/");
107
+ if (!range) return false;
108
+ const isV6 = range.includes(":");
109
+ const mapped = ip.toLowerCase().match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
110
+ const candidate = !isV6 && mapped ? mapped[1] : ip;
111
+ const width = isV6 ? 128 : 32;
112
+ const bits = bitsRaw === void 0 ? width : Number(bitsRaw);
113
+ if (!Number.isInteger(bits) || bits < 0 || bits > width) return false;
114
+ const a = isV6 ? v6ToBigInt(candidate) : v4ToBigInt(candidate);
115
+ const b = isV6 ? v6ToBigInt(range) : v4ToBigInt(range);
116
+ if (a === null || b === null) return false;
117
+ const shift = BigInt(width - bits);
118
+ return a >> shift === b >> shift;
119
+ }
120
+ var ipInRanges = (ip, cidrs) => cidrs.some((c) => ipInCidr(ip, c));
121
+
29
122
  // src/server/crawlers.ts
30
123
  var RDNS_DOMAINS = {
31
124
  Googlebot: ["googlebot.com", "google.com", "googleusercontent.com"],
@@ -64,14 +157,19 @@ async function nodeResolver() {
64
157
  }
65
158
  }
66
159
  var rdnsCache = /* @__PURE__ */ new Map();
67
- async function verifyCrawler(claim, ip, resolver) {
160
+ async function verifyCrawler(claim, ip, resolver, ranges = {}) {
68
161
  const domains = RDNS_DOMAINS[claim.name];
69
- if (!domains) return { status: "unverified", name: claim.name, reason: "no_rdns_method_for_vendor" };
162
+ const cidrs = await rangesFor(claim.name, ranges);
163
+ if (!domains && !cidrs) return { status: "unverified", name: claim.name, reason: "no_verification_method_for_vendor" };
70
164
  if (!ip) return { status: "unverified", name: claim.name, reason: "client_ip_unknown" };
165
+ if (cidrs?.length && ipInRanges(ip, cidrs)) {
166
+ return { status: "verified", name: claim.name, host: ip, ip, via: "ip-range" };
167
+ }
168
+ if (!domains) return { status: "unverified", name: claim.name, reason: "ip_outside_published_ranges" };
71
169
  const key = `${claim.name}|${ip}`;
72
170
  const hit = rdnsCache.get(key);
73
171
  if (hit && hit.until > Date.now()) {
74
- return hit.ok ? { status: "verified", name: claim.name, host: hit.host, ip } : { status: "unverified", name: claim.name, reason: "rdns_mismatch" };
172
+ return hit.ok ? { status: "verified", name: claim.name, host: hit.host, ip, via: "rdns" } : { status: "unverified", name: claim.name, reason: "rdns_mismatch" };
75
173
  }
76
174
  const dns = resolver ?? await nodeResolver();
77
175
  if (!dns) return { status: "unverified", name: claim.name, reason: "dns_unavailable_in_runtime" };
@@ -87,7 +185,7 @@ async function verifyCrawler(claim, ip, resolver) {
87
185
  const forward = await dns.lookup(host);
88
186
  if (forward.includes(ip)) {
89
187
  remember(true, host);
90
- return { status: "verified", name: claim.name, host, ip };
188
+ return { status: "verified", name: claim.name, host, ip, via: "rdns" };
91
189
  }
92
190
  }
93
191
  remember(false);
@@ -320,6 +418,117 @@ function bytesToBase64(bytes) {
320
418
  }
321
419
  var bytesToBase64Url = (bytes) => bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
322
420
 
421
+ // src/server/oauth.ts
422
+ var josePromise;
423
+ var loadJose = () => josePromise ??= import('jose');
424
+ var remoteSets = /* @__PURE__ */ new Map();
425
+ var ASYMMETRIC = ["RS256", "PS256", "ES256", "EdDSA"];
426
+ function unverifiedIssuer(token) {
427
+ try {
428
+ const payload = token.split(".")[1];
429
+ const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
430
+ const iss = JSON.parse(json).iss;
431
+ return typeof iss === "string" ? iss : null;
432
+ } catch {
433
+ return null;
434
+ }
435
+ }
436
+ var str = (v) => typeof v === "string" && v ? v : void 0;
437
+ async function sha256B64u(text) {
438
+ const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
439
+ return bytesToBase64Url(new Uint8Array(d));
440
+ }
441
+ var htuOf = (url) => {
442
+ const u = new URL(url);
443
+ return `${u.origin}${u.pathname}`;
444
+ };
445
+ async function verifyDpopProof(jose, proof, token, jkt, request, replay) {
446
+ try {
447
+ const { payload, protectedHeader } = await jose.jwtVerify(proof, jose.EmbeddedJWK, {
448
+ typ: "dpop+jwt",
449
+ algorithms: ASYMMETRIC
450
+ });
451
+ const jwk = protectedHeader.jwk;
452
+ if (!jwk) return "dpop_missing_jwk";
453
+ if (await jose.calculateJwkThumbprint(jwk, "sha256") !== jkt) return "dpop_key_mismatch";
454
+ if (str(payload.htm)?.toUpperCase() !== request.method.toUpperCase()) return "dpop_htm_mismatch";
455
+ if (str(payload.htu) === void 0 || htuOf(str(payload.htu)) !== htuOf(request.url)) return "dpop_htu_mismatch";
456
+ const iat = typeof payload.iat === "number" ? payload.iat : NaN;
457
+ if (!Number.isFinite(iat) || Math.abs(Date.now() / 1e3 - iat) > 300) return "dpop_stale";
458
+ if (str(payload.ath) !== await sha256B64u(token)) return "dpop_ath_mismatch";
459
+ const jti = str(payload.jti);
460
+ if (!jti) return "dpop_missing_jti";
461
+ if (replay) {
462
+ const key = `dpop:${jkt}:${jti}`;
463
+ if (await replay.has(key)) return "dpop_replayed";
464
+ await replay.add(key, 600);
465
+ }
466
+ return null;
467
+ } catch (e) {
468
+ return `dpop_invalid:${e.code ?? e.message}`;
469
+ }
470
+ }
471
+ async function verifyOAuthToken(token, o, ctx) {
472
+ const jose = await loadJose();
473
+ const keySet = o.jwks ? jose.createLocalJWKSet(o.jwks) : (() => {
474
+ const uri = o.jwksUri ?? `${o.issuer.replace(/\/$/, "")}/.well-known/jwks.json`;
475
+ let s = remoteSets.get(uri);
476
+ if (!s) {
477
+ s = jose.createRemoteJWKSet(new URL(uri), { cacheMaxAge: 36e5, cooldownDuration: 3e4 });
478
+ remoteSets.set(uri, s);
479
+ }
480
+ return s;
481
+ })();
482
+ let payload;
483
+ try {
484
+ ;
485
+ ({ payload } = await jose.jwtVerify(token, keySet, {
486
+ issuer: o.issuer,
487
+ audience: o.audience,
488
+ algorithms: o.algorithms ?? ASYMMETRIC,
489
+ clockTolerance: o.clockToleranceSec ?? 30
490
+ }));
491
+ } catch (e) {
492
+ return { error: `invalid_token:${e.code ?? e.message}` };
493
+ }
494
+ const jkt = str(payload.cnf?.jkt);
495
+ let dpop = false;
496
+ if (jkt) {
497
+ const proof = ctx.request.headers.get("dpop");
498
+ if (ctx.scheme !== "dpop" || !proof) return { error: "dpop_proof_required" };
499
+ const bad = await verifyDpopProof(jose, proof, token, jkt, ctx.request, ctx.replayCache);
500
+ if (bad) return { error: bad };
501
+ dpop = true;
502
+ } else if (o.requireDpop) {
503
+ return { error: "token_not_dpop_bound" };
504
+ }
505
+ if (o.requiredScopes?.length) {
506
+ const granted = new Set(String(payload.scope ?? payload.scp ?? "").split(/\s+/));
507
+ const missing = o.requiredScopes.filter((s) => !granted.has(s));
508
+ if (missing.length) return { error: `missing_scopes:${missing.join(",")}` };
509
+ }
510
+ const act = payload.act;
511
+ const clientId = str(payload.client_id) ?? str(payload.azp);
512
+ const sub = str(payload.sub);
513
+ const agentId = str(act?.client_id) ?? str(act?.sub) ?? clientId ?? sub;
514
+ if (!agentId) return { error: "token_has_no_client_id" };
515
+ const user = act ? sub : sub && clientId && sub !== clientId ? sub : void 0;
516
+ const agent = {
517
+ id: `oauth2:${agentId}`,
518
+ name: str(payload.agent_name) ?? str(payload.client_name) ?? agentId,
519
+ vendor: str(payload.agent_vendor) ?? null,
520
+ claims: { iss: payload.iss, sub, scope: payload.scope ?? payload.scp, ...act ? { act } : {} },
521
+ dpop
522
+ };
523
+ if (user) {
524
+ const onBehalfOf = { sub: user };
525
+ if (str(payload.email)) onBehalfOf.email = str(payload.email);
526
+ if (str(payload.name)) onBehalfOf.name = str(payload.name);
527
+ agent.onBehalfOf = onBehalfOf;
528
+ }
529
+ return agent;
530
+ }
531
+
323
532
  // src/server/httpSignatures.ts
324
533
  var SignatureError = class extends Error {
325
534
  constructor(code, message) {
@@ -758,61 +967,15 @@ var KNOWN_SIGNERS = {
758
967
  };
759
968
  var GENERIC_AGENT_UA = /\b(bot|crawler|spider|agent|headless|scraper)\b/i;
760
969
  var JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$/;
761
- function bearer(request) {
970
+ function credential(request) {
762
971
  const h = request.headers.get("authorization");
763
- return h && /^bearer\s+/i.test(h) ? h.replace(/^bearer\s+/i, "").trim() : null;
764
- }
765
- function unverifiedJwtIssuer(token) {
766
- try {
767
- const payload = token.split(".")[1];
768
- const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
769
- const iss = JSON.parse(json).iss;
770
- return typeof iss === "string" ? iss : null;
771
- } catch {
772
- return null;
773
- }
774
- }
775
- var josePromise;
776
- var remoteSets = /* @__PURE__ */ new Map();
777
- async function verifyOAuth2(token, o) {
778
- josePromise ??= import('jose');
779
- const jose = await josePromise;
780
- const keySet = o.jwks ? jose.createLocalJWKSet(o.jwks) : (() => {
781
- const uri = o.jwksUri ?? `${o.issuer.replace(/\/$/, "")}/.well-known/jwks.json`;
782
- let s = remoteSets.get(uri);
783
- if (!s) {
784
- s = jose.createRemoteJWKSet(new URL(uri), { cacheMaxAge: 36e5, cooldownDuration: 3e4 });
785
- remoteSets.set(uri, s);
786
- }
787
- return s;
788
- })();
789
- try {
790
- const { payload } = await jose.jwtVerify(token, keySet, {
791
- issuer: o.issuer,
792
- audience: o.audience,
793
- algorithms: o.algorithms ?? ["RS256", "PS256", "ES256", "EdDSA"],
794
- clockTolerance: o.clockToleranceSec ?? 30
795
- });
796
- if (o.requiredScopes?.length) {
797
- const granted = new Set(String(payload.scope ?? payload.scp ?? "").split(/\s+/));
798
- const missing = o.requiredScopes.filter((s) => !granted.has(s));
799
- if (missing.length) return { error: `missing_scopes:${missing.join(",")}` };
800
- }
801
- const clientId = String(payload.client_id ?? payload.azp ?? payload.sub ?? "");
802
- if (!clientId) return { error: "token_has_no_client_id" };
803
- return {
804
- id: `oauth2:${clientId}`,
805
- name: typeof payload.agent_name === "string" ? payload.agent_name : clientId,
806
- vendor: typeof payload.agent_vendor === "string" ? payload.agent_vendor : null,
807
- method: "oauth2",
808
- claims: { sub: payload.sub, scope: payload.scope ?? payload.scp }
809
- };
810
- } catch (e) {
811
- return { error: `invalid_token:${e.code ?? e.message}` };
812
- }
972
+ const m = h?.match(/^(bearer|dpop)\s+(.+)$/i);
973
+ return m ? { token: m[2].trim(), scheme: m[1].toLowerCase() } : null;
813
974
  }
814
975
  function createAgentAuth(options = {}) {
815
- const wba = options.webBotAuth === false ? null : { replayCache: memoryReplayCache(), ...typeof options.webBotAuth === "object" ? options.webBotAuth : {} };
976
+ const replayCache = options.replayCache ?? memoryReplayCache();
977
+ const wba = options.webBotAuth === false ? null : { replayCache, ...typeof options.webBotAuth === "object" ? options.webBotAuth : {} };
978
+ const issuers = options.oauth2 ? Array.isArray(options.oauth2) ? options.oauth2 : [options.oauth2] : [];
816
979
  const crawlers = options.crawlers === false ? null : typeof options.crawlers === "object" ? options.crawlers : {};
817
980
  const keyPrefix = options.apiKey?.prefix ?? "agk_";
818
981
  async function authenticate(request) {
@@ -841,7 +1004,8 @@ function createAgentAuth(options = {}) {
841
1004
  claimedName ??= r.agentUrl ? new URL(r.agentUrl).hostname : void 0;
842
1005
  }
843
1006
  }
844
- const token = bearer(request) ?? request.headers.get("x-agent-key");
1007
+ const cred = credential(request);
1008
+ const token = cred?.token ?? request.headers.get("x-agent-key");
845
1009
  if (token && options.apiKey && token.startsWith(keyPrefix)) {
846
1010
  attempted.push("api-key");
847
1011
  const id = await options.apiKey.verify(token);
@@ -858,12 +1022,23 @@ function createAgentAuth(options = {}) {
858
1022
  };
859
1023
  }
860
1024
  reasons.push("api-key:unknown_or_revoked");
861
- } else if (token && options.oauth2 && JWT_SHAPE.test(token)) {
862
- const iss = unverifiedJwtIssuer(token);
863
- if (iss === options.oauth2.issuer) {
1025
+ } else if (cred && issuers.length && JWT_SHAPE.test(cred.token)) {
1026
+ const iss = unverifiedIssuer(cred.token);
1027
+ const config = issuers.find((i) => i.issuer === iss);
1028
+ if (config) {
864
1029
  attempted.push("oauth2");
865
- const r = await verifyOAuth2(token, options.oauth2);
866
- if (!("error" in r)) return { status: "verified", agent: r };
1030
+ const r = await verifyOAuthToken(cred.token, config, { request, scheme: cred.scheme, replayCache });
1031
+ if (!("error" in r)) {
1032
+ const agent = {
1033
+ id: r.id,
1034
+ name: r.name,
1035
+ vendor: r.vendor,
1036
+ method: "oauth2",
1037
+ claims: { ...r.claims, dpop: r.dpop }
1038
+ };
1039
+ if (r.onBehalfOf) agent.onBehalfOf = r.onBehalfOf;
1040
+ return { status: "verified", agent };
1041
+ }
867
1042
  reasons.push(`oauth2:${r.error}`);
868
1043
  }
869
1044
  }
@@ -873,11 +1048,11 @@ function createAgentAuth(options = {}) {
873
1048
  claimedName ??= claim.name;
874
1049
  if (crawlers) {
875
1050
  attempted.push("verified-crawler");
876
- const v = await verifyCrawler(claim, clientIp(request, crawlers.clientIp), crawlers.resolver);
1051
+ const v = await verifyCrawler(claim, clientIp(request, crawlers.clientIp), crawlers.resolver, crawlers);
877
1052
  if (v.status === "verified") {
878
1053
  return {
879
1054
  status: "verified",
880
- agent: { id: `crawler:${claim.name}`, name: claim.name, vendor: null, method: "verified-crawler", claims: { host: v.host } }
1055
+ agent: { id: `crawler:${claim.name}`, name: claim.name, vendor: null, method: "verified-crawler", claims: { host: v.host, via: v.via } }
881
1056
  };
882
1057
  }
883
1058
  reasons.push(`crawler:${v.reason}`);