@kya-os/agentshield-nextjs 0.2.7 → 0.2.8

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.
@@ -0,0 +1,161 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { createEvaluationContext, evaluatePolicy, ENFORCEMENT_ACTIONS, PolicyConfigSchema, DEFAULT_POLICY, matchPath, createPolicyFetcher } from '@kya-os/agentshield-shared';
3
+ export { DEFAULT_POLICY, ENFORCEMENT_ACTIONS, createEvaluationContext, evaluatePolicy } from '@kya-os/agentshield-shared';
4
+
5
+ // src/policy.ts
6
+ function createContextFromDetection(detection, request) {
7
+ return createEvaluationContext({
8
+ agentType: detection.detectedAgent?.type,
9
+ agentName: detection.detectedAgent?.name,
10
+ agentVendor: detection.detectedAgent?.vendor,
11
+ confidence: detection.confidence,
12
+ riskLevel: detection.riskLevel,
13
+ path: request.nextUrl.pathname,
14
+ method: request.method,
15
+ signatureVerified: detection.verificationMethod === "signature",
16
+ isAuthenticated: false,
17
+ // TODO: integrate with auth
18
+ userAgent: request.headers.get("user-agent") || void 0
19
+ });
20
+ }
21
+ function evaluatePolicyForDetection(detection, request, policy) {
22
+ const context = createContextFromDetection(detection, request);
23
+ return evaluatePolicy(policy, context);
24
+ }
25
+ function buildBlockedResponse(decision, config) {
26
+ const status = config.blockedResponse?.status ?? 403;
27
+ const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
28
+ const response = NextResponse.json(
29
+ {
30
+ error: message,
31
+ code: "POLICY_BLOCKED",
32
+ reason: decision.reason,
33
+ ruleId: decision.ruleId,
34
+ matchType: decision.matchType
35
+ },
36
+ { status }
37
+ );
38
+ if (config.blockedResponse?.headers) {
39
+ for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
40
+ response.headers.set(key, value);
41
+ }
42
+ }
43
+ response.headers.set("X-AgentShield-Action", decision.action);
44
+ response.headers.set("X-AgentShield-Reason", decision.reason);
45
+ response.headers.set("X-AgentShield-MatchType", decision.matchType);
46
+ return response;
47
+ }
48
+ function buildRedirectResponse(request, decision, config) {
49
+ const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
50
+ const url = new URL(redirectUrl, request.url);
51
+ url.searchParams.set("reason", decision.reason);
52
+ if (decision.ruleId) {
53
+ url.searchParams.set("ruleId", decision.ruleId);
54
+ }
55
+ return NextResponse.redirect(url);
56
+ }
57
+ function buildChallengeResponse(request, decision, config) {
58
+ return buildRedirectResponse(request, decision, config);
59
+ }
60
+ async function handlePolicyDecision(request, decision, config) {
61
+ switch (decision.action) {
62
+ case ENFORCEMENT_ACTIONS.BLOCK:
63
+ if (config.customBlockedResponse) {
64
+ return await config.customBlockedResponse(request, decision);
65
+ }
66
+ return buildBlockedResponse(decision, config);
67
+ case ENFORCEMENT_ACTIONS.REDIRECT:
68
+ return buildRedirectResponse(request, decision, config);
69
+ case ENFORCEMENT_ACTIONS.CHALLENGE:
70
+ return buildChallengeResponse(request, decision, config);
71
+ case ENFORCEMENT_ACTIONS.LOG:
72
+ console.log("[AgentShield] Policy decision (log):", {
73
+ path: request.nextUrl.pathname,
74
+ action: decision.action,
75
+ reason: decision.reason,
76
+ matchType: decision.matchType,
77
+ ruleId: decision.ruleId
78
+ });
79
+ return null;
80
+ // Continue to allow
81
+ case ENFORCEMENT_ACTIONS.ALLOW:
82
+ default:
83
+ return null;
84
+ }
85
+ }
86
+ var fetcherCache = /* @__PURE__ */ new Map();
87
+ function getFetcherCacheKey(config) {
88
+ return `${config.apiUrl ?? "default"}:${config.apiKey ?? ""}:${config.cacheTtlSeconds ?? "default"}`;
89
+ }
90
+ function getPolicyFetcher(config) {
91
+ if (!config) {
92
+ throw new Error("fetchPolicy config required");
93
+ }
94
+ const cacheKey = getFetcherCacheKey(config);
95
+ let fetcher = fetcherCache.get(cacheKey);
96
+ if (!fetcher) {
97
+ const fetcherConfig = {
98
+ apiBaseUrl: config.apiUrl || "https://kya.vouched.id",
99
+ apiKey: config.apiKey,
100
+ cacheTtlSeconds: config.cacheTtlSeconds
101
+ };
102
+ fetcher = createPolicyFetcher(fetcherConfig);
103
+ fetcherCache.set(cacheKey, fetcher);
104
+ }
105
+ return fetcher;
106
+ }
107
+ async function getPolicy(config) {
108
+ if (config.policy) {
109
+ return PolicyConfigSchema.parse({ ...DEFAULT_POLICY, ...config.policy });
110
+ }
111
+ if (config.fetchPolicy) {
112
+ try {
113
+ const fetcher = getPolicyFetcher(config.fetchPolicy);
114
+ return await fetcher.getPolicy(config.fetchPolicy.projectId);
115
+ } catch (error) {
116
+ if (config.debug) {
117
+ console.warn("[AgentShield] Policy fetch failed, using fallback:", error);
118
+ }
119
+ return PolicyConfigSchema.parse({
120
+ ...DEFAULT_POLICY,
121
+ ...config.fallbackPolicy || {}
122
+ });
123
+ }
124
+ }
125
+ return PolicyConfigSchema.parse(DEFAULT_POLICY);
126
+ }
127
+ async function applyPolicy(request, detection, config) {
128
+ try {
129
+ const path = request.nextUrl.pathname;
130
+ if (config.skipPaths?.some((pattern) => matchPath(path, pattern))) {
131
+ return null;
132
+ }
133
+ if (config.includePaths && config.includePaths.length > 0) {
134
+ if (!config.includePaths.some((pattern) => matchPath(path, pattern))) {
135
+ return null;
136
+ }
137
+ }
138
+ const policy = await getPolicy(config);
139
+ const context = createContextFromDetection(detection, request);
140
+ const decision = evaluatePolicy(policy, context);
141
+ if (config.onPolicyDecision) {
142
+ await config.onPolicyDecision(request, decision, context);
143
+ }
144
+ return await handlePolicyDecision(request, decision, config);
145
+ } catch (error) {
146
+ if (config.debug) {
147
+ console.error("[AgentShield] Policy evaluation error:", error);
148
+ }
149
+ if (config.failOpen !== false) {
150
+ return null;
151
+ }
152
+ return NextResponse.json(
153
+ { error: "Security check failed", code: "POLICY_ERROR" },
154
+ { status: 503 }
155
+ );
156
+ }
157
+ }
158
+
159
+ export { applyPolicy, buildBlockedResponse, buildChallengeResponse, buildRedirectResponse, createContextFromDetection, evaluatePolicyForDetection, getPolicy, handlePolicyDecision };
160
+ //# sourceMappingURL=policy.mjs.map
161
+ //# sourceMappingURL=policy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policy.ts"],"names":[],"mappings":"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","file":"policy.mjs","sourcesContent":["/**\n * Policy Integration for agentshield-nextjs\n *\n * This module provides policy evaluation support for the Next.js middleware.\n * It can use:\n * - Local policy configuration (static)\n * - Fetched policy from AgentShield API (dynamic with caching)\n * - Fallback/default policies\n *\n * @example\n * ```typescript\n * import { createPolicyMiddleware } from '@kya-os/agentshield-nextjs/policy';\n *\n * export default createPolicyMiddleware({\n * policy: {\n * enabled: true,\n * defaultAction: 'allow',\n * thresholds: { confidenceThreshold: 80, confidenceAction: 'block' },\n * allowList: [{ clientName: 'ChatGPT' }],\n * },\n * });\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport {\n evaluatePolicy,\n createEvaluationContext,\n createPolicyFetcher,\n matchPath,\n PolicyFetcher,\n PolicyConfigSchema,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n type PolicyFetcherConfig,\n type DetectionResult,\n} from '@kya-os/agentshield-shared';\n\n// Re-export shared policy types for convenience\nexport {\n evaluatePolicy,\n createEvaluationContext,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n} from '@kya-os/agentshield-shared';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Policy middleware configuration\n */\nexport interface PolicyMiddlewareConfig {\n /**\n * Local policy configuration (static)\n * If provided, this policy is used instead of fetching from API\n */\n policy?: Partial<PolicyConfig>;\n\n /**\n * Fetch policy from AgentShield API\n * Requires projectId and optionally an apiKey\n */\n fetchPolicy?: {\n /** Project ID to fetch policy for */\n projectId: string;\n /** API base URL (defaults to production) */\n apiUrl?: string;\n /** API key for authentication */\n apiKey?: string;\n /** Cache TTL in seconds (default: 300) */\n cacheTtlSeconds?: number;\n };\n\n /**\n * Fallback policy to use when fetch fails\n * Defaults to DEFAULT_POLICY (allow all)\n */\n fallbackPolicy?: Partial<PolicyConfig>;\n\n /**\n * Custom blocked response\n */\n blockedResponse?: {\n status?: number;\n message?: string;\n headers?: Record<string, string>;\n };\n\n /**\n * Default redirect URL for redirect actions\n */\n redirectUrl?: string;\n\n /**\n * Callback when policy decision is made\n */\n onPolicyDecision?: (\n request: NextRequest,\n decision: PolicyEvaluationResult,\n context: PolicyEvaluationContext\n ) => void | Promise<void>;\n\n /**\n * Custom response builder for blocked requests\n */\n customBlockedResponse?: (\n request: NextRequest,\n decision: PolicyEvaluationResult\n ) => NextResponse | Promise<NextResponse>;\n\n /**\n * Whether to fail open (allow) on policy evaluation errors\n * Default: true (recommended for production)\n */\n failOpen?: boolean;\n\n /**\n * Enable debug logging\n */\n debug?: boolean;\n}\n\n/**\n * Combined middleware configuration with policy support\n */\nexport interface NextJSPolicyMiddlewareConfig extends PolicyMiddlewareConfig {\n /**\n * Paths to skip (in addition to policy excludedPaths)\n */\n skipPaths?: string[];\n\n /**\n * Only enforce on these paths (overrides policy includedPaths)\n */\n includePaths?: string[];\n}\n\n// ============================================================================\n// Policy Evaluation Helper\n// ============================================================================\n\n/**\n * Create policy evaluation context from detection result and request\n */\nexport function createContextFromDetection(\n detection: DetectionResult,\n request: NextRequest\n): PolicyEvaluationContext {\n return createEvaluationContext({\n agentType: detection.detectedAgent?.type,\n agentName: detection.detectedAgent?.name,\n agentVendor: detection.detectedAgent?.vendor,\n confidence: detection.confidence,\n riskLevel: detection.riskLevel,\n path: request.nextUrl.pathname,\n method: request.method,\n signatureVerified: detection.verificationMethod === 'signature',\n isAuthenticated: false, // TODO: integrate with auth\n userAgent: request.headers.get('user-agent') || undefined,\n });\n}\n\n/**\n * Evaluate policy for a detection result\n */\nexport function evaluatePolicyForDetection(\n detection: DetectionResult,\n request: NextRequest,\n policy: PolicyConfig\n): PolicyEvaluationResult {\n const context = createContextFromDetection(detection, request);\n return evaluatePolicy(policy, context);\n}\n\n// ============================================================================\n// Response Builders\n// ============================================================================\n\n/**\n * Build blocked response based on policy decision\n */\nexport function buildBlockedResponse(\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n const status = config.blockedResponse?.status ?? 403;\n const message = config.blockedResponse?.message ?? decision.message ?? 'Access denied';\n\n const response = NextResponse.json(\n {\n error: message,\n code: 'POLICY_BLOCKED',\n reason: decision.reason,\n ruleId: decision.ruleId,\n matchType: decision.matchType,\n },\n { status }\n );\n\n // Add custom headers\n if (config.blockedResponse?.headers) {\n for (const [key, value] of Object.entries(config.blockedResponse.headers)) {\n response.headers.set(key, value);\n }\n }\n\n // Add AgentShield headers\n response.headers.set('X-AgentShield-Action', decision.action);\n response.headers.set('X-AgentShield-Reason', decision.reason);\n response.headers.set('X-AgentShield-MatchType', decision.matchType);\n\n return response;\n}\n\n/**\n * Build redirect response based on policy decision\n */\nexport function buildRedirectResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n const redirectUrl = decision.redirectUrl || config.redirectUrl || '/blocked';\n const url = new URL(redirectUrl, request.url);\n\n // Add query params with policy info\n url.searchParams.set('reason', decision.reason);\n if (decision.ruleId) {\n url.searchParams.set('ruleId', decision.ruleId);\n }\n\n return NextResponse.redirect(url);\n}\n\n/**\n * Build challenge response (placeholder - future implementation)\n */\nexport function buildChallengeResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n // For now, treat challenge as redirect\n // Future: implement CAPTCHA, proof-of-work, etc.\n return buildRedirectResponse(request, decision, config);\n}\n\n// ============================================================================\n// Policy Handler\n// ============================================================================\n\n/**\n * Handle policy decision and return appropriate response\n */\nexport async function handlePolicyDecision(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): Promise<NextResponse | null> {\n switch (decision.action) {\n case ENFORCEMENT_ACTIONS.BLOCK:\n if (config.customBlockedResponse) {\n return await config.customBlockedResponse(request, decision);\n }\n return buildBlockedResponse(decision, config);\n\n case ENFORCEMENT_ACTIONS.REDIRECT:\n return buildRedirectResponse(request, decision, config);\n\n case ENFORCEMENT_ACTIONS.CHALLENGE:\n return buildChallengeResponse(request, decision, config);\n\n case ENFORCEMENT_ACTIONS.LOG:\n // LOG action always logs - that's its purpose\n // (debug flag controls verbose debugging output, not LOG action behavior)\n console.log('[AgentShield] Policy decision (log):', {\n path: request.nextUrl.pathname,\n action: decision.action,\n reason: decision.reason,\n matchType: decision.matchType,\n ruleId: decision.ruleId,\n });\n return null; // Continue to allow\n\n case ENFORCEMENT_ACTIONS.ALLOW:\n default:\n return null; // Continue\n }\n}\n\n// ============================================================================\n// Policy Fetcher Integration\n// ============================================================================\n\n// Cache fetchers by config to avoid recreating them, but also support\n// different configurations (different apiUrl, apiKey, etc.)\nconst fetcherCache = new Map<string, PolicyFetcher>();\n\n/**\n * Generate a cache key for fetcher config.\n * Uses ?? to distinguish between explicit 0 and undefined values.\n */\nfunction getFetcherCacheKey(config: NonNullable<PolicyMiddlewareConfig['fetchPolicy']>): string {\n return `${config.apiUrl ?? 'default'}:${config.apiKey ?? ''}:${config.cacheTtlSeconds ?? 'default'}`;\n}\n\n/**\n * Get or create policy fetcher for the given config\n */\nfunction getPolicyFetcher(config: PolicyMiddlewareConfig['fetchPolicy']): PolicyFetcher {\n if (!config) {\n throw new Error('fetchPolicy config required');\n }\n\n const cacheKey = getFetcherCacheKey(config);\n let fetcher = fetcherCache.get(cacheKey);\n\n if (!fetcher) {\n const fetcherConfig: PolicyFetcherConfig = {\n apiBaseUrl: config.apiUrl || 'https://kya.vouched.id',\n apiKey: config.apiKey,\n cacheTtlSeconds: config.cacheTtlSeconds,\n };\n fetcher = createPolicyFetcher(fetcherConfig);\n fetcherCache.set(cacheKey, fetcher);\n }\n\n return fetcher;\n}\n\n/**\n * Get policy (local, fetched, or fallback)\n */\nexport async function getPolicy(config: PolicyMiddlewareConfig): Promise<PolicyConfig> {\n // Use local policy if provided\n if (config.policy) {\n return PolicyConfigSchema.parse({ ...DEFAULT_POLICY, ...config.policy });\n }\n\n // Fetch from API if configured\n if (config.fetchPolicy) {\n try {\n const fetcher = getPolicyFetcher(config.fetchPolicy);\n return await fetcher.getPolicy(config.fetchPolicy.projectId);\n } catch (error) {\n if (config.debug) {\n console.warn('[AgentShield] Policy fetch failed, using fallback:', error);\n }\n // Return fallback policy\n return PolicyConfigSchema.parse({\n ...DEFAULT_POLICY,\n ...(config.fallbackPolicy || {}),\n });\n }\n }\n\n // No policy configured - return default (allow all)\n return PolicyConfigSchema.parse(DEFAULT_POLICY);\n}\n\n// ============================================================================\n// Standalone Policy Middleware\n// ============================================================================\n\n/**\n * Apply policy to a detection result\n *\n * This function can be used standalone to evaluate policy after detection.\n * Supports extended config with skipPaths and includePaths for path-based filtering.\n *\n * @example\n * ```typescript\n * const result = await detector.analyze(context);\n * const response = await applyPolicy(request, result, {\n * policy: { thresholds: { confidenceThreshold: 80 } },\n * skipPaths: ['/health', '/api/public/*'],\n * includePaths: ['/api/*'],\n * });\n *\n * if (response) {\n * return response; // Policy blocked the request\n * }\n * ```\n */\nexport async function applyPolicy(\n request: NextRequest,\n detection: DetectionResult,\n config: NextJSPolicyMiddlewareConfig\n): Promise<NextResponse | null> {\n try {\n const path = request.nextUrl.pathname;\n\n // Check skipPaths - if path matches any skip pattern, allow through\n if (config.skipPaths?.some((pattern) => matchPath(path, pattern))) {\n return null; // Skip policy enforcement for this path\n }\n\n // Check includePaths - if defined, path must match at least one pattern\n if (config.includePaths && config.includePaths.length > 0) {\n if (!config.includePaths.some((pattern) => matchPath(path, pattern))) {\n return null; // Path not in included paths, skip policy enforcement\n }\n }\n\n // Get policy\n const policy = await getPolicy(config);\n\n // Create context and evaluate\n const context = createContextFromDetection(detection, request);\n const decision = evaluatePolicy(policy, context);\n\n // Call decision callback if provided\n if (config.onPolicyDecision) {\n await config.onPolicyDecision(request, decision, context);\n }\n\n // Handle decision\n return await handlePolicyDecision(request, decision, config);\n } catch (error) {\n if (config.debug) {\n console.error('[AgentShield] Policy evaluation error:', error);\n }\n\n if (config.failOpen !== false) {\n return null; // Allow on error\n }\n\n // Fail closed\n return NextResponse.json(\n { error: 'Security check failed', code: 'POLICY_ERROR' },\n { status: 503 }\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/agentshield-nextjs",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Next.js middleware for AgentShield AI agent detection",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -75,6 +75,11 @@
75
75
  "import": "./dist/api-middleware.mjs",
76
76
  "require": "./dist/api-middleware.js"
77
77
  },
78
+ "./policy": {
79
+ "types": "./dist/policy.d.ts",
80
+ "import": "./dist/policy.mjs",
81
+ "require": "./dist/policy.js"
82
+ },
78
83
  "./package.json": "./package.json"
79
84
  },
80
85
  "files": [
@@ -136,9 +141,9 @@
136
141
  },
137
142
  "sideEffects": false,
138
143
  "dependencies": {
139
- "@kya-os/agentshield": "^0.1.40",
140
- "@kya-os/agentshield-shared": "^0.2.2",
141
- "@kya-os/agentshield-wasm-runtime": "^0.1.4",
144
+ "@kya-os/agentshield": "workspace:*",
145
+ "@kya-os/agentshield-shared": "workspace:*",
146
+ "@kya-os/agentshield-wasm-runtime": "workspace:*",
142
147
  "@noble/ed25519": "^2.2.3",
143
148
  "@noble/hashes": "^2.0.1"
144
149
  }
@@ -12,27 +12,18 @@ export function detect_agent(metadata: JsRequestMetadata): JsDetectionResult;
12
12
  * Get the version of the AgentShield library
13
13
  */
14
14
  export function version(): string;
15
- /**
16
- * Get the version of the AgentShield library
17
- */
18
- export function get_version(): string;
19
- /**
20
- * Get build information
21
- */
22
- export function get_build_info(): string;
23
15
  /**
24
16
  * JavaScript-compatible detection result
25
17
  */
26
18
  export class JsDetectionResult {
27
19
  private constructor();
28
20
  free(): void;
29
- [Symbol.dispose](): void;
30
21
  /**
31
22
  * Whether the request was identified as coming from an agent
32
23
  */
33
24
  is_agent: boolean;
34
25
  /**
35
- * Confidence score (0-100 scale)
26
+ * Confidence score (0.0 to 1.0)
36
27
  */
37
28
  confidence: number;
38
29
  /**
@@ -57,11 +48,10 @@ export class JsDetectionResult {
57
48
  */
58
49
  export class JsRequestMetadata {
59
50
  free(): void;
60
- [Symbol.dispose](): void;
61
51
  /**
62
52
  * Constructor for JsRequestMetadata
63
53
  */
64
- constructor(user_agent: string | null | undefined, ip_address: string | null | undefined, headers: string, timestamp: string, url?: string | null, method?: string | null, client_fingerprint?: string | null);
54
+ constructor(user_agent: string | null | undefined, ip_address: string | null | undefined, headers: string, timestamp: string);
65
55
  /**
66
56
  * Get the user agent
67
57
  */
@@ -78,18 +68,6 @@ export class JsRequestMetadata {
78
68
  * Get the timestamp
79
69
  */
80
70
  readonly timestamp: string;
81
- /**
82
- * Get the URL
83
- */
84
- readonly url: string | undefined;
85
- /**
86
- * Get the method
87
- */
88
- readonly method: string | undefined;
89
- /**
90
- * Get the client fingerprint
91
- */
92
- readonly client_fingerprint: string | undefined;
93
71
  }
94
72
 
95
73
  export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
@@ -106,22 +84,17 @@ export interface InitOutput {
106
84
  readonly jsdetectionresult_risk_level: (a: number, b: number) => void;
107
85
  readonly jsdetectionresult_timestamp: (a: number, b: number) => void;
108
86
  readonly __wbg_jsrequestmetadata_free: (a: number, b: number) => void;
109
- readonly jsrequestmetadata_new: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number) => number;
87
+ readonly jsrequestmetadata_new: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
110
88
  readonly jsrequestmetadata_user_agent: (a: number, b: number) => void;
111
89
  readonly jsrequestmetadata_ip_address: (a: number, b: number) => void;
112
90
  readonly jsrequestmetadata_headers: (a: number, b: number) => void;
113
91
  readonly jsrequestmetadata_timestamp: (a: number, b: number) => void;
114
- readonly jsrequestmetadata_url: (a: number, b: number) => void;
115
- readonly jsrequestmetadata_method: (a: number, b: number) => void;
116
- readonly jsrequestmetadata_client_fingerprint: (a: number, b: number) => void;
117
92
  readonly init: () => void;
118
93
  readonly detect_agent: (a: number, b: number) => void;
119
- readonly get_version: (a: number) => void;
120
- readonly get_build_info: (a: number) => void;
121
94
  readonly version: (a: number) => void;
122
- readonly __wbindgen_export: (a: number, b: number, c: number) => void;
123
- readonly __wbindgen_export2: (a: number, b: number) => number;
124
- readonly __wbindgen_export3: (a: number, b: number, c: number, d: number) => number;
95
+ readonly __wbindgen_export_0: (a: number, b: number, c: number) => void;
96
+ readonly __wbindgen_export_1: (a: number, b: number) => number;
97
+ readonly __wbindgen_export_2: (a: number, b: number, c: number, d: number) => number;
125
98
  readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
126
99
  readonly __wbindgen_start: () => void;
127
100
  }