@slashfi/agents-sdk 0.31.0 → 0.32.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 +122 -37
  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 +122 -37
  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 +161 -37
  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
  // ============================================
@@ -450,6 +505,15 @@ export interface RegistryConsumer {
450
505
  /** Discover a registry's configuration */
451
506
  discover(registryUrl: string): Promise<RegistryConfiguration>;
452
507
 
508
+ /** Browse agents from a specific registry (or all if url omitted), with optional BM25 search */
509
+ browse(registryUrl?: string, query?: string): Promise<AgentListing[]>;
510
+
511
+ /** Inspect a specific agent — returns tools, auth requirements, resources */
512
+ inspect(
513
+ agentPath: string,
514
+ registryUrl?: string,
515
+ ): Promise<AgentListing | null>;
516
+
453
517
  /** Resolve a secret URL to its value */
454
518
  resolveSecret(url: string): Promise<string>;
455
519
 
@@ -490,12 +554,15 @@ export async function createRegistryConsumer(
490
554
  const discoveryCache = new Map<string, RegistryConfiguration>();
491
555
 
492
556
  // Discover a registry
493
- async function discover(registryUrl: string): Promise<RegistryConfiguration> {
557
+ async function discover(registryUrl: string, registry?: ResolvedRegistry): Promise<RegistryConfiguration> {
494
558
  const cached = discoveryCache.get(registryUrl);
495
559
  if (cached) return cached;
496
560
 
497
561
  const url = `${registryUrl.replace(/\/$/, "")}/.well-known/configuration`;
498
- const res = await fetchFn(url);
562
+ const headers: Record<string, string> = registry
563
+ ? buildRegistryAuthHeaders(registry, options.token)
564
+ : (options.token ? { Authorization: `Bearer ${options.token}` } : {});
565
+ const res = await fetchFn(url, { headers });
499
566
  if (!res.ok) {
500
567
  throw new Error(
501
568
  `Failed to discover registry ${registryUrl}: ${res.status}`,
@@ -506,22 +573,24 @@ export async function createRegistryConsumer(
506
573
  return configuration;
507
574
  }
508
575
 
509
- // List agents from a single registry
576
+ // List agents from a single registry, with optional search query
510
577
  async function listFromRegistry(
511
578
  registry: ResolvedRegistry,
579
+ query?: string,
512
580
  ): Promise<AgentListing[]> {
513
- const configuration = await discover(registry.url);
514
- const listUrl =
581
+ const configuration = await discover(registry.url, registry);
582
+ let listUrl =
515
583
  configuration.agents_endpoint ??
516
584
  `${registry.url.replace(/\/$/, "")}/list`;
517
585
 
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}`;
586
+ // Append search query if provided
587
+ if (query) {
588
+ const sep = listUrl.includes("?") ? "&" : "?";
589
+ listUrl += `${sep}q=${encodeURIComponent(query)}`;
523
590
  }
524
591
 
592
+ const headers = buildRegistryAuthHeaders(registry, options.token);
593
+
525
594
  const res = await fetchFn(listUrl, { headers });
526
595
  if (!res.ok) {
527
596
  throw new Error(
@@ -529,7 +598,9 @@ export async function createRegistryConsumer(
529
598
  );
530
599
  }
531
600
 
532
- const agents = (await res.json()) as Array<{
601
+ const body = (await res.json()) as any;
602
+ // Support both paginated { agents: [...] } and legacy array responses
603
+ const agents = (Array.isArray(body) ? body : body.agents) as Array<{
533
604
  path: string;
534
605
  description?: string;
535
606
  tools?: Array<{ name: string; description?: string }>;
@@ -546,26 +617,19 @@ export async function createRegistryConsumer(
546
617
  }));
547
618
  }
548
619
 
549
- // Call a tool via a registry using MCP JSON-RPC (tools/call)
550
- async function callTool(
620
+ // Send any call_agent request through a registry's MCP endpoint
621
+ async function callRegistry(
551
622
  registry: ResolvedRegistry,
552
- agentPath: string,
553
- tool: string,
554
- params: Record<string, unknown>,
623
+ request: CallAgentRequest,
555
624
  ): Promise<unknown> {
556
- const configuration = await discover(registry.url);
557
- // MCP endpoint is the base URL (POST /), not /call
625
+ const configuration = await discover(registry.url, registry);
558
626
  const mcpUrl =
559
627
  configuration.call_endpoint ?? registry.url.replace(/\/$/, "");
560
628
 
561
629
  const headers: Record<string, string> = {
562
630
  "Content-Type": "application/json",
631
+ ...buildRegistryAuthHeaders(registry, options.token),
563
632
  };
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
633
 
570
634
  const requestId = `call-${Date.now()}`;
571
635
  const res = await fetchFn(mcpUrl, {
@@ -577,14 +641,7 @@ export async function createRegistryConsumer(
577
641
  method: "tools/call",
578
642
  params: {
579
643
  name: "call_agent",
580
- arguments: {
581
- request: {
582
- action: "execute_tool",
583
- path: agentPath,
584
- tool,
585
- params,
586
- },
587
- },
644
+ arguments: { request },
588
645
  },
589
646
  }),
590
647
  });
@@ -592,7 +649,7 @@ export async function createRegistryConsumer(
592
649
  if (!res.ok) {
593
650
  const text = await res.text().catch(() => "unknown error");
594
651
  throw new Error(
595
- `Tool call failed (${registry.url}/${agentPath}/${tool}): ${res.status} ${text}`,
652
+ `Registry call failed (${registry.url}): ${res.status} ${text}`,
596
653
  );
597
654
  }
598
655
 
@@ -608,7 +665,7 @@ export async function createRegistryConsumer(
608
665
 
609
666
  if (rpcResponse.error) {
610
667
  throw new Error(
611
- `Tool call RPC error (${agentPath}/${tool}): ${rpcResponse.error.message}`,
668
+ `Registry RPC error: ${rpcResponse.error.message}`,
612
669
  );
613
670
  }
614
671
 
@@ -616,10 +673,10 @@ export async function createRegistryConsumer(
616
673
  if (mcpResult?.isError) {
617
674
  const errorText =
618
675
  mcpResult.content?.map((c) => c.text).join("\n") ?? "Unknown error";
619
- throw new Error(`Tool call error (${agentPath}/${tool}): ${errorText}`);
676
+ throw new Error(`Registry call error: ${errorText}`);
620
677
  }
621
678
 
622
- // Parse text content - call_agent returns JSON-stringified results
679
+ // Parse text content
623
680
  const textContent = mcpResult?.content?.find((c) => c.type === "text");
624
681
  if (textContent?.text) {
625
682
  try {
@@ -632,12 +689,27 @@ export async function createRegistryConsumer(
632
689
  return mcpResult;
633
690
  }
634
691
 
692
+ // Call a tool via a registry (convenience wrapper)
693
+ async function callTool(
694
+ registry: ResolvedRegistry,
695
+ agentPath: string,
696
+ tool: string,
697
+ params: Record<string, unknown>,
698
+ ): Promise<unknown> {
699
+ return callRegistry(registry, {
700
+ action: "execute_tool",
701
+ path: agentPath,
702
+ tool,
703
+ params,
704
+ });
705
+ }
706
+
635
707
  // Build the consumer
636
708
  const consumer: RegistryConsumer = {
637
709
  async list(): Promise<AgentListing[]> {
638
710
  // Collect from standard registries
639
711
  const registryResults = await Promise.allSettled(
640
- resolvedRegistries.map(listFromRegistry),
712
+ resolvedRegistries.map((r) => listFromRegistry(r)),
641
713
  );
642
714
  const listings = registryResults.flatMap((r) =>
643
715
  r.status === "fulfilled" ? r.value : [],
@@ -730,7 +802,59 @@ export async function createRegistryConsumer(
730
802
  return callTool(registry, ref.ref, tool, params);
731
803
  },
732
804
 
733
- discover,
805
+ discover(registryUrl: string) {
806
+ // Find matching resolved registry for auth headers
807
+ const registry = resolvedRegistries.find((r) => r.url === registryUrl);
808
+ return discover(registryUrl, registry);
809
+ },
810
+
811
+ async browse(registryUrl?: string, query?: string): Promise<AgentListing[]> {
812
+ // List agents from a specific registry, or all registries if not specified
813
+ const targets = registryUrl
814
+ ? resolvedRegistries.filter(
815
+ (r) => r.url === registryUrl || r.name === registryUrl,
816
+ )
817
+ : resolvedRegistries;
818
+ // Pass query to server for BM25 search
819
+ const results = await Promise.allSettled(
820
+ targets.map((t) => listFromRegistry(t, query)),
821
+ );
822
+ return results.flatMap((r) =>
823
+ r.status === "fulfilled" ? r.value : [],
824
+ );
825
+ },
826
+
827
+ async inspect(
828
+ agentPath: string,
829
+ registryUrl?: string,
830
+ ): Promise<AgentListing | null> {
831
+ const targetRegistries = registryUrl
832
+ ? resolvedRegistries.filter((r) => r.url === registryUrl || r.name === registryUrl)
833
+ : resolvedRegistries;
834
+
835
+ // Parallel O(1) lookups via describe_tools
836
+ const results = await Promise.allSettled(
837
+ targetRegistries.map(async (registry) => {
838
+ const data = (await callRegistry(registry, {
839
+ action: "describe_tools",
840
+ path: agentPath,
841
+ tools: [],
842
+ })) as { tools?: unknown[]; description?: string } | null;
843
+ if (!data) return null;
844
+ return {
845
+ path: agentPath,
846
+ publisher: registry.publisher,
847
+ tools: data.tools,
848
+ description: data.description,
849
+ } as AgentListing;
850
+ }),
851
+ );
852
+
853
+ for (const r of results) {
854
+ if (r.status === "fulfilled" && r.value) return r.value;
855
+ }
856
+ return null;
857
+ },
734
858
 
735
859
  async resolveSecret(url: string): Promise<string> {
736
860
  return resolveSecretFn(url, { token: options.token });