@slashfi/agents-sdk 0.31.0 → 0.33.0

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 (52) hide show
  1. package/dist/auth-governance.d.ts +37 -0
  2. package/dist/auth-governance.d.ts.map +1 -0
  3. package/dist/auth-governance.js +73 -0
  4. package/dist/auth-governance.js.map +1 -0
  5. package/dist/call-agent-schema.d.ts +20 -0
  6. package/dist/call-agent-schema.d.ts.map +1 -1
  7. package/dist/call-agent-schema.js +19 -0
  8. package/dist/call-agent-schema.js.map +1 -1
  9. package/dist/cjs/auth-governance.js +79 -0
  10. package/dist/cjs/auth-governance.js.map +1 -0
  11. package/dist/cjs/call-agent-schema.js +20 -1
  12. package/dist/cjs/call-agent-schema.js.map +1 -1
  13. package/dist/cjs/define-config.js +1 -0
  14. package/dist/cjs/define-config.js.map +1 -1
  15. package/dist/cjs/index.js +4 -2
  16. package/dist/cjs/index.js.map +1 -1
  17. package/dist/cjs/key-manager.js +9 -4
  18. package/dist/cjs/key-manager.js.map +1 -1
  19. package/dist/cjs/registry-consumer.js +121 -43
  20. package/dist/cjs/registry-consumer.js.map +1 -1
  21. package/dist/cjs/server.js +149 -209
  22. package/dist/cjs/server.js.map +1 -1
  23. package/dist/define-config.d.ts +8 -0
  24. package/dist/define-config.d.ts.map +1 -1
  25. package/dist/define-config.js +1 -0
  26. package/dist/define-config.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/key-manager.d.ts.map +1 -1
  32. package/dist/key-manager.js +9 -4
  33. package/dist/key-manager.js.map +1 -1
  34. package/dist/registry-consumer.d.ts +4 -0
  35. package/dist/registry-consumer.d.ts.map +1 -1
  36. package/dist/registry-consumer.js +121 -43
  37. package/dist/registry-consumer.js.map +1 -1
  38. package/dist/server.d.ts +3 -13
  39. package/dist/server.d.ts.map +1 -1
  40. package/dist/server.js +136 -199
  41. package/dist/server.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/auth-governance.ts +94 -0
  44. package/src/call-agent-schema.ts +33 -0
  45. package/src/codegen.test.ts +10 -0
  46. package/src/consumer.test.ts +132 -0
  47. package/src/define-config.ts +12 -0
  48. package/src/index.ts +2 -0
  49. package/src/key-manager.test.ts +17 -0
  50. package/src/key-manager.ts +10 -4
  51. package/src/registry-consumer.ts +176 -57
  52. package/src/server.ts +180 -215
@@ -37,6 +37,7 @@ import type {
37
37
  ResolvedRef,
38
38
  ResolvedRegistry,
39
39
  } from "./define-config.js";
40
+ import type { CallAgentRequest } from "./call-agent-schema.js";
40
41
  import type { SecuritySchemeSummary } from "./types.js";
41
42
  import {
42
43
  isSecretUri,
@@ -122,6 +123,60 @@ async function resolveTemplates<T>(
122
123
  return obj;
123
124
  }
124
125
 
126
+ // ============================================
127
+ // Registry Auth Headers
128
+ // ============================================
129
+
130
+ /**
131
+ * Build auth headers for a registry based on its auth config and custom headers.
132
+ * Merges typed auth (bearer, api-key) with arbitrary custom headers.
133
+ */
134
+ function buildRegistryAuthHeaders(
135
+ registry: ResolvedRegistry,
136
+ fallbackToken?: string,
137
+ ): Record<string, string> {
138
+ const headers: Record<string, string> = {};
139
+
140
+ // Apply typed auth
141
+ switch (registry.auth.type) {
142
+ case "bearer": {
143
+ const token = ("token" in registry.auth ? registry.auth.token : undefined) ?? fallbackToken;
144
+ if (token) {
145
+ headers.Authorization = `Bearer ${token}`;
146
+ }
147
+ break;
148
+ }
149
+ case "api-key": {
150
+ if ("key" in registry.auth && registry.auth.key) {
151
+ const headerName = ("header" in registry.auth ? registry.auth.header : undefined) ?? "x-api-key";
152
+ headers[headerName] = registry.auth.key;
153
+ }
154
+ break;
155
+ }
156
+ case "jwt": {
157
+ // JWT auth would require token exchange — not yet implemented
158
+ if (fallbackToken) {
159
+ headers.Authorization = `Bearer ${fallbackToken}`;
160
+ }
161
+ break;
162
+ }
163
+ case "none":
164
+ default: {
165
+ if (fallbackToken) {
166
+ headers.Authorization = `Bearer ${fallbackToken}`;
167
+ }
168
+ break;
169
+ }
170
+ }
171
+
172
+ // Merge custom headers (these override auth-generated headers)
173
+ if (registry.headers) {
174
+ Object.assign(headers, registry.headers);
175
+ }
176
+
177
+ return headers;
178
+ }
179
+
125
180
  // ============================================
126
181
  // Registry Discovery Types
127
182
  // ============================================
@@ -169,6 +224,14 @@ export interface AgentListing {
169
224
  };
170
225
  }
171
226
 
227
+ /** Raw agent entry returned by the list_agents MCP tool (before normalization). */
228
+ type ListAgentsEntry = Omit<AgentListing, "publisher" | "tools"> & {
229
+ tools?: Array<{ name: string; description?: string } | string>;
230
+ };
231
+
232
+ /** Response shape from list_agents — an array of agent entries. */
233
+ type ListAgentsResponse = ListAgentsEntry[];
234
+
172
235
  // ============================================
173
236
  // Secret Resolver
174
237
  // ============================================
@@ -450,6 +513,15 @@ export interface RegistryConsumer {
450
513
  /** Discover a registry's configuration */
451
514
  discover(registryUrl: string): Promise<RegistryConfiguration>;
452
515
 
516
+ /** Browse agents from a specific registry (or all if url omitted), with optional BM25 search */
517
+ browse(registryUrl?: string, query?: string): Promise<AgentListing[]>;
518
+
519
+ /** Inspect a specific agent — returns tools, auth requirements, resources */
520
+ inspect(
521
+ agentPath: string,
522
+ registryUrl?: string,
523
+ ): Promise<AgentListing | null>;
524
+
453
525
  /** Resolve a secret URL to its value */
454
526
  resolveSecret(url: string): Promise<string>;
455
527
 
@@ -490,12 +562,15 @@ export async function createRegistryConsumer(
490
562
  const discoveryCache = new Map<string, RegistryConfiguration>();
491
563
 
492
564
  // Discover a registry
493
- async function discover(registryUrl: string): Promise<RegistryConfiguration> {
565
+ async function discover(registryUrl: string, registry?: ResolvedRegistry): Promise<RegistryConfiguration> {
494
566
  const cached = discoveryCache.get(registryUrl);
495
567
  if (cached) return cached;
496
568
 
497
569
  const url = `${registryUrl.replace(/\/$/, "")}/.well-known/configuration`;
498
- const res = await fetchFn(url);
570
+ const headers: Record<string, string> = registry
571
+ ? buildRegistryAuthHeaders(registry, options.token)
572
+ : (options.token ? { Authorization: `Bearer ${options.token}` } : {});
573
+ const res = await fetchFn(url, { headers });
499
574
  if (!res.ok) {
500
575
  throw new Error(
501
576
  `Failed to discover registry ${registryUrl}: ${res.status}`,
@@ -506,66 +581,50 @@ export async function createRegistryConsumer(
506
581
  return configuration;
507
582
  }
508
583
 
509
- // List agents from a single registry
584
+ // List agents from a single registry via MCP tools/call list_agents
510
585
  async function listFromRegistry(
511
586
  registry: ResolvedRegistry,
587
+ query?: string,
512
588
  ): Promise<AgentListing[]> {
513
- const configuration = await discover(registry.url);
514
- const listUrl =
515
- configuration.agents_endpoint ??
516
- `${registry.url.replace(/\/$/, "")}/list`;
517
-
518
- const headers: Record<string, string> = {};
519
- if (registry.auth.type === "bearer" && "token" in registry.auth) {
520
- headers.Authorization = `Bearer ${registry.auth.token}`;
521
- } else if (options.token) {
522
- headers.Authorization = `Bearer ${options.token}`;
523
- }
524
-
525
- const res = await fetchFn(listUrl, { headers });
526
- if (!res.ok) {
527
- throw new Error(
528
- `Failed to list agents from ${registry.url}: ${res.status}`,
529
- );
530
- }
589
+ const configuration = await discover(registry.url, registry);
590
+ const mcpUrl =
591
+ configuration.call_endpoint ?? registry.url.replace(/\/$/, "");
531
592
 
532
- const agents = (await res.json()) as Array<{
533
- path: string;
534
- description?: string;
535
- tools?: Array<{ name: string; description?: string }>;
536
- integration?: {
537
- provider: string;
538
- displayName: string;
539
- category?: string;
540
- };
541
- }>;
593
+ const agents = await callMcpTool(
594
+ mcpUrl,
595
+ "list_agents",
596
+ query ? { query } : {},
597
+ {
598
+ token: options.token,
599
+ headers: buildRegistryAuthHeaders(registry, options.token),
600
+ },
601
+ fetchFn,
602
+ ) as ListAgentsResponse;
542
603
 
543
604
  return agents.map((agent) => ({
544
605
  ...agent,
606
+ ...agent,
607
+ // Normalize tools: strings become { name } objects
608
+ tools: agent.tools?.map((t) =>
609
+ typeof t === "string" ? { name: t } : t,
610
+ ),
545
611
  publisher: registry.publisher,
546
612
  }));
547
613
  }
548
614
 
549
- // Call a tool via a registry using MCP JSON-RPC (tools/call)
550
- async function callTool(
615
+ // Send any call_agent request through a registry's MCP endpoint
616
+ async function callRegistry(
551
617
  registry: ResolvedRegistry,
552
- agentPath: string,
553
- tool: string,
554
- params: Record<string, unknown>,
618
+ request: CallAgentRequest,
555
619
  ): Promise<unknown> {
556
- const configuration = await discover(registry.url);
557
- // MCP endpoint is the base URL (POST /), not /call
620
+ const configuration = await discover(registry.url, registry);
558
621
  const mcpUrl =
559
622
  configuration.call_endpoint ?? registry.url.replace(/\/$/, "");
560
623
 
561
624
  const headers: Record<string, string> = {
562
625
  "Content-Type": "application/json",
626
+ ...buildRegistryAuthHeaders(registry, options.token),
563
627
  };
564
- if (registry.auth.type === "bearer" && "token" in registry.auth) {
565
- headers.Authorization = `Bearer ${registry.auth.token}`;
566
- } else if (options.token) {
567
- headers.Authorization = `Bearer ${options.token}`;
568
- }
569
628
 
570
629
  const requestId = `call-${Date.now()}`;
571
630
  const res = await fetchFn(mcpUrl, {
@@ -577,14 +636,7 @@ export async function createRegistryConsumer(
577
636
  method: "tools/call",
578
637
  params: {
579
638
  name: "call_agent",
580
- arguments: {
581
- request: {
582
- action: "execute_tool",
583
- path: agentPath,
584
- tool,
585
- params,
586
- },
587
- },
639
+ arguments: { request },
588
640
  },
589
641
  }),
590
642
  });
@@ -592,7 +644,7 @@ export async function createRegistryConsumer(
592
644
  if (!res.ok) {
593
645
  const text = await res.text().catch(() => "unknown error");
594
646
  throw new Error(
595
- `Tool call failed (${registry.url}/${agentPath}/${tool}): ${res.status} ${text}`,
647
+ `Registry call failed (${registry.url}): ${res.status} ${text}`,
596
648
  );
597
649
  }
598
650
 
@@ -608,7 +660,7 @@ export async function createRegistryConsumer(
608
660
 
609
661
  if (rpcResponse.error) {
610
662
  throw new Error(
611
- `Tool call RPC error (${agentPath}/${tool}): ${rpcResponse.error.message}`,
663
+ `Registry RPC error: ${rpcResponse.error.message}`,
612
664
  );
613
665
  }
614
666
 
@@ -616,10 +668,10 @@ export async function createRegistryConsumer(
616
668
  if (mcpResult?.isError) {
617
669
  const errorText =
618
670
  mcpResult.content?.map((c) => c.text).join("\n") ?? "Unknown error";
619
- throw new Error(`Tool call error (${agentPath}/${tool}): ${errorText}`);
671
+ throw new Error(`Registry call error: ${errorText}`);
620
672
  }
621
673
 
622
- // Parse text content - call_agent returns JSON-stringified results
674
+ // Parse text content
623
675
  const textContent = mcpResult?.content?.find((c) => c.type === "text");
624
676
  if (textContent?.text) {
625
677
  try {
@@ -632,12 +684,27 @@ export async function createRegistryConsumer(
632
684
  return mcpResult;
633
685
  }
634
686
 
687
+ // Call a tool via a registry (convenience wrapper)
688
+ async function callTool(
689
+ registry: ResolvedRegistry,
690
+ agentPath: string,
691
+ tool: string,
692
+ params: Record<string, unknown>,
693
+ ): Promise<unknown> {
694
+ return callRegistry(registry, {
695
+ action: "execute_tool",
696
+ path: agentPath,
697
+ tool,
698
+ params,
699
+ });
700
+ }
701
+
635
702
  // Build the consumer
636
703
  const consumer: RegistryConsumer = {
637
704
  async list(): Promise<AgentListing[]> {
638
705
  // Collect from standard registries
639
706
  const registryResults = await Promise.allSettled(
640
- resolvedRegistries.map(listFromRegistry),
707
+ resolvedRegistries.map((r) => listFromRegistry(r)),
641
708
  );
642
709
  const listings = registryResults.flatMap((r) =>
643
710
  r.status === "fulfilled" ? r.value : [],
@@ -730,7 +797,59 @@ export async function createRegistryConsumer(
730
797
  return callTool(registry, ref.ref, tool, params);
731
798
  },
732
799
 
733
- discover,
800
+ discover(registryUrl: string) {
801
+ // Find matching resolved registry for auth headers
802
+ const registry = resolvedRegistries.find((r) => r.url === registryUrl);
803
+ return discover(registryUrl, registry);
804
+ },
805
+
806
+ async browse(registryUrl?: string, query?: string): Promise<AgentListing[]> {
807
+ // List agents from a specific registry, or all registries if not specified
808
+ const targets = registryUrl
809
+ ? resolvedRegistries.filter(
810
+ (r) => r.url === registryUrl || r.name === registryUrl,
811
+ )
812
+ : resolvedRegistries;
813
+ // Pass query to server for BM25 search
814
+ const results = await Promise.allSettled(
815
+ targets.map((t) => listFromRegistry(t, query)),
816
+ );
817
+ return results.flatMap((r) =>
818
+ r.status === "fulfilled" ? r.value : [],
819
+ );
820
+ },
821
+
822
+ async inspect(
823
+ agentPath: string,
824
+ registryUrl?: string,
825
+ ): Promise<AgentListing | null> {
826
+ const targetRegistries = registryUrl
827
+ ? resolvedRegistries.filter((r) => r.url === registryUrl || r.name === registryUrl)
828
+ : resolvedRegistries;
829
+
830
+ // Parallel O(1) lookups via describe_tools
831
+ const results = await Promise.allSettled(
832
+ targetRegistries.map(async (registry) => {
833
+ const data = (await callRegistry(registry, {
834
+ action: "describe_tools",
835
+ path: agentPath,
836
+ tools: [],
837
+ })) as { tools?: unknown[]; description?: string } | null;
838
+ if (!data) return null;
839
+ return {
840
+ path: agentPath,
841
+ publisher: registry.publisher,
842
+ tools: data.tools,
843
+ description: data.description,
844
+ } as AgentListing;
845
+ }),
846
+ );
847
+
848
+ for (const r of results) {
849
+ if (r.status === "fulfilled" && r.value) return r.value;
850
+ }
851
+ return null;
852
+ },
734
853
 
735
854
  async resolveSecret(url: string): Promise<string> {
736
855
  return resolveSecretFn(url, { token: options.token });