@kya-os/mcp-i-core 1.3.10-canary.clientinfo.20251126124133 → 1.3.10

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 (64) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/__tests__/utils/mock-providers.d.ts +2 -1
  3. package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
  4. package/dist/__tests__/utils/mock-providers.js.map +1 -1
  5. package/dist/config/remote-config.d.ts +51 -0
  6. package/dist/config/remote-config.d.ts.map +1 -1
  7. package/dist/config/remote-config.js +74 -0
  8. package/dist/config/remote-config.js.map +1 -1
  9. package/dist/config.d.ts +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +4 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/delegation/did-key-resolver.d.ts +64 -0
  14. package/dist/delegation/did-key-resolver.d.ts.map +1 -0
  15. package/dist/delegation/did-key-resolver.js +159 -0
  16. package/dist/delegation/did-key-resolver.js.map +1 -0
  17. package/dist/delegation/utils.d.ts +76 -0
  18. package/dist/delegation/utils.d.ts.map +1 -1
  19. package/dist/delegation/utils.js +117 -0
  20. package/dist/delegation/utils.js.map +1 -1
  21. package/dist/identity/user-did-manager.d.ts +95 -12
  22. package/dist/identity/user-did-manager.d.ts.map +1 -1
  23. package/dist/identity/user-did-manager.js +107 -25
  24. package/dist/identity/user-did-manager.js.map +1 -1
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +23 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/runtime/base.d.ts +25 -8
  30. package/dist/runtime/base.d.ts.map +1 -1
  31. package/dist/runtime/base.js +74 -21
  32. package/dist/runtime/base.js.map +1 -1
  33. package/dist/services/session-registration.service.d.ts.map +1 -1
  34. package/dist/services/session-registration.service.js +10 -90
  35. package/dist/services/session-registration.service.js.map +1 -1
  36. package/dist/services/tool-protection.service.d.ts +5 -2
  37. package/dist/services/tool-protection.service.d.ts.map +1 -1
  38. package/dist/services/tool-protection.service.js +72 -24
  39. package/dist/services/tool-protection.service.js.map +1 -1
  40. package/dist/utils/base58.d.ts +31 -0
  41. package/dist/utils/base58.d.ts.map +1 -0
  42. package/dist/utils/base58.js +103 -0
  43. package/dist/utils/base58.js.map +1 -0
  44. package/package.json +3 -3
  45. package/src/__tests__/identity/user-did-manager.test.ts +64 -45
  46. package/src/__tests__/integration/full-flow.test.ts +23 -10
  47. package/src/__tests__/runtime/base-extensions.test.ts +23 -21
  48. package/src/__tests__/runtime/proof-client-did.test.ts +19 -18
  49. package/src/__tests__/services/agentshield-integration.test.ts +10 -3
  50. package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
  51. package/src/__tests__/services/tool-protection.service.test.ts +18 -11
  52. package/src/config/__tests__/merged-config.spec.ts +445 -0
  53. package/src/config/remote-config.ts +90 -0
  54. package/src/config.ts +3 -0
  55. package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
  56. package/src/delegation/did-key-resolver.ts +179 -0
  57. package/src/delegation/utils.ts +179 -0
  58. package/src/identity/user-did-manager.ts +185 -29
  59. package/src/index.ts +36 -1
  60. package/src/runtime/base.ts +84 -21
  61. package/src/services/session-registration.service.ts +26 -121
  62. package/src/services/tool-protection.service.ts +125 -56
  63. package/src/utils/base58.ts +109 -0
  64. package/coverage/coverage-final.json +0 -57
@@ -109,55 +109,9 @@ export class SessionRegistrationService {
109
109
  sessionId,
110
110
  agentDid: request.agent_did,
111
111
  clientName: request.client_info.name,
112
- clientVersion: request.client_info.version,
113
- hasClientIdentity: !!request.client_identity,
114
112
  url,
115
113
  });
116
114
 
117
- // ✅ EMPIRICAL PROOF: Prepare request headers
118
- // Try Authorization: Bearer first (same as proofs endpoint)
119
- // If that fails, AgentShield may need X-AgentShield-Key instead
120
- const requestHeaders = {
121
- "Content-Type": "application/json",
122
- Authorization: `Bearer ${this.config.apiKey}`, // Use same auth as proofs endpoint
123
- };
124
-
125
- // ✅ EMPIRICAL PROOF: Log exact request details (sanitized for security)
126
- const sanitizedHeaders = {
127
- ...requestHeaders,
128
- Authorization: `Bearer ${this.config.apiKey.slice(0, 8)}...${this.config.apiKey.slice(-4)}`, // Show first 8 and last 4 chars
129
- };
130
-
131
- const sanitizedBody = {
132
- session_id: request.session_id,
133
- agent_did: request.agent_did,
134
- project_id: request.project_id,
135
- created_at: request.created_at,
136
- client_info: request.client_info,
137
- client_identity: request.client_identity,
138
- server_did: request.server_did,
139
- ttl_minutes: request.ttl_minutes,
140
- };
141
-
142
- this.config.logger(
143
- "[SessionRegistration] 🔍 EMPIRICAL DEBUG - Request details",
144
- {
145
- url,
146
- method: "POST",
147
- headers: sanitizedHeaders,
148
- headerKeys: Object.keys(requestHeaders),
149
- authHeaderPresent: !!requestHeaders["Authorization"],
150
- authHeaderName: "Authorization",
151
- authHeaderFormat: requestHeaders["Authorization"]?.startsWith(
152
- "Bearer "
153
- )
154
- ? "Bearer"
155
- : "Other",
156
- body: sanitizedBody,
157
- bodySize: JSON.stringify(request).length,
158
- }
159
- );
160
-
161
115
  // Make the request with timeout
162
116
  const controller = new AbortController();
163
117
  const timeoutId = setTimeout(
@@ -168,89 +122,47 @@ export class SessionRegistrationService {
168
122
  try {
169
123
  const response = await this.config.fetchProvider.fetch(url, {
170
124
  method: "POST",
171
- headers: requestHeaders,
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Authorization: `Bearer ${this.config.apiKey}`,
128
+ },
172
129
  body: JSON.stringify(request),
173
130
  signal: controller.signal,
174
131
  });
175
132
 
176
133
  clearTimeout(timeoutId);
177
134
 
178
- // ✅ EMPIRICAL PROOF: Capture exact response details
179
- const responseHeaders: Record<string, string> = {};
180
- response.headers.forEach((value, key) => {
181
- responseHeaders[key] = value;
182
- });
183
-
184
- const responseText = await response
185
- .text()
186
- .catch(() => "Failed to read response");
187
- let responseBody: unknown;
188
- try {
189
- responseBody = JSON.parse(responseText);
190
- } catch {
191
- responseBody = responseText;
192
- }
193
-
194
- // ✅ EMPIRICAL PROOF: Log exact response details
195
- this.config.logger(
196
- "[SessionRegistration] 🔍 EMPIRICAL DEBUG - Response details",
197
- {
198
- status: response.status,
199
- statusText: response.statusText,
200
- ok: response.ok,
201
- headers: responseHeaders,
202
- body: responseBody,
203
- bodyLength: responseText.length,
204
- clientName: request.client_info.name,
205
- // ✅ DEBUG: Include request ID from response headers for AgentShield support
206
- requestId:
207
- responseHeaders["x-request-id"] ||
208
- responseHeaders["X-Request-ID"] ||
209
- "not-provided",
210
- // ✅ DEBUG: Full response text for AgentShield team
211
- fullResponseText: responseText,
212
- }
213
- );
214
-
215
135
  if (!response.ok) {
216
136
  // Log error but don't throw - this is fire-and-forget
137
+ const errorText = await response.text().catch(() => "Unknown error");
217
138
  this.config.logger("[SessionRegistration] Registration failed", {
218
139
  sessionId,
219
140
  status: response.status,
220
- error: responseText,
221
- // ✅ DEBUG: EMPIRICAL: Include full response details
222
- responseHeaders,
223
- responseBody,
224
- clientName: request.client_info.name,
225
- // ✅ DEBUG: Request ID for AgentShield support ticket
226
- requestId:
227
- responseHeaders["x-request-id"] ||
228
- responseHeaders["X-Request-ID"] ||
229
- "not-provided",
230
- // ✅ DEBUG: Full request body for AgentShield team
231
- fullRequestBody: JSON.stringify(request, null, 2),
141
+ error: errorText,
232
142
  });
233
143
  return {
234
144
  success: false,
235
145
  sessionId,
236
- error: `HTTP ${response.status}: ${responseText}`,
146
+ error: `HTTP ${response.status}: ${errorText}`,
237
147
  };
238
148
  }
239
149
 
240
- // Parse response (using already-captured responseBody)
241
- const responseData = responseBody as
242
- | { data?: RegisterSessionResponse }
243
- | RegisterSessionResponse;
150
+ // Parse response
151
+ const responseData = (await response.json()) as {
152
+ data?: RegisterSessionResponse;
153
+ } & RegisterSessionResponse;
244
154
  const parseResult = registerSessionResponseSchema.safeParse(
245
- (responseData as { data?: RegisterSessionResponse }).data ||
246
- responseData
155
+ responseData.data || responseData
247
156
  );
248
157
 
249
158
  if (!parseResult.success) {
250
- this.config.logger("[SessionRegistration] Invalid response format", {
251
- sessionId,
252
- response: responseData,
253
- });
159
+ this.config.logger(
160
+ "[SessionRegistration] Invalid response format",
161
+ {
162
+ sessionId,
163
+ response: responseData,
164
+ }
165
+ );
254
166
  // Still consider it a success if we got a 200 OK
255
167
  return { success: true, sessionId };
256
168
  }
@@ -258,12 +170,6 @@ export class SessionRegistrationService {
258
170
  this.config.logger("[SessionRegistration] Session registered", {
259
171
  sessionId,
260
172
  registered: parseResult.data.registered,
261
- // ✅ DEBUG: Include full response for AgentShield debugging
262
- responseData: parseResult.data,
263
- requestId:
264
- responseHeaders["x-request-id"] ||
265
- responseHeaders["X-Request-ID"] ||
266
- "not-provided",
267
173
  });
268
174
 
269
175
  return { success: true, sessionId };
@@ -281,7 +187,8 @@ export class SessionRegistrationService {
281
187
  }
282
188
 
283
189
  // Log any other error
284
- const errorMsg = error instanceof Error ? error.message : "Unknown error";
190
+ const errorMsg =
191
+ error instanceof Error ? error.message : "Unknown error";
285
192
  this.config.logger("[SessionRegistration] Unexpected error", {
286
193
  sessionId,
287
194
  error: errorMsg,
@@ -303,13 +210,10 @@ export class SessionRegistrationService {
303
210
  this.registerSession(request).catch((error) => {
304
211
  // This should never happen since registerSession catches all errors,
305
212
  // but just in case
306
- this.config.logger(
307
- "[SessionRegistration] Background registration failed",
308
- {
309
- sessionId: request.session_id,
310
- error: error instanceof Error ? error.message : "Unknown error",
311
- }
312
- );
213
+ this.config.logger("[SessionRegistration] Background registration failed", {
214
+ sessionId: request.session_id,
215
+ error: error instanceof Error ? error.message : "Unknown error",
216
+ });
313
217
  });
314
218
  }
315
219
  }
@@ -344,3 +248,4 @@ export function createSessionRegistrationService(options: {
344
248
  logger: options.logger,
345
249
  });
346
250
  }
251
+
@@ -46,7 +46,7 @@
46
46
  * If tool not discovered:
47
47
  * - Tool won't appear in dashboard
48
48
  * - Protection settings can't be configured
49
- * - GET /tool-protections returns empty object
49
+ * - GET /config returns empty toolProtection.tools object
50
50
  *
51
51
  * DEBUGGING:
52
52
  * ----------
@@ -87,34 +87,56 @@ import type {
87
87
  import type { ToolProtectionCache } from "../cache/tool-protection-cache.js";
88
88
  import { InMemoryToolProtectionCache } from "../cache/tool-protection-cache.js";
89
89
 
90
+ /**
91
+ * Tool protection data structure in API responses
92
+ */
93
+ interface ToolProtectionData {
94
+ requiresDelegation?: boolean;
95
+ requires_delegation?: boolean;
96
+ requiredScopes?: string[];
97
+ required_scopes?: string[];
98
+ scopes?: string[];
99
+ riskLevel?: string;
100
+ risk_level?: string;
101
+ oauthProvider?: string;
102
+ oauth_provider?: string;
103
+ authorization?: {
104
+ type: string;
105
+ provider?: string;
106
+ issuer?: string;
107
+ credentialType?: string;
108
+ };
109
+ }
110
+
90
111
  /**
91
112
  * Response from AgentShield API bouncer endpoints
92
113
  *
93
114
  * Supports multiple endpoint formats:
94
- * 1. New endpoint (/projects/{projectId}/tool-protections): { data: { toolProtections: { [toolName]: {...} } } } }
95
- * 2. Old endpoint (/config?agent_did=...): { data: { tools: [{ name: string, ... }] } }
96
- * 3. Legacy format: { data: { tools: { [toolName]: {...} } } }
115
+ * 1. Merged config (/projects/{projectId}/config): { data: { config: { toolProtection: { tools: {...} } } } }
116
+ * 2. Legacy tool-protections endpoint: { data: { toolProtections: { [toolName]: {...} } } }
117
+ * 3. Old config endpoint (/config?agent_did=...): { data: { tools: [{ name: string, ... }] } }
118
+ * 4. Legacy format: { data: { tools: { [toolName]: {...} } } }
97
119
  */
98
120
  interface BouncerConfigApiResponse {
99
121
  success: boolean;
100
122
  data: {
101
123
  agent_did?: string;
102
- // New endpoint format: toolProtections object
103
- toolProtections?: Record<
104
- string,
105
- {
106
- requiresDelegation?: boolean;
107
- requires_delegation?: boolean;
108
- requiredScopes?: string[];
109
- required_scopes?: string[];
110
- scopes?: string[];
111
- riskLevel?: string;
112
- risk_level?: string;
113
- oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
114
- oauth_provider?: string; // Phase 2: snake_case variant
115
- }
116
- >;
117
- // Old endpoint format: tools array or object
124
+
125
+ // NEW: Merged config format (v1.6.0+) - preferred format
126
+ // The entire config is returned with tools embedded at config.toolProtection.tools
127
+ config?: {
128
+ toolProtection?: {
129
+ source?: string;
130
+ tools?: Record<string, ToolProtectionData>;
131
+ };
132
+ // Other config fields we don't need to parse
133
+ [key: string]: unknown;
134
+ };
135
+
136
+ // DEPRECATED: Top-level toolProtections (backward compatibility during transition)
137
+ toolProtections?: Record<string, ToolProtectionData>;
138
+
139
+ // Legacy endpoint formats
118
140
  tools?:
119
141
  | Array<{
120
142
  name: string;
@@ -122,20 +144,10 @@ interface BouncerConfigApiResponse {
122
144
  requires_delegation?: boolean;
123
145
  scopes?: string[];
124
146
  required_scopes?: string[];
125
- oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
126
- oauth_provider?: string; // Phase 2: snake_case variant
147
+ oauthProvider?: string;
148
+ oauth_provider?: string;
127
149
  }>
128
- | Record<
129
- string,
130
- {
131
- requiresDelegation?: boolean;
132
- requires_delegation?: boolean;
133
- scopes?: string[];
134
- required_scopes?: string[];
135
- oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
136
- oauth_provider?: string; // Phase 2: snake_case variant
137
- }
138
- >;
150
+ | Record<string, ToolProtectionData>;
139
151
  reputation_threshold?: number;
140
152
  denied_agents?: string[];
141
153
  };
@@ -271,7 +283,7 @@ export class ToolProtectionService {
271
283
  projectId: this.config.projectId || "none",
272
284
  apiUrl: this.config.apiUrl,
273
285
  endpoint: this.config.projectId
274
- ? `/api/v1/bouncer/projects/${this.config.projectId}/tool-protections`
286
+ ? `/api/v1/bouncer/projects/${this.config.projectId}/config`
275
287
  : `/api/v1/bouncer/config?agent_did=${agentDid}`,
276
288
  });
277
289
  }
@@ -287,6 +299,8 @@ export class ToolProtectionService {
287
299
  projectId: this.config.projectId || "none",
288
300
  responseKeys: Object.keys(response),
289
301
  dataKeys: response.data ? Object.keys(response.data) : [],
302
+ rawConfig: response.data?.config || null,
303
+ rawConfigToolProtection: response.data?.config?.toolProtection || null,
290
304
  rawToolProtections: response.data?.toolProtections || null,
291
305
  rawTools: response.data?.tools || null,
292
306
  responseMetadata: response.metadata || null,
@@ -294,15 +308,54 @@ export class ToolProtectionService {
294
308
  }
295
309
 
296
310
  // Transform API response format to internal format
297
- // Supports multiple response formats:
298
- // 1. New endpoint: { data: { toolProtections: { greet: { requiresDelegation: true, ... } } } }
299
- // 2. Old endpoint (array): { data: { tools: [{ name: "greet", requiresDelegation: true, ... }] } }
300
- // 3. Old endpoint (object): { data: { tools: { greet: { requiresDelegation: true, ... } } } }
311
+ // Supports multiple response formats (in priority order):
312
+ // 1. Merged config endpoint: { data: { config: { toolProtection: { tools: {...} } } } }
313
+ // 2. Legacy toolProtections: { data: { toolProtections: { greet: { requiresDelegation: true, ... } } } }
314
+ // 3. Old endpoint (array): { data: { tools: [{ name: "greet", requiresDelegation: true, ... }] } }
315
+ // 4. Old endpoint (object): { data: { tools: { greet: { requiresDelegation: true, ... } } } }
301
316
  const toolProtections: Record<string, ToolProtection> = {};
302
317
 
303
- // Check for new endpoint format first (toolProtections)
304
- if (response.data.toolProtections) {
305
- // New endpoint format: object with tool names as keys
318
+ // Check for merged config format first (data.config.toolProtection.tools)
319
+ if (response.data.config?.toolProtection?.tools) {
320
+ // Merged config endpoint format: object with tool names as keys
321
+ if (this.config.debug) {
322
+ console.log("[ToolProtectionService] Using merged config format (data.config.toolProtection.tools)");
323
+ }
324
+ for (const [toolName, toolConfig] of Object.entries(
325
+ response.data.config.toolProtection.tools
326
+ )) {
327
+ const requiresDelegation =
328
+ (toolConfig as any).requiresDelegation ??
329
+ (toolConfig as any).requires_delegation ??
330
+ false;
331
+ const requiredScopes =
332
+ (toolConfig as any).requiredScopes ??
333
+ (toolConfig as any).required_scopes ??
334
+ (toolConfig as any).scopes ??
335
+ [];
336
+
337
+ const oauthProvider =
338
+ (toolConfig as any).oauthProvider ??
339
+ (toolConfig as any).oauth_provider ??
340
+ undefined;
341
+
342
+ const riskLevel =
343
+ (toolConfig as any).riskLevel ??
344
+ (toolConfig as any).risk_level ??
345
+ undefined;
346
+
347
+ toolProtections[toolName] = {
348
+ requiresDelegation,
349
+ requiredScopes,
350
+ ...(oauthProvider && { oauthProvider }),
351
+ ...(riskLevel && { riskLevel }),
352
+ };
353
+ }
354
+ } else if (response.data.toolProtections) {
355
+ // Legacy toolProtections format: object with tool names as keys
356
+ if (this.config.debug) {
357
+ console.log("[ToolProtectionService] Using legacy toolProtections format (data.toolProtections)");
358
+ }
306
359
  // Prefer camelCase over snake_case when both present
307
360
  for (const [toolName, toolConfig] of Object.entries(
308
361
  response.data.toolProtections
@@ -665,7 +718,10 @@ export class ToolProtectionService {
665
718
 
666
719
  /**
667
720
  * Fetch tool protection config from AgentShield API
668
- * Uses projectId endpoint if available (preferred, project-scoped), otherwise falls back to agent_did query param
721
+ *
722
+ * Uses the merged /config endpoint which returns tool protections embedded
723
+ * at config.toolProtection.tools. Falls back to legacy formats for backward
724
+ * compatibility.
669
725
  *
670
726
  * @param agentDid DID of the agent to fetch config for
671
727
  * @param options Optional fetch options
@@ -675,17 +731,17 @@ export class ToolProtectionService {
675
731
  agentDid: string,
676
732
  options?: { bypassCDNCache?: boolean }
677
733
  ): Promise<BouncerConfigApiResponse> {
678
- // Prefer new project-scoped endpoint: /api/v1/bouncer/projects/{projectId}/tool-protections
679
- // Falls back to old endpoint: /api/v1/bouncer/config?agent_did={did} for backward compatibility
734
+ // Use the merged /config endpoint which includes embedded tool protections
735
+ // This endpoint returns config.toolProtection.tools with all tool rules
680
736
  let url: string;
681
- let useNewEndpoint = false;
737
+ let useMergedEndpoint = false;
682
738
 
683
739
  if (this.config.projectId) {
684
- // ✅ NEW ENDPOINT: Project-scoped, returns toolProtections object
685
- url = `${this.config.apiUrl}/api/v1/bouncer/projects/${encodeURIComponent(this.config.projectId)}/tool-protections`;
686
- useNewEndpoint = true;
740
+ // ✅ MERGED CONFIG ENDPOINT: Returns config with embedded toolProtection.tools
741
+ url = `${this.config.apiUrl}/api/v1/bouncer/projects/${encodeURIComponent(this.config.projectId)}/config`;
742
+ useMergedEndpoint = true;
687
743
  } else {
688
- // ⚠️ OLD ENDPOINT: Agent-scoped, returns tools array (backward compatibility)
744
+ // ⚠️ LEGACY ENDPOINT: Agent-scoped, returns tools array (backward compatibility)
689
745
  url = `${this.config.apiUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(agentDid)}`;
690
746
  }
691
747
 
@@ -712,9 +768,9 @@ export class ToolProtectionService {
712
768
 
713
769
  if (this.config.debug) {
714
770
  console.log("[ToolProtectionService] Fetching from API:", url, {
715
- method: useNewEndpoint
716
- ? "projects/{projectId}/tool-protections (new)"
717
- : "config?agent_did (old)",
771
+ method: useMergedEndpoint
772
+ ? "projects/{projectId}/config (merged)"
773
+ : "config?agent_did (legacy)",
718
774
  projectId: this.config.projectId || "none",
719
775
  apiKeyPresent: !!this.config.apiKey,
720
776
  apiKeyLength,
@@ -736,19 +792,19 @@ export class ToolProtectionService {
736
792
  );
737
793
  }
738
794
 
739
- // Build headers - new endpoint uses X-API-Key, old endpoint uses Authorization Bearer
795
+ // Build headers - merged endpoint uses X-API-Key, legacy uses Authorization Bearer
740
796
  const headers: Record<string, string> = {
741
797
  "Content-Type": "application/json",
742
798
  };
743
799
 
744
- if (useNewEndpoint) {
745
- // ✅ New endpoint headers
800
+ if (useMergedEndpoint) {
801
+ // ✅ Merged config endpoint headers
746
802
  headers["X-API-Key"] = this.config.apiKey;
747
803
  if (this.config.projectId) {
748
804
  headers["X-Project-Id"] = this.config.projectId;
749
805
  }
750
806
  } else {
751
- // ⚠️ Old endpoint headers (backward compatibility)
807
+ // ⚠️ Legacy endpoint headers (backward compatibility)
752
808
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
753
809
  }
754
810
 
@@ -786,6 +842,19 @@ export class ToolProtectionService {
786
842
  throw new Error("API returned success: false");
787
843
  }
788
844
 
845
+ // Transform merged config format to normalized format
846
+ // If response contains config.toolProtection.tools, extract them to data.toolProtections
847
+ if (useMergedEndpoint && data.data.config?.toolProtection?.tools) {
848
+ // Extract embedded tools to the standard toolProtections field
849
+ data.data.toolProtections = data.data.config.toolProtection.tools;
850
+ if (this.config.debug) {
851
+ console.log("[ToolProtectionService] Extracted tools from merged config", {
852
+ toolCount: Object.keys(data.data.toolProtections).length,
853
+ tools: Object.keys(data.data.toolProtections),
854
+ });
855
+ }
856
+ }
857
+
789
858
  return data;
790
859
  }
791
860
 
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Base58 Utilities (Bitcoin alphabet)
3
+ *
4
+ * Encoding and decoding utilities for Base58 (Bitcoin alphabet).
5
+ * Used for did:key multibase encoding (with 'z' prefix for base58btc).
6
+ *
7
+ * The Bitcoin alphabet excludes ambiguous characters (0, O, I, l).
8
+ */
9
+
10
+ const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
11
+ const ALPHABET_MAP = new Map<string, number>();
12
+
13
+ // Build reverse lookup map
14
+ for (let i = 0; i < ALPHABET.length; i++) {
15
+ ALPHABET_MAP.set(ALPHABET[i], i);
16
+ }
17
+
18
+ /**
19
+ * Encode bytes to Base58 (Bitcoin alphabet)
20
+ *
21
+ * @param bytes - Bytes to encode
22
+ * @returns Base58-encoded string
23
+ */
24
+ export function base58Encode(bytes: Uint8Array): string {
25
+ if (bytes.length === 0) return '';
26
+
27
+ // Convert bytes to big integer
28
+ let num = BigInt(0);
29
+ for (let i = 0; i < bytes.length; i++) {
30
+ num = num * BigInt(256) + BigInt(bytes[i]);
31
+ }
32
+
33
+ // Convert to base58
34
+ let result = '';
35
+ while (num > 0) {
36
+ result = ALPHABET[Number(num % BigInt(58))] + result;
37
+ num = num / BigInt(58);
38
+ }
39
+
40
+ // Add leading zeros (encoded as '1' in base58)
41
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
42
+ result = '1' + result;
43
+ }
44
+
45
+ return result;
46
+ }
47
+
48
+ /**
49
+ * Decode Base58 (Bitcoin alphabet) to bytes
50
+ *
51
+ * @param encoded - Base58-encoded string
52
+ * @returns Decoded bytes
53
+ * @throws Error if input contains invalid characters
54
+ */
55
+ export function base58Decode(encoded: string): Uint8Array {
56
+ if (encoded.length === 0) return new Uint8Array(0);
57
+
58
+ // Convert base58 to big integer
59
+ let num = BigInt(0);
60
+ for (const char of encoded) {
61
+ const value = ALPHABET_MAP.get(char);
62
+ if (value === undefined) {
63
+ throw new Error(`Invalid base58 character: ${char}`);
64
+ }
65
+ num = num * BigInt(58) + BigInt(value);
66
+ }
67
+
68
+ // Convert big integer to bytes
69
+ const bytes: number[] = [];
70
+ while (num > 0) {
71
+ bytes.unshift(Number(num % BigInt(256)));
72
+ num = num / BigInt(256);
73
+ }
74
+
75
+ // Count leading zeros in input (encoded as '1')
76
+ let leadingZeros = 0;
77
+ for (const char of encoded) {
78
+ if (char === '1') {
79
+ leadingZeros++;
80
+ } else {
81
+ break;
82
+ }
83
+ }
84
+
85
+ // Prepend leading zero bytes
86
+ const result = new Uint8Array(leadingZeros + bytes.length);
87
+ // Leading zeros are already 0 in Uint8Array
88
+ result.set(bytes, leadingZeros);
89
+
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Validate a Base58 string
95
+ *
96
+ * @param encoded - String to validate
97
+ * @returns true if valid Base58, false otherwise
98
+ */
99
+ export function isValidBase58(encoded: string): boolean {
100
+ if (encoded.length === 0) return true;
101
+
102
+ for (const char of encoded) {
103
+ if (!ALPHABET_MAP.has(char)) {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ return true;
109
+ }