@kya-os/mcp-i-core 1.3.12 → 1.3.14

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 (254) hide show
  1. package/dist/config/remote-config.js +9 -12
  2. package/dist/runtime/base.js +11 -0
  3. package/dist/services/access-control.service.js +5 -0
  4. package/dist/services/tool-protection.service.js +17 -8
  5. package/package.json +2 -2
  6. package/.turbo/turbo-build.log +0 -4
  7. package/.turbo/turbo-test$colon$coverage.log +0 -4586
  8. package/.turbo/turbo-test.log +0 -3169
  9. package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
  10. package/Composer 3.md +0 -615
  11. package/GPT-5.md +0 -1169
  12. package/OPUS-plan.md +0 -352
  13. package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
  14. package/PHASE_3_SUMMARY.md +0 -317
  15. package/PHASE_4.1.3_SUMMARY.md +0 -428
  16. package/PHASE_4.1_COMPLETE.md +0 -525
  17. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
  18. package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
  19. package/TEST_PLAN.md +0 -571
  20. package/coverage/coverage-final.json +0 -60
  21. package/dist/cache/oauth-config-cache.d.ts.map +0 -1
  22. package/dist/cache/oauth-config-cache.js.map +0 -1
  23. package/dist/cache/tool-protection-cache.d.ts.map +0 -1
  24. package/dist/cache/tool-protection-cache.js.map +0 -1
  25. package/dist/compliance/index.d.ts.map +0 -1
  26. package/dist/compliance/index.js.map +0 -1
  27. package/dist/compliance/schema-registry.d.ts.map +0 -1
  28. package/dist/compliance/schema-registry.js.map +0 -1
  29. package/dist/compliance/schema-verifier.d.ts.map +0 -1
  30. package/dist/compliance/schema-verifier.js.map +0 -1
  31. package/dist/config/remote-config.d.ts.map +0 -1
  32. package/dist/config/remote-config.js.map +0 -1
  33. package/dist/config.d.ts.map +0 -1
  34. package/dist/config.js.map +0 -1
  35. package/dist/delegation/audience-validator.d.ts.map +0 -1
  36. package/dist/delegation/audience-validator.js.map +0 -1
  37. package/dist/delegation/bitstring.d.ts.map +0 -1
  38. package/dist/delegation/bitstring.js.map +0 -1
  39. package/dist/delegation/cascading-revocation.d.ts.map +0 -1
  40. package/dist/delegation/cascading-revocation.js.map +0 -1
  41. package/dist/delegation/delegation-graph.d.ts.map +0 -1
  42. package/dist/delegation/delegation-graph.js.map +0 -1
  43. package/dist/delegation/did-key-resolver.d.ts.map +0 -1
  44. package/dist/delegation/did-key-resolver.js.map +0 -1
  45. package/dist/delegation/index.d.ts.map +0 -1
  46. package/dist/delegation/index.js.map +0 -1
  47. package/dist/delegation/statuslist-manager.d.ts.map +0 -1
  48. package/dist/delegation/statuslist-manager.js.map +0 -1
  49. package/dist/delegation/storage/index.d.ts.map +0 -1
  50. package/dist/delegation/storage/index.js.map +0 -1
  51. package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
  52. package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
  53. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
  54. package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
  55. package/dist/delegation/utils.d.ts.map +0 -1
  56. package/dist/delegation/utils.js.map +0 -1
  57. package/dist/delegation/vc-issuer.d.ts.map +0 -1
  58. package/dist/delegation/vc-issuer.js.map +0 -1
  59. package/dist/delegation/vc-verifier.d.ts.map +0 -1
  60. package/dist/delegation/vc-verifier.js.map +0 -1
  61. package/dist/identity/idp-token-resolver.d.ts.map +0 -1
  62. package/dist/identity/idp-token-resolver.js.map +0 -1
  63. package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
  64. package/dist/identity/idp-token-storage.interface.js.map +0 -1
  65. package/dist/identity/user-did-manager.d.ts.map +0 -1
  66. package/dist/identity/user-did-manager.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/index.js.map +0 -1
  69. package/dist/providers/base.d.ts.map +0 -1
  70. package/dist/providers/base.js.map +0 -1
  71. package/dist/providers/memory.d.ts.map +0 -1
  72. package/dist/providers/memory.js.map +0 -1
  73. package/dist/runtime/audit-logger.d.ts.map +0 -1
  74. package/dist/runtime/audit-logger.js.map +0 -1
  75. package/dist/runtime/base.d.ts.map +0 -1
  76. package/dist/runtime/base.js.map +0 -1
  77. package/dist/services/access-control.service.d.ts.map +0 -1
  78. package/dist/services/access-control.service.js.map +0 -1
  79. package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
  80. package/dist/services/authorization/authorization-registry.js.map +0 -1
  81. package/dist/services/authorization/types.d.ts.map +0 -1
  82. package/dist/services/authorization/types.js.map +0 -1
  83. package/dist/services/batch-delegation.service.d.ts.map +0 -1
  84. package/dist/services/batch-delegation.service.js.map +0 -1
  85. package/dist/services/crypto.service.d.ts.map +0 -1
  86. package/dist/services/crypto.service.js.map +0 -1
  87. package/dist/services/errors.d.ts.map +0 -1
  88. package/dist/services/errors.js.map +0 -1
  89. package/dist/services/index.d.ts.map +0 -1
  90. package/dist/services/index.js.map +0 -1
  91. package/dist/services/oauth-config.service.d.ts.map +0 -1
  92. package/dist/services/oauth-config.service.js.map +0 -1
  93. package/dist/services/oauth-provider-registry.d.ts.map +0 -1
  94. package/dist/services/oauth-provider-registry.js.map +0 -1
  95. package/dist/services/oauth-service.d.ts.map +0 -1
  96. package/dist/services/oauth-service.js.map +0 -1
  97. package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
  98. package/dist/services/oauth-token-retrieval.service.js.map +0 -1
  99. package/dist/services/proof-verifier.d.ts.map +0 -1
  100. package/dist/services/proof-verifier.js.map +0 -1
  101. package/dist/services/provider-resolver.d.ts.map +0 -1
  102. package/dist/services/provider-resolver.js.map +0 -1
  103. package/dist/services/provider-validator.d.ts.map +0 -1
  104. package/dist/services/provider-validator.js.map +0 -1
  105. package/dist/services/session-registration.service.d.ts.map +0 -1
  106. package/dist/services/session-registration.service.js.map +0 -1
  107. package/dist/services/storage.service.d.ts.map +0 -1
  108. package/dist/services/storage.service.js.map +0 -1
  109. package/dist/services/tool-context-builder.d.ts.map +0 -1
  110. package/dist/services/tool-context-builder.js.map +0 -1
  111. package/dist/services/tool-protection.service.d.ts.map +0 -1
  112. package/dist/services/tool-protection.service.js.map +0 -1
  113. package/dist/types/oauth-required-error.d.ts.map +0 -1
  114. package/dist/types/oauth-required-error.js.map +0 -1
  115. package/dist/types/tool-protection.d.ts.map +0 -1
  116. package/dist/types/tool-protection.js.map +0 -1
  117. package/dist/utils/base58.d.ts.map +0 -1
  118. package/dist/utils/base58.js.map +0 -1
  119. package/dist/utils/base64.d.ts.map +0 -1
  120. package/dist/utils/base64.js.map +0 -1
  121. package/dist/utils/cors.d.ts.map +0 -1
  122. package/dist/utils/cors.js.map +0 -1
  123. package/dist/utils/did-helpers.d.ts.map +0 -1
  124. package/dist/utils/did-helpers.js.map +0 -1
  125. package/dist/utils/index.d.ts.map +0 -1
  126. package/dist/utils/index.js.map +0 -1
  127. package/dist/utils/storage-keys.d.ts.map +0 -1
  128. package/dist/utils/storage-keys.js.map +0 -1
  129. package/docs/API_REFERENCE.md +0 -1362
  130. package/docs/COMPLIANCE_MATRIX.md +0 -691
  131. package/docs/STATUSLIST2021_GUIDE.md +0 -696
  132. package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
  133. package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
  134. package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
  135. package/src/__tests__/delegation-e2e.test.ts +0 -690
  136. package/src/__tests__/identity/user-did-manager.test.ts +0 -232
  137. package/src/__tests__/index.test.ts +0 -56
  138. package/src/__tests__/integration/full-flow.test.ts +0 -789
  139. package/src/__tests__/integration.test.ts +0 -281
  140. package/src/__tests__/providers/base.test.ts +0 -173
  141. package/src/__tests__/providers/memory.test.ts +0 -319
  142. package/src/__tests__/regression/phase2-regression.test.ts +0 -429
  143. package/src/__tests__/runtime/audit-logger.test.ts +0 -154
  144. package/src/__tests__/runtime/base-extensions.test.ts +0 -595
  145. package/src/__tests__/runtime/base.test.ts +0 -869
  146. package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
  147. package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
  148. package/src/__tests__/runtime/route-interception.test.ts +0 -686
  149. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
  150. package/src/__tests__/services/agentshield-integration.test.ts +0 -791
  151. package/src/__tests__/services/cache-busting.test.ts +0 -125
  152. package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
  153. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
  154. package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
  155. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
  156. package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
  157. package/src/__tests__/utils/mock-providers.ts +0 -340
  158. package/src/cache/oauth-config-cache.d.ts +0 -69
  159. package/src/cache/oauth-config-cache.d.ts.map +0 -1
  160. package/src/cache/oauth-config-cache.js.map +0 -1
  161. package/src/cache/oauth-config-cache.ts +0 -123
  162. package/src/cache/tool-protection-cache.ts +0 -171
  163. package/src/compliance/EXAMPLE.md +0 -412
  164. package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
  165. package/src/compliance/index.ts +0 -8
  166. package/src/compliance/schema-registry.ts +0 -460
  167. package/src/compliance/schema-verifier.ts +0 -708
  168. package/src/config/__tests__/merged-config.spec.ts +0 -445
  169. package/src/config/__tests__/remote-config.spec.ts +0 -268
  170. package/src/config/remote-config.ts +0 -264
  171. package/src/config.ts +0 -312
  172. package/src/delegation/__tests__/audience-validator.test.ts +0 -112
  173. package/src/delegation/__tests__/bitstring.test.ts +0 -346
  174. package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
  175. package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
  176. package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
  177. package/src/delegation/__tests__/utils.test.ts +0 -152
  178. package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
  179. package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
  180. package/src/delegation/audience-validator.ts +0 -52
  181. package/src/delegation/bitstring.ts +0 -278
  182. package/src/delegation/cascading-revocation.ts +0 -370
  183. package/src/delegation/delegation-graph.ts +0 -299
  184. package/src/delegation/did-key-resolver.ts +0 -179
  185. package/src/delegation/index.ts +0 -14
  186. package/src/delegation/statuslist-manager.ts +0 -353
  187. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
  188. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
  189. package/src/delegation/storage/index.ts +0 -9
  190. package/src/delegation/storage/memory-graph-storage.ts +0 -178
  191. package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
  192. package/src/delegation/utils.ts +0 -221
  193. package/src/delegation/vc-issuer.ts +0 -232
  194. package/src/delegation/vc-verifier.ts +0 -568
  195. package/src/identity/idp-token-resolver.ts +0 -181
  196. package/src/identity/idp-token-storage.interface.ts +0 -94
  197. package/src/identity/user-did-manager.ts +0 -526
  198. package/src/index.ts +0 -310
  199. package/src/providers/base.d.ts +0 -91
  200. package/src/providers/base.d.ts.map +0 -1
  201. package/src/providers/base.js.map +0 -1
  202. package/src/providers/base.ts +0 -96
  203. package/src/providers/memory.ts +0 -142
  204. package/src/runtime/audit-logger.ts +0 -39
  205. package/src/runtime/base.ts +0 -1392
  206. package/src/services/__tests__/access-control.integration.test.ts +0 -443
  207. package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
  208. package/src/services/__tests__/access-control.service.test.ts +0 -970
  209. package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
  210. package/src/services/__tests__/crypto.service.test.ts +0 -531
  211. package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
  212. package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
  213. package/src/services/__tests__/proof-verifier.test.ts +0 -489
  214. package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
  215. package/src/services/__tests__/provider-resolver.test.ts +0 -213
  216. package/src/services/__tests__/storage.service.test.ts +0 -358
  217. package/src/services/access-control.service.ts +0 -990
  218. package/src/services/authorization/authorization-registry.ts +0 -66
  219. package/src/services/authorization/types.ts +0 -71
  220. package/src/services/batch-delegation.service.ts +0 -137
  221. package/src/services/crypto.service.ts +0 -302
  222. package/src/services/errors.ts +0 -76
  223. package/src/services/index.ts +0 -18
  224. package/src/services/oauth-config.service.d.ts +0 -53
  225. package/src/services/oauth-config.service.d.ts.map +0 -1
  226. package/src/services/oauth-config.service.js.map +0 -1
  227. package/src/services/oauth-config.service.ts +0 -192
  228. package/src/services/oauth-provider-registry.d.ts +0 -57
  229. package/src/services/oauth-provider-registry.d.ts.map +0 -1
  230. package/src/services/oauth-provider-registry.js.map +0 -1
  231. package/src/services/oauth-provider-registry.ts +0 -141
  232. package/src/services/oauth-service.ts +0 -544
  233. package/src/services/oauth-token-retrieval.service.ts +0 -245
  234. package/src/services/proof-verifier.ts +0 -478
  235. package/src/services/provider-resolver.d.ts +0 -48
  236. package/src/services/provider-resolver.d.ts.map +0 -1
  237. package/src/services/provider-resolver.js.map +0 -1
  238. package/src/services/provider-resolver.ts +0 -146
  239. package/src/services/provider-validator.ts +0 -170
  240. package/src/services/session-registration.service.ts +0 -251
  241. package/src/services/storage.service.ts +0 -566
  242. package/src/services/tool-context-builder.ts +0 -237
  243. package/src/services/tool-protection.service.ts +0 -1070
  244. package/src/types/oauth-required-error.ts +0 -63
  245. package/src/types/tool-protection.ts +0 -155
  246. package/src/utils/__tests__/did-helpers.test.ts +0 -156
  247. package/src/utils/base58.ts +0 -109
  248. package/src/utils/base64.ts +0 -148
  249. package/src/utils/cors.ts +0 -83
  250. package/src/utils/did-helpers.ts +0 -210
  251. package/src/utils/index.ts +0 -8
  252. package/src/utils/storage-keys.ts +0 -278
  253. package/tsconfig.json +0 -21
  254. package/vitest.config.ts +0 -56
@@ -1,990 +0,0 @@
1
- /**
2
- * Access Control API Service
3
- *
4
- * Brand-neutral service for interacting with access-control APIs
5
- * (currently AgentShield/bouncer, but designed to be provider-agnostic).
6
- *
7
- * This service provides:
8
- * - Delegation verification
9
- * - Configuration fetching
10
- * - Proof submission
11
- *
12
- * @package @kya-os/mcp-i-core
13
- */
14
-
15
- import type {
16
- VerifyDelegationRequest,
17
- VerifyDelegationAPIResponse,
18
- ToolProtectionConfigAPIResponse,
19
- ProofSubmissionRequest,
20
- ProofSubmissionResponse,
21
- } from "@kya-os/contracts/agentshield-api";
22
- import {
23
- verifyDelegationRequestSchema,
24
- verifyDelegationAPIResponseSchema,
25
- toolProtectionConfigAPIResponseSchema,
26
- proofSubmissionRequestSchema,
27
- proofSubmissionResponseSchema,
28
- } from "@kya-os/contracts/agentshield-api";
29
- import { AgentShieldAPIError } from "@kya-os/contracts/agentshield-api";
30
- import type { FetchProvider } from "../providers/base";
31
-
32
- // Type for error response
33
- type AgentShieldAPIErrorResponse = {
34
- code: string;
35
- message: string;
36
- details?: Record<string, unknown>;
37
- };
38
-
39
- export interface AccessControlApiServiceConfig {
40
- /** Base URL for the access-control API (e.g., "https://kya.vouched.id") */
41
- baseUrl: string;
42
-
43
- /** API key for authentication */
44
- apiKey: string;
45
-
46
- /** Fetch provider for making HTTP requests */
47
- fetchProvider: FetchProvider;
48
-
49
- /** Optional logger callback for diagnostics */
50
- logger?: (message: string, data?: unknown) => void;
51
-
52
- /** Optional retry configuration */
53
- retryConfig?: {
54
- maxRetries?: number;
55
- initialDelayMs?: number;
56
- maxDelayMs?: number;
57
- };
58
-
59
- /** Optional sleep function for delays (platform-agnostic) */
60
- sleepProvider?: (ms: number) => Promise<void>;
61
- }
62
-
63
- export interface AccessControlApiServiceMetrics {
64
- /** Number of successful requests */
65
- successCount: number;
66
-
67
- /** Number of failed requests */
68
- errorCount: number;
69
-
70
- /** Number of retries performed */
71
- retryCount: number;
72
- }
73
-
74
- /**
75
- * Internal type with all required properties
76
- */
77
- type RequiredAccessControlApiServiceConfig = Required<
78
- Omit<AccessControlApiServiceConfig, "retryConfig" | "sleepProvider">
79
- > & {
80
- retryConfig: Required<
81
- NonNullable<AccessControlApiServiceConfig["retryConfig"]>
82
- >;
83
- sleepProvider: NonNullable<AccessControlApiServiceConfig["sleepProvider"]>;
84
- };
85
-
86
- /**
87
- * Generate correlation ID for request tracking
88
- */
89
- function generateCorrelationId(): string {
90
- // Use crypto.randomUUID() if available (Node.js 14.17+, Cloudflare Workers)
91
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
92
- return crypto.randomUUID();
93
- }
94
- // Fallback for older environments
95
- return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
96
- }
97
-
98
- /**
99
- * Access Control API Service
100
- *
101
- * Handles all interactions with the access-control API (AgentShield/bouncer).
102
- * Designed to be brand-neutral and work with any access-control provider.
103
- */
104
- export class AccessControlApiService {
105
- private config: RequiredAccessControlApiServiceConfig;
106
- private metrics: AccessControlApiServiceMetrics;
107
-
108
- constructor(config: AccessControlApiServiceConfig) {
109
- const retryConfig = config.retryConfig || {};
110
- this.config = {
111
- retryConfig: {
112
- maxRetries: retryConfig.maxRetries ?? 3,
113
- initialDelayMs: retryConfig.initialDelayMs ?? 100,
114
- maxDelayMs: retryConfig.maxDelayMs ?? 5000,
115
- },
116
- logger: config.logger || (() => {}),
117
- baseUrl: config.baseUrl,
118
- apiKey: config.apiKey,
119
- fetchProvider: config.fetchProvider,
120
- sleepProvider:
121
- config.sleepProvider ||
122
- ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))),
123
- };
124
-
125
- this.metrics = {
126
- successCount: 0,
127
- errorCount: 0,
128
- retryCount: 0,
129
- };
130
- }
131
-
132
- /**
133
- * Fetch tool protection configuration for an agent
134
- *
135
- * GET /api/v1/bouncer/config?agent_did={agentDid}
136
- */
137
- async fetchConfig(options: {
138
- agentDid: string;
139
- }): Promise<ToolProtectionConfigAPIResponse> {
140
- return this.retryWithBackoff(async () => {
141
- const correlationId = generateCorrelationId();
142
- const url = `${this.config.baseUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(options.agentDid)}`;
143
-
144
- this.config.logger(
145
- `[AccessControl] Fetching config for agent: ${options.agentDid}`,
146
- {
147
- correlationId,
148
- url,
149
- }
150
- );
151
-
152
- const response = await this.config.fetchProvider.fetch(url, {
153
- method: "GET",
154
- headers: {
155
- Authorization: `Bearer ${this.config.apiKey}`,
156
- "Content-Type": "application/json",
157
- "X-Request-ID": correlationId,
158
- },
159
- });
160
-
161
- const responseText = await response.text();
162
- const responseData = this.parseResponseJSON(response, responseText);
163
-
164
- // Handle error responses
165
- if (!response.ok) {
166
- this.handleErrorResponse(response, responseData);
167
- }
168
-
169
- // Validate and parse success response
170
- const parsed =
171
- toolProtectionConfigAPIResponseSchema.safeParse(responseData);
172
- if (!parsed.success) {
173
- throw new AgentShieldAPIError(
174
- "invalid_response",
175
- "Response validation failed",
176
- { zodErrors: parsed.error.errors }
177
- );
178
- }
179
-
180
- this.config.logger(`[AccessControl] Config fetched successfully`, {
181
- correlationId,
182
- agentDid: options.agentDid,
183
- });
184
-
185
- return parsed.data;
186
- });
187
- }
188
-
189
- /**
190
- * Verify a delegation token
191
- *
192
- * POST /api/v1/bouncer/delegations/verify
193
- */
194
- async verifyDelegation(
195
- request: VerifyDelegationRequest,
196
- context?: {
197
- delegationToken?: string;
198
- credentialJwt?: string;
199
- }
200
- ): Promise<VerifyDelegationAPIResponse> {
201
- return this.retryWithBackoff(async () => {
202
- const correlationId = generateCorrelationId();
203
- const url = `${this.config.baseUrl}/api/v1/bouncer/delegations/verify`;
204
-
205
- // Build request body dynamically to handle optional fields
206
- const requestBody: Partial<VerifyDelegationRequest> & {
207
- agent_did: string;
208
- } = {
209
- agent_did: request.agent_did,
210
- };
211
-
212
- // Add optional fields only if they exist
213
- if (request.scopes !== undefined) {
214
- requestBody.scopes = request.scopes;
215
- }
216
-
217
- // Handle credential_jwt: prefer request, fallback to context
218
- if (request.credential_jwt !== undefined) {
219
- requestBody.credential_jwt = request.credential_jwt;
220
- } else if (context?.credentialJwt) {
221
- requestBody.credential_jwt = context.credentialJwt;
222
- }
223
-
224
- // Handle delegation_token: prefer request, fallback to context
225
- if (request.delegation_token !== undefined) {
226
- requestBody.delegation_token = request.delegation_token;
227
- } else if (context?.delegationToken) {
228
- requestBody.delegation_token = context.delegationToken;
229
- }
230
-
231
- if (request.timestamp !== undefined) {
232
- requestBody.timestamp = request.timestamp;
233
- }
234
-
235
- if (request.client_info) {
236
- requestBody.client_info = request.client_info;
237
- }
238
-
239
- // Remove undefined values to ensure Zod validation passes (optional fields should be omitted, not undefined)
240
- const cleanedRequestBody = Object.fromEntries(
241
- Object.entries(requestBody).filter(([_, value]) => value !== undefined)
242
- );
243
-
244
- // Validate the cleaned request body
245
- // Note: Workaround for Zod schema issue where .optional() doesn't properly handle omitted fields
246
- // See AGENTSHIELD_MCPI_COMPLIANCE_REVIEW.md for details
247
- const validationResult =
248
- verifyDelegationRequestSchema.safeParse(cleanedRequestBody);
249
- if (!validationResult.success) {
250
- // Check if the error is specifically about scopes being required when it's omitted
251
- const scopesError = validationResult.error.errors.find(
252
- (e) => e.path.includes("scopes") && e.message === "Required"
253
- );
254
- if (scopesError && !("scopes" in cleanedRequestBody)) {
255
- // This is a known Zod schema issue in AgentShield - scopes is optional but Zod treats it as required
256
- // Skip validation for this case since the field is correctly omitted
257
- // TODO: Remove this workaround once AgentShield fixes their schema
258
- } else {
259
- this.config.logger(`[AccessControl] Validation failed:`, {
260
- errors: validationResult.error.errors,
261
- requestBody: requestBody,
262
- });
263
- throw new AgentShieldAPIError(
264
- "validation_error",
265
- "Request validation failed",
266
- { zodErrors: validationResult.error.errors }
267
- );
268
- }
269
- }
270
-
271
- this.config.logger(
272
- `[AccessControl] Verifying delegation for agent: ${request.agent_did}`,
273
- {
274
- correlationId,
275
- url,
276
- hasScopes: (request.scopes?.length || 0) > 0,
277
- }
278
- );
279
-
280
- const response = await this.config.fetchProvider.fetch(url, {
281
- method: "POST",
282
- headers: {
283
- Authorization: `Bearer ${this.config.apiKey}`,
284
- "Content-Type": "application/json",
285
- "X-Request-ID": correlationId,
286
- },
287
- // Use cleanedRequestBody directly instead of validated data
288
- // because Zod seems to strip optional fields in some cases
289
- body: JSON.stringify(cleanedRequestBody),
290
- });
291
-
292
- const responseText = await response.text();
293
- const responseData = this.parseResponseJSON(response, responseText);
294
-
295
- // Handle error responses
296
- if (!response.ok) {
297
- this.handleErrorResponse(response, responseData);
298
- }
299
-
300
- // Validate and parse success response
301
- const parsed = verifyDelegationAPIResponseSchema.safeParse(responseData);
302
- if (!parsed.success) {
303
- throw new AgentShieldAPIError(
304
- "invalid_response",
305
- "Response validation failed",
306
- { zodErrors: parsed.error.errors }
307
- );
308
- }
309
-
310
- this.config.logger(`[AccessControl] Delegation verified`, {
311
- correlationId,
312
- agentDid: request.agent_did,
313
- valid: parsed.data.data.valid,
314
- });
315
-
316
- return parsed.data;
317
- });
318
- }
319
-
320
- /**
321
- * Submit proofs for audit and verification
322
- *
323
- * POST /api/v1/bouncer/proofs
324
- */
325
- async submitProofs(
326
- request: ProofSubmissionRequest
327
- ): Promise<ProofSubmissionResponse> {
328
- return this.retryWithBackoff(async () => {
329
- // Validate request directly - Zod's .nullish() should handle null/undefined correctly
330
- const validationResult = proofSubmissionRequestSchema.safeParse(request);
331
- if (!validationResult.success) {
332
- // Log validation errors for debugging
333
- const errorDetails = JSON.stringify(
334
- validationResult.error.errors,
335
- null,
336
- 2
337
- );
338
- this.config.logger(
339
- `[AccessControl] Proof submission validation failed:`,
340
- {
341
- errors: validationResult.error.errors,
342
- request: JSON.stringify(request, null, 2),
343
- }
344
- );
345
- throw new AgentShieldAPIError(
346
- "validation_error",
347
- `Request validation failed: ${errorDetails}`,
348
- { zodErrors: validationResult.error.errors }
349
- );
350
- }
351
-
352
- // Use validated request for the API call
353
- const validatedRequest = validationResult.data;
354
-
355
- const correlationId = generateCorrelationId();
356
- const url = `${this.config.baseUrl}/api/v1/bouncer/proofs`;
357
-
358
- this.config.logger(
359
- `[AccessControl] Submitting ${request.proofs.length} proof(s)`,
360
- {
361
- correlationId,
362
- url,
363
- sessionId: request.session_id,
364
- delegationId: request.delegation_id,
365
- }
366
- );
367
-
368
- const httpResponse = await this.config.fetchProvider.fetch(url, {
369
- method: "POST",
370
- headers: {
371
- Authorization: `Bearer ${this.config.apiKey}`,
372
- "Content-Type": "application/json",
373
- "X-Request-ID": correlationId,
374
- },
375
- body: JSON.stringify(validatedRequest),
376
- });
377
-
378
- const responseText = await httpResponse.text();
379
-
380
- // CRITICAL: Log raw response IMMEDIATELY before parsing/validation
381
- // This will help us debug validation failures
382
- console.error(`[AccessControl] 🔍 RAW API RESPONSE (before parsing):`, {
383
- correlationId,
384
- status: httpResponse.status,
385
- statusText: httpResponse.statusText,
386
- headers: Object.fromEntries(httpResponse.headers.entries()),
387
- responseTextLength: responseText.length,
388
- responseTextPreview: responseText.substring(0, 500),
389
- fullResponseText: responseText, // Full response for debugging
390
- });
391
-
392
- const responseData = this.parseResponseJSON(httpResponse, responseText);
393
-
394
- // Log parsed response immediately after parsing
395
- console.error(`[AccessControl] 🔍 PARSED RESPONSE DATA:`, {
396
- correlationId,
397
- status: httpResponse.status,
398
- responseDataType: typeof responseData,
399
- responseDataKeys: Object.keys(
400
- (responseData as Record<string, unknown>) || {}
401
- ),
402
- responseData: JSON.stringify(responseData, null, 2),
403
- });
404
-
405
- // Handle error responses
406
- if (!httpResponse.ok) {
407
- const errorData = responseData as {
408
- success?: boolean;
409
- error?: AgentShieldAPIErrorResponse;
410
- };
411
-
412
- if (errorData.error) {
413
- const errorCode = errorData.error.code || "api_error";
414
-
415
- // Special handling for all_proofs_rejected - return response instead of throwing
416
- if (
417
- errorCode === "all_proofs_rejected" &&
418
- httpResponse.status === 400
419
- ) {
420
- // Parse the error details to extract accepted/rejected counts
421
- const errorDetails = errorData.error.details as
422
- | { rejected?: number; errors?: unknown[] }
423
- | undefined;
424
- // Create response matching ProofSubmissionResponse interface
425
- // Validate structure with Zod to ensure type safety
426
- const errorResponseData = {
427
- success: false, // ProofSubmissionResponse has a success field
428
- accepted: 0,
429
- rejected: errorDetails?.rejected || request.proofs.length,
430
- outcomes: {
431
- success: 0,
432
- failed: 0,
433
- blocked: 0,
434
- error: errorDetails?.rejected || request.proofs.length,
435
- },
436
- errors: errorDetails?.errors,
437
- };
438
-
439
- // Validate with Zod schema to ensure type safety
440
- const validated =
441
- proofSubmissionResponseSchema.safeParse(errorResponseData);
442
- if (validated.success) {
443
- return validated.data;
444
- }
445
-
446
- // If validation fails, log and throw error
447
- throw new AgentShieldAPIError(
448
- "invalid_response",
449
- "Error response validation failed",
450
- { zodErrors: validated.error.errors }
451
- );
452
- }
453
-
454
- // Ensure error details include status code for retry detection
455
- const errorDetails = {
456
- ...(errorData.error.details || {}),
457
- status: httpResponse.status,
458
- };
459
- throw new AgentShieldAPIError(
460
- errorCode,
461
- errorData.error.message || `API error: ${httpResponse.status}`,
462
- errorDetails
463
- );
464
- }
465
-
466
- // Map status codes to error codes
467
- let errorCode = "api_error";
468
- if (httpResponse.status === 400) {
469
- errorCode = "validation_error";
470
- } else if (httpResponse.status === 404) {
471
- errorCode = httpResponse.statusText.includes("delegation")
472
- ? "delegation_not_found"
473
- : "session_not_found";
474
- }
475
-
476
- throw new AgentShieldAPIError(
477
- errorCode,
478
- `API request failed: ${httpResponse.status} ${httpResponse.statusText}`,
479
- { status: httpResponse.status, responseData }
480
- );
481
- }
482
-
483
- // Try to handle wrapped response format first
484
- const wrappedResponse = responseData as {
485
- success?: boolean;
486
- data?: unknown;
487
- metadata?: unknown;
488
- };
489
-
490
- // Log raw response for debugging (always log full response on first submission)
491
- const rawResponseLog = {
492
- correlationId,
493
- hasSuccess: wrappedResponse.success !== undefined,
494
- hasData: wrappedResponse.data !== undefined,
495
- responseType: typeof responseData,
496
- responseKeys: Object.keys(
497
- (responseData as Record<string, unknown>) || {}
498
- ),
499
- responseData: JSON.stringify(responseData, null, 2).substring(0, 2000), // Increased limit for debugging
500
- };
501
- this.config.logger(
502
- `[AccessControl] Raw response received`,
503
- rawResponseLog
504
- );
505
- // Also log to console.error for visibility (especially for first proof submission errors)
506
- console.error(
507
- `[AccessControl] Raw response received:`,
508
- JSON.stringify(responseData, null, 2)
509
- );
510
-
511
- if (wrappedResponse.success !== undefined && wrappedResponse.data) {
512
- // Response is wrapped in { success, data }
513
- // Extract data and add success field if missing (for schema validation)
514
- // CRITICAL: Use JSON.parse(JSON.stringify()) to create a clean object
515
- // This handles edge cases in Cloudflare Workers where response.json()
516
- // might return objects with proxy/getter issues that prevent property access
517
- let dataToValidate: Record<string, unknown>;
518
-
519
- try {
520
- // Deep clone via JSON to ensure we have a plain JavaScript object
521
- // This is the most reliable way to handle potential proxy/getter issues
522
- const rawData = wrappedResponse.data;
523
- const clonedData = JSON.parse(JSON.stringify(rawData));
524
-
525
- if (typeof clonedData !== "object" || clonedData === null) {
526
- throw new AgentShieldAPIError(
527
- "invalid_response",
528
- "Response data is not an object after cloning",
529
- {
530
- originalType: typeof rawData,
531
- clonedType: typeof clonedData,
532
- clonedValue: clonedData,
533
- }
534
- );
535
- }
536
-
537
- dataToValidate = clonedData as Record<string, unknown>;
538
- } catch (cloneError) {
539
- if (cloneError instanceof AgentShieldAPIError) {
540
- throw cloneError;
541
- }
542
- throw new AgentShieldAPIError(
543
- "invalid_response",
544
- "Failed to clone response data",
545
- {
546
- error:
547
- cloneError instanceof Error
548
- ? cloneError.message
549
- : String(cloneError),
550
- dataType: typeof wrappedResponse.data,
551
- }
552
- );
553
- }
554
-
555
- // CRITICAL: Log the actual data structure for debugging
556
- console.error(`[AccessControl] 🔍 DATA OBJECT STRUCTURE (after deep clone):`, {
557
- correlationId,
558
- dataKeys: Object.keys(dataToValidate),
559
- hasAccepted: "accepted" in dataToValidate,
560
- hasRejected: "rejected" in dataToValidate,
561
- hasOutcomes: "outcomes" in dataToValidate,
562
- hasErrors: "errors" in dataToValidate,
563
- acceptedType: typeof dataToValidate.accepted,
564
- acceptedValue: dataToValidate.accepted,
565
- rejectedType: typeof dataToValidate.rejected,
566
- rejectedValue: dataToValidate.rejected,
567
- outcomesType: typeof dataToValidate.outcomes,
568
- outcomesValue: dataToValidate.outcomes,
569
- errorsType: typeof dataToValidate.errors,
570
- errorsIsArray: Array.isArray(dataToValidate.errors),
571
- fullData: JSON.stringify(dataToValidate, null, 2),
572
- });
573
-
574
- // Ensure success field is present (required by schema)
575
- // wrappedResponse.success should be true since we checked it exists
576
- // CRITICAL: Construct from the cloned data to ensure all fields are present
577
- const dataWithSuccess: Record<string, unknown> = {
578
- success: wrappedResponse.success === true,
579
- accepted: dataToValidate.accepted,
580
- rejected: dataToValidate.rejected,
581
- };
582
-
583
- // Add optional fields if present
584
- if (
585
- "outcomes" in dataToValidate &&
586
- dataToValidate.outcomes !== undefined
587
- ) {
588
- dataWithSuccess.outcomes = dataToValidate.outcomes;
589
- }
590
- if ("errors" in dataToValidate && dataToValidate.errors !== undefined) {
591
- dataWithSuccess.errors = dataToValidate.errors;
592
- }
593
-
594
- // CRITICAL: Log what we're validating
595
- console.error(`[AccessControl] 🔍 VALIDATING DATA WITH SUCCESS:`, {
596
- correlationId,
597
- dataWithSuccessKeys: Object.keys(dataWithSuccess),
598
- hasSuccess: "success" in dataWithSuccess,
599
- successValue: dataWithSuccess.success,
600
- hasAccepted: "accepted" in dataWithSuccess,
601
- acceptedValue: dataWithSuccess.accepted,
602
- hasRejected: "rejected" in dataWithSuccess,
603
- rejectedValue: dataWithSuccess.rejected,
604
- hasOutcomes: "outcomes" in dataWithSuccess,
605
- outcomesValue: dataWithSuccess.outcomes,
606
- fullDataWithSuccess: JSON.stringify(dataWithSuccess, null, 2),
607
- });
608
-
609
- // CRITICAL: Ensure all required fields are present before validation
610
- if (
611
- typeof dataWithSuccess.accepted !== "number" ||
612
- typeof dataWithSuccess.rejected !== "number"
613
- ) {
614
- console.error(
615
- `[AccessControl] ❌ MISSING REQUIRED FIELDS AFTER CONSTRUCTION:`,
616
- {
617
- correlationId,
618
- hasAccepted: "accepted" in dataWithSuccess,
619
- acceptedType: typeof dataWithSuccess.accepted,
620
- acceptedValue: dataWithSuccess.accepted,
621
- hasRejected: "rejected" in dataWithSuccess,
622
- rejectedType: typeof dataWithSuccess.rejected,
623
- rejectedValue: dataWithSuccess.rejected,
624
- dataWithSuccessKeys: Object.keys(dataWithSuccess),
625
- fullDataWithSuccess: JSON.stringify(dataWithSuccess, null, 2),
626
- dataToValidateKeys: Object.keys(dataToValidate),
627
- fullDataToValidate: JSON.stringify(dataToValidate, null, 2),
628
- originalResponseData: JSON.stringify(responseData, null, 2),
629
- }
630
- );
631
- throw new AgentShieldAPIError(
632
- "invalid_response",
633
- "Response data missing required fields (accepted/rejected)",
634
- {
635
- responseData,
636
- dataToValidate,
637
- dataWithSuccess,
638
- }
639
- );
640
- }
641
-
642
- const dataParsed =
643
- proofSubmissionResponseSchema.safeParse(dataWithSuccess);
644
- if (dataParsed.success) {
645
- this.config.logger(
646
- `[AccessControl] Proofs submitted successfully (wrapped)`,
647
- {
648
- correlationId,
649
- accepted: dataParsed.data.accepted,
650
- rejected: dataParsed.data.rejected,
651
- }
652
- );
653
- return dataParsed.data;
654
- } else {
655
- // Log validation errors for wrapped response (always log errors)
656
- const validationErrorLog = {
657
- correlationId,
658
- zodErrors: dataParsed.error.errors,
659
- zodErrorDetails: JSON.stringify(dataParsed.error.errors, null, 2),
660
- dataToValidate: JSON.stringify(dataToValidate, null, 2).substring(
661
- 0,
662
- 2000
663
- ),
664
- dataWithSuccess: JSON.stringify(dataWithSuccess, null, 2).substring(
665
- 0,
666
- 2000
667
- ),
668
- dataKeys: Object.keys(dataToValidate),
669
- dataWithSuccessKeys: Object.keys(dataWithSuccess),
670
- originalResponse: JSON.stringify(responseData, null, 2).substring(
671
- 0,
672
- 2000
673
- ),
674
- };
675
- this.config.logger(
676
- `[AccessControl] Wrapped response validation failed`,
677
- validationErrorLog
678
- );
679
- // Also log to console.error for visibility in production
680
- console.error(
681
- `[AccessControl] Wrapped response validation failed`,
682
- validationErrorLog
683
- );
684
- console.error(
685
- `[AccessControl] Original wrapped response:`,
686
- JSON.stringify(responseData, null, 2)
687
- );
688
-
689
- // CRITICAL: Log each zod error individually for easier debugging
690
- console.error(
691
- `[AccessControl] ❌ ZOD VALIDATION FAILED - ${dataParsed.error.errors.length} error(s):`
692
- );
693
- dataParsed.error.errors.forEach((err, idx) => {
694
- const errorDetails: Record<string, unknown> = {
695
- path: err.path.join(".") || "(root)",
696
- message: err.message,
697
- code: err.code,
698
- };
699
- // Only include properties that exist on specific error types
700
- if ("received" in err) errorDetails.received = err.received;
701
- if ("expected" in err) errorDetails.expected = err.expected;
702
- if ("input" in err) errorDetails.input = err.input;
703
- console.error(
704
- `[AccessControl] Error ${idx + 1}:`,
705
- JSON.stringify(errorDetails, null, 2)
706
- );
707
- });
708
- console.error(
709
- `[AccessControl] ❌ Full ZOD errors JSON:`,
710
- JSON.stringify(dataParsed.error.errors, null, 2)
711
- );
712
- // CRITICAL: Throw error instead of falling through to direct parsing
713
- // The response is wrapped, so direct parsing will also fail
714
- throw new AgentShieldAPIError(
715
- "invalid_response",
716
- "Response validation failed",
717
- { zodErrors: dataParsed.error.errors, responseData }
718
- );
719
- }
720
- }
721
-
722
- // Try parsing as direct ProofSubmissionResponse
723
- const parsed = proofSubmissionResponseSchema.safeParse(responseData);
724
- if (!parsed.success) {
725
- // Log detailed validation errors (always log errors)
726
- const validationErrorLog = {
727
- correlationId,
728
- zodErrors: parsed.error.errors,
729
- zodErrorDetails: JSON.stringify(parsed.error.errors, null, 2),
730
- responseData: JSON.stringify(responseData, null, 2),
731
- responseDataType: typeof responseData,
732
- responseKeys: Object.keys(
733
- (responseData as Record<string, unknown>) || {}
734
- ),
735
- httpStatus: httpResponse.status,
736
- httpStatusText: httpResponse.statusText,
737
- };
738
- this.config.logger(
739
- `[AccessControl] Response validation failed`,
740
- validationErrorLog
741
- );
742
- // CRITICAL: Log to console.error with full details for debugging
743
- // This format matches test expectations: single call with message and error object
744
- // This log must include 'Response validation failed' in the message for test compatibility
745
- console.error(`[AccessControl] Response validation failed`, {
746
- zodErrors: parsed.error.errors,
747
- responseData: responseData,
748
- });
749
- // Additional detailed logging for debugging
750
- console.error(
751
- `[AccessControl] Response validation failed`,
752
- validationErrorLog
753
- );
754
-
755
- // CRITICAL: Log each zod error individually for easier debugging
756
- console.error(
757
- `[AccessControl] ❌ ZOD VALIDATION FAILED (direct) - ${parsed.error.errors.length} error(s):`
758
- );
759
- parsed.error.errors.forEach((err, idx) => {
760
- const errorDetails: Record<string, unknown> = {
761
- path: err.path.join(".") || "(root)",
762
- message: err.message,
763
- code: err.code,
764
- };
765
- // Only include properties that exist on specific error types
766
- if ("received" in err) errorDetails.received = err.received;
767
- if ("expected" in err) errorDetails.expected = err.expected;
768
- if ("input" in err) errorDetails.input = err.input;
769
- console.error(`[AccessControl] Error ${idx + 1}:`, errorDetails);
770
- });
771
- console.error(
772
- `[AccessControl] ❌ Full ZOD errors JSON:`,
773
- JSON.stringify(parsed.error.errors, null, 2)
774
- );
775
- console.error(
776
- `[AccessControl] ❌ ACTUAL RESPONSE DATA:`,
777
- JSON.stringify(responseData, null, 2)
778
- );
779
- throw new AgentShieldAPIError(
780
- "invalid_response",
781
- "Response validation failed",
782
- { zodErrors: parsed.error.errors, responseData }
783
- );
784
- }
785
-
786
- this.config.logger(`[AccessControl] Proofs submitted successfully`, {
787
- correlationId,
788
- accepted: parsed.data.accepted,
789
- rejected: parsed.data.rejected,
790
- });
791
-
792
- return parsed.data;
793
- });
794
- }
795
-
796
- /**
797
- * Get current metrics
798
- */
799
- getMetrics(): AccessControlApiServiceMetrics {
800
- return { ...this.metrics };
801
- }
802
-
803
- /**
804
- * Reset metrics
805
- */
806
- resetMetrics(): void {
807
- this.metrics = {
808
- successCount: 0,
809
- errorCount: 0,
810
- retryCount: 0,
811
- };
812
- }
813
-
814
- /**
815
- * Retry logic with exponential backoff
816
- *
817
- * @internal
818
- */
819
- private async retryWithBackoff<T>(
820
- operation: () => Promise<T>,
821
- retryCount = 0
822
- ): Promise<T> {
823
- try {
824
- const result = await operation();
825
- this.metrics.successCount++;
826
- return result;
827
- } catch (error: unknown) {
828
- // Check if error is retryable (5xx status codes)
829
- const isRetryable = this.isRetryableError(error);
830
- const { maxRetries, initialDelayMs, maxDelayMs } =
831
- this.config.retryConfig;
832
-
833
- if (isRetryable && retryCount < maxRetries) {
834
- const delay = Math.min(
835
- initialDelayMs * Math.pow(2, retryCount),
836
- maxDelayMs
837
- );
838
-
839
- this.metrics.retryCount++;
840
- this.config.logger(
841
- `Retrying after ${delay}ms (attempt ${retryCount + 1}/${maxRetries})`
842
- );
843
-
844
- await this.sleep(delay);
845
- return this.retryWithBackoff(operation, retryCount + 1);
846
- }
847
-
848
- this.metrics.errorCount++;
849
- throw error;
850
- }
851
- }
852
-
853
- /**
854
- * Check if an error is retryable (5xx status codes)
855
- *
856
- * @internal
857
- */
858
- private isRetryableError(error: unknown): boolean {
859
- // Network errors (fetch failures) are retryable
860
- if (error instanceof TypeError && error.message.includes("fetch")) {
861
- return true;
862
- }
863
-
864
- // AgentShieldAPIError with 5xx status codes are retryable
865
- if (error instanceof AgentShieldAPIError) {
866
- // Check error code for server errors
867
- if (error.code === "server_error") {
868
- return true;
869
- }
870
-
871
- // Check if error details contain status code
872
- const status = (error.details as { status?: number } | undefined)?.status;
873
- if (status && status >= 500 && status < 600) {
874
- return true;
875
- }
876
- // Rate limiting (429) is also retryable
877
- if (status === 429) {
878
- return true;
879
- }
880
- }
881
-
882
- // Check for timeout errors
883
- if (error instanceof Error) {
884
- const message = error.message.toLowerCase();
885
- if (message.includes("timeout") || message.includes("timed out")) {
886
- return true;
887
- }
888
- }
889
-
890
- return false;
891
- }
892
-
893
- /**
894
- * Sleep utility for retry delays
895
- * Uses platform-agnostic sleep provider
896
- *
897
- * @internal
898
- */
899
- private sleep(ms: number): Promise<void> {
900
- return this.config.sleepProvider(ms);
901
- }
902
-
903
- /**
904
- * Parse response text to JSON with error handling
905
- *
906
- * @internal
907
- */
908
- private parseResponseJSON(response: Response, responseText: string): unknown {
909
- try {
910
- return JSON.parse(responseText);
911
- } catch (error) {
912
- // Handle non-JSON error responses (e.g., plain text "Internal Server Error")
913
- if (!response.ok) {
914
- const errorCode = this.mapStatusToErrorCode(response.status);
915
- throw new AgentShieldAPIError(
916
- errorCode,
917
- `API request failed: ${response.status} ${response.statusText}`,
918
- {
919
- status: response.status,
920
- responseText: responseText.substring(0, 500),
921
- }
922
- );
923
- }
924
- // For success responses that aren't JSON, throw invalid_response error
925
- throw new AgentShieldAPIError(
926
- "invalid_response",
927
- `Failed to parse response: ${error instanceof Error ? error.message : String(error)}`,
928
- { responseText: responseText.substring(0, 500) }
929
- );
930
- }
931
- }
932
-
933
- /**
934
- * Map HTTP status codes to error codes
935
- *
936
- * @internal
937
- */
938
- private mapStatusToErrorCode(status: number, statusText = ""): string {
939
- if (status === 400) {
940
- return "validation_error";
941
- } else if (status === 401 || status === 403) {
942
- return "authentication_failed";
943
- } else if (status === 404) {
944
- return "config_not_found";
945
- } else if (status >= 500) {
946
- return "server_error";
947
- }
948
- return "api_error";
949
- }
950
-
951
- /**
952
- * Handle error responses and throw appropriate AgentShieldAPIError
953
- *
954
- * @internal
955
- */
956
- private handleErrorResponse(
957
- response: Response,
958
- responseData: unknown
959
- ): never {
960
- const errorData = responseData as {
961
- success?: boolean;
962
- error?: AgentShieldAPIErrorResponse;
963
- };
964
-
965
- if (errorData.error) {
966
- const errorCode = errorData.error.code || "api_error";
967
- // Ensure error details include status code for retry detection
968
- const errorDetails = {
969
- ...(errorData.error.details || {}),
970
- status: response.status,
971
- };
972
- throw new AgentShieldAPIError(
973
- errorCode,
974
- errorData.error.message || `API error: ${response.status}`,
975
- errorDetails
976
- );
977
- }
978
-
979
- // Map status codes to error codes
980
- const errorCode = this.mapStatusToErrorCode(
981
- response.status,
982
- response.statusText
983
- );
984
- throw new AgentShieldAPIError(
985
- errorCode,
986
- `API request failed: ${response.status} ${response.statusText}`,
987
- { status: response.status, responseData }
988
- );
989
- }
990
- }