@kya-os/mcp-i-core 1.3.13 → 1.3.15

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 (255) hide show
  1. package/dist/config/remote-config.js +9 -12
  2. package/dist/runtime/base.d.ts +2 -1
  3. package/dist/runtime/base.js +34 -6
  4. package/dist/services/access-control.service.js +5 -0
  5. package/dist/services/tool-protection.service.js +17 -8
  6. package/package.json +2 -2
  7. package/.turbo/turbo-build.log +0 -4
  8. package/.turbo/turbo-test$colon$coverage.log +0 -4586
  9. package/.turbo/turbo-test.log +0 -4631
  10. package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
  11. package/Composer 3.md +0 -615
  12. package/GPT-5.md +0 -1169
  13. package/OPUS-plan.md +0 -352
  14. package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
  15. package/PHASE_3_SUMMARY.md +0 -317
  16. package/PHASE_4.1.3_SUMMARY.md +0 -428
  17. package/PHASE_4.1_COMPLETE.md +0 -525
  18. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
  19. package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
  20. package/TEST_PLAN.md +0 -571
  21. package/coverage/coverage-final.json +0 -60
  22. package/dist/cache/oauth-config-cache.d.ts.map +0 -1
  23. package/dist/cache/oauth-config-cache.js.map +0 -1
  24. package/dist/cache/tool-protection-cache.d.ts.map +0 -1
  25. package/dist/cache/tool-protection-cache.js.map +0 -1
  26. package/dist/compliance/index.d.ts.map +0 -1
  27. package/dist/compliance/index.js.map +0 -1
  28. package/dist/compliance/schema-registry.d.ts.map +0 -1
  29. package/dist/compliance/schema-registry.js.map +0 -1
  30. package/dist/compliance/schema-verifier.d.ts.map +0 -1
  31. package/dist/compliance/schema-verifier.js.map +0 -1
  32. package/dist/config/remote-config.d.ts.map +0 -1
  33. package/dist/config/remote-config.js.map +0 -1
  34. package/dist/config.d.ts.map +0 -1
  35. package/dist/config.js.map +0 -1
  36. package/dist/delegation/audience-validator.d.ts.map +0 -1
  37. package/dist/delegation/audience-validator.js.map +0 -1
  38. package/dist/delegation/bitstring.d.ts.map +0 -1
  39. package/dist/delegation/bitstring.js.map +0 -1
  40. package/dist/delegation/cascading-revocation.d.ts.map +0 -1
  41. package/dist/delegation/cascading-revocation.js.map +0 -1
  42. package/dist/delegation/delegation-graph.d.ts.map +0 -1
  43. package/dist/delegation/delegation-graph.js.map +0 -1
  44. package/dist/delegation/did-key-resolver.d.ts.map +0 -1
  45. package/dist/delegation/did-key-resolver.js.map +0 -1
  46. package/dist/delegation/index.d.ts.map +0 -1
  47. package/dist/delegation/index.js.map +0 -1
  48. package/dist/delegation/statuslist-manager.d.ts.map +0 -1
  49. package/dist/delegation/statuslist-manager.js.map +0 -1
  50. package/dist/delegation/storage/index.d.ts.map +0 -1
  51. package/dist/delegation/storage/index.js.map +0 -1
  52. package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
  53. package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
  54. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
  55. package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
  56. package/dist/delegation/utils.d.ts.map +0 -1
  57. package/dist/delegation/utils.js.map +0 -1
  58. package/dist/delegation/vc-issuer.d.ts.map +0 -1
  59. package/dist/delegation/vc-issuer.js.map +0 -1
  60. package/dist/delegation/vc-verifier.d.ts.map +0 -1
  61. package/dist/delegation/vc-verifier.js.map +0 -1
  62. package/dist/identity/idp-token-resolver.d.ts.map +0 -1
  63. package/dist/identity/idp-token-resolver.js.map +0 -1
  64. package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
  65. package/dist/identity/idp-token-storage.interface.js.map +0 -1
  66. package/dist/identity/user-did-manager.d.ts.map +0 -1
  67. package/dist/identity/user-did-manager.js.map +0 -1
  68. package/dist/index.d.ts.map +0 -1
  69. package/dist/index.js.map +0 -1
  70. package/dist/providers/base.d.ts.map +0 -1
  71. package/dist/providers/base.js.map +0 -1
  72. package/dist/providers/memory.d.ts.map +0 -1
  73. package/dist/providers/memory.js.map +0 -1
  74. package/dist/runtime/audit-logger.d.ts.map +0 -1
  75. package/dist/runtime/audit-logger.js.map +0 -1
  76. package/dist/runtime/base.d.ts.map +0 -1
  77. package/dist/runtime/base.js.map +0 -1
  78. package/dist/services/access-control.service.d.ts.map +0 -1
  79. package/dist/services/access-control.service.js.map +0 -1
  80. package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
  81. package/dist/services/authorization/authorization-registry.js.map +0 -1
  82. package/dist/services/authorization/types.d.ts.map +0 -1
  83. package/dist/services/authorization/types.js.map +0 -1
  84. package/dist/services/batch-delegation.service.d.ts.map +0 -1
  85. package/dist/services/batch-delegation.service.js.map +0 -1
  86. package/dist/services/crypto.service.d.ts.map +0 -1
  87. package/dist/services/crypto.service.js.map +0 -1
  88. package/dist/services/errors.d.ts.map +0 -1
  89. package/dist/services/errors.js.map +0 -1
  90. package/dist/services/index.d.ts.map +0 -1
  91. package/dist/services/index.js.map +0 -1
  92. package/dist/services/oauth-config.service.d.ts.map +0 -1
  93. package/dist/services/oauth-config.service.js.map +0 -1
  94. package/dist/services/oauth-provider-registry.d.ts.map +0 -1
  95. package/dist/services/oauth-provider-registry.js.map +0 -1
  96. package/dist/services/oauth-service.d.ts.map +0 -1
  97. package/dist/services/oauth-service.js.map +0 -1
  98. package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
  99. package/dist/services/oauth-token-retrieval.service.js.map +0 -1
  100. package/dist/services/proof-verifier.d.ts.map +0 -1
  101. package/dist/services/proof-verifier.js.map +0 -1
  102. package/dist/services/provider-resolver.d.ts.map +0 -1
  103. package/dist/services/provider-resolver.js.map +0 -1
  104. package/dist/services/provider-validator.d.ts.map +0 -1
  105. package/dist/services/provider-validator.js.map +0 -1
  106. package/dist/services/session-registration.service.d.ts.map +0 -1
  107. package/dist/services/session-registration.service.js.map +0 -1
  108. package/dist/services/storage.service.d.ts.map +0 -1
  109. package/dist/services/storage.service.js.map +0 -1
  110. package/dist/services/tool-context-builder.d.ts.map +0 -1
  111. package/dist/services/tool-context-builder.js.map +0 -1
  112. package/dist/services/tool-protection.service.d.ts.map +0 -1
  113. package/dist/services/tool-protection.service.js.map +0 -1
  114. package/dist/types/oauth-required-error.d.ts.map +0 -1
  115. package/dist/types/oauth-required-error.js.map +0 -1
  116. package/dist/types/tool-protection.d.ts.map +0 -1
  117. package/dist/types/tool-protection.js.map +0 -1
  118. package/dist/utils/base58.d.ts.map +0 -1
  119. package/dist/utils/base58.js.map +0 -1
  120. package/dist/utils/base64.d.ts.map +0 -1
  121. package/dist/utils/base64.js.map +0 -1
  122. package/dist/utils/cors.d.ts.map +0 -1
  123. package/dist/utils/cors.js.map +0 -1
  124. package/dist/utils/did-helpers.d.ts.map +0 -1
  125. package/dist/utils/did-helpers.js.map +0 -1
  126. package/dist/utils/index.d.ts.map +0 -1
  127. package/dist/utils/index.js.map +0 -1
  128. package/dist/utils/storage-keys.d.ts.map +0 -1
  129. package/dist/utils/storage-keys.js.map +0 -1
  130. package/docs/API_REFERENCE.md +0 -1362
  131. package/docs/COMPLIANCE_MATRIX.md +0 -691
  132. package/docs/STATUSLIST2021_GUIDE.md +0 -696
  133. package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
  134. package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
  135. package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
  136. package/src/__tests__/delegation-e2e.test.ts +0 -690
  137. package/src/__tests__/identity/user-did-manager.test.ts +0 -232
  138. package/src/__tests__/index.test.ts +0 -56
  139. package/src/__tests__/integration/full-flow.test.ts +0 -789
  140. package/src/__tests__/integration.test.ts +0 -281
  141. package/src/__tests__/providers/base.test.ts +0 -173
  142. package/src/__tests__/providers/memory.test.ts +0 -319
  143. package/src/__tests__/regression/phase2-regression.test.ts +0 -429
  144. package/src/__tests__/runtime/audit-logger.test.ts +0 -154
  145. package/src/__tests__/runtime/base-extensions.test.ts +0 -595
  146. package/src/__tests__/runtime/base.test.ts +0 -869
  147. package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
  148. package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
  149. package/src/__tests__/runtime/route-interception.test.ts +0 -686
  150. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
  151. package/src/__tests__/services/agentshield-integration.test.ts +0 -791
  152. package/src/__tests__/services/cache-busting.test.ts +0 -125
  153. package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
  154. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
  155. package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
  156. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
  157. package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
  158. package/src/__tests__/utils/mock-providers.ts +0 -340
  159. package/src/cache/oauth-config-cache.d.ts +0 -69
  160. package/src/cache/oauth-config-cache.d.ts.map +0 -1
  161. package/src/cache/oauth-config-cache.js.map +0 -1
  162. package/src/cache/oauth-config-cache.ts +0 -123
  163. package/src/cache/tool-protection-cache.ts +0 -171
  164. package/src/compliance/EXAMPLE.md +0 -412
  165. package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
  166. package/src/compliance/index.ts +0 -8
  167. package/src/compliance/schema-registry.ts +0 -460
  168. package/src/compliance/schema-verifier.ts +0 -708
  169. package/src/config/__tests__/merged-config.spec.ts +0 -445
  170. package/src/config/__tests__/remote-config.spec.ts +0 -268
  171. package/src/config/remote-config.ts +0 -264
  172. package/src/config.ts +0 -312
  173. package/src/delegation/__tests__/audience-validator.test.ts +0 -112
  174. package/src/delegation/__tests__/bitstring.test.ts +0 -346
  175. package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
  176. package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
  177. package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
  178. package/src/delegation/__tests__/utils.test.ts +0 -152
  179. package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
  180. package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
  181. package/src/delegation/audience-validator.ts +0 -52
  182. package/src/delegation/bitstring.ts +0 -278
  183. package/src/delegation/cascading-revocation.ts +0 -370
  184. package/src/delegation/delegation-graph.ts +0 -299
  185. package/src/delegation/did-key-resolver.ts +0 -179
  186. package/src/delegation/index.ts +0 -14
  187. package/src/delegation/statuslist-manager.ts +0 -353
  188. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
  189. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
  190. package/src/delegation/storage/index.ts +0 -9
  191. package/src/delegation/storage/memory-graph-storage.ts +0 -178
  192. package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
  193. package/src/delegation/utils.ts +0 -221
  194. package/src/delegation/vc-issuer.ts +0 -232
  195. package/src/delegation/vc-verifier.ts +0 -568
  196. package/src/identity/idp-token-resolver.ts +0 -181
  197. package/src/identity/idp-token-storage.interface.ts +0 -94
  198. package/src/identity/user-did-manager.ts +0 -526
  199. package/src/index.ts +0 -310
  200. package/src/providers/base.d.ts +0 -91
  201. package/src/providers/base.d.ts.map +0 -1
  202. package/src/providers/base.js.map +0 -1
  203. package/src/providers/base.ts +0 -96
  204. package/src/providers/memory.ts +0 -142
  205. package/src/runtime/audit-logger.ts +0 -39
  206. package/src/runtime/base.ts +0 -1392
  207. package/src/services/__tests__/access-control.integration.test.ts +0 -443
  208. package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
  209. package/src/services/__tests__/access-control.service.test.ts +0 -970
  210. package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
  211. package/src/services/__tests__/crypto.service.test.ts +0 -531
  212. package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
  213. package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
  214. package/src/services/__tests__/proof-verifier.test.ts +0 -489
  215. package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
  216. package/src/services/__tests__/provider-resolver.test.ts +0 -213
  217. package/src/services/__tests__/storage.service.test.ts +0 -358
  218. package/src/services/access-control.service.ts +0 -990
  219. package/src/services/authorization/authorization-registry.ts +0 -66
  220. package/src/services/authorization/types.ts +0 -71
  221. package/src/services/batch-delegation.service.ts +0 -137
  222. package/src/services/crypto.service.ts +0 -302
  223. package/src/services/errors.ts +0 -76
  224. package/src/services/index.ts +0 -18
  225. package/src/services/oauth-config.service.d.ts +0 -53
  226. package/src/services/oauth-config.service.d.ts.map +0 -1
  227. package/src/services/oauth-config.service.js.map +0 -1
  228. package/src/services/oauth-config.service.ts +0 -192
  229. package/src/services/oauth-provider-registry.d.ts +0 -57
  230. package/src/services/oauth-provider-registry.d.ts.map +0 -1
  231. package/src/services/oauth-provider-registry.js.map +0 -1
  232. package/src/services/oauth-provider-registry.ts +0 -141
  233. package/src/services/oauth-service.ts +0 -544
  234. package/src/services/oauth-token-retrieval.service.ts +0 -245
  235. package/src/services/proof-verifier.ts +0 -478
  236. package/src/services/provider-resolver.d.ts +0 -48
  237. package/src/services/provider-resolver.d.ts.map +0 -1
  238. package/src/services/provider-resolver.js.map +0 -1
  239. package/src/services/provider-resolver.ts +0 -146
  240. package/src/services/provider-validator.ts +0 -170
  241. package/src/services/session-registration.service.ts +0 -251
  242. package/src/services/storage.service.ts +0 -566
  243. package/src/services/tool-context-builder.ts +0 -237
  244. package/src/services/tool-protection.service.ts +0 -1070
  245. package/src/types/oauth-required-error.ts +0 -63
  246. package/src/types/tool-protection.ts +0 -155
  247. package/src/utils/__tests__/did-helpers.test.ts +0 -156
  248. package/src/utils/base58.ts +0 -109
  249. package/src/utils/base64.ts +0 -148
  250. package/src/utils/cors.ts +0 -83
  251. package/src/utils/did-helpers.ts +0 -210
  252. package/src/utils/index.ts +0 -8
  253. package/src/utils/storage-keys.ts +0 -278
  254. package/tsconfig.json +0 -21
  255. 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
- }