@slashfi/agents-sdk 0.30.2 → 0.31.0-pr.dev.4fd2a3c

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 (46) hide show
  1. package/dist/call-agent-schema.d.ts +155 -0
  2. package/dist/call-agent-schema.d.ts.map +1 -1
  3. package/dist/call-agent-schema.js +13 -0
  4. package/dist/call-agent-schema.js.map +1 -1
  5. package/dist/cjs/call-agent-schema.js +14 -1
  6. package/dist/cjs/call-agent-schema.js.map +1 -1
  7. package/dist/cjs/define-config.js +1 -0
  8. package/dist/cjs/define-config.js.map +1 -1
  9. package/dist/cjs/index.js +3 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/registry-consumer.js +110 -33
  12. package/dist/cjs/registry-consumer.js.map +1 -1
  13. package/dist/cjs/registry.js +35 -0
  14. package/dist/cjs/registry.js.map +1 -1
  15. package/dist/cjs/server.js +14 -3
  16. package/dist/cjs/server.js.map +1 -1
  17. package/dist/define-config.d.ts +4 -0
  18. package/dist/define-config.d.ts.map +1 -1
  19. package/dist/define-config.js +1 -0
  20. package/dist/define-config.js.map +1 -1
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/registry-consumer.d.ts +13 -0
  26. package/dist/registry-consumer.d.ts.map +1 -1
  27. package/dist/registry-consumer.js +110 -33
  28. package/dist/registry-consumer.js.map +1 -1
  29. package/dist/registry.d.ts.map +1 -1
  30. package/dist/registry.js +35 -0
  31. package/dist/registry.js.map +1 -1
  32. package/dist/server.d.ts.map +1 -1
  33. package/dist/server.js +14 -3
  34. package/dist/server.js.map +1 -1
  35. package/dist/types.d.ts +53 -1
  36. package/dist/types.d.ts.map +1 -1
  37. package/package.json +2 -2
  38. package/src/call-agent-schema.ts +21 -0
  39. package/src/codegen.test.ts +10 -0
  40. package/src/consumer.test.ts +132 -0
  41. package/src/define-config.ts +6 -0
  42. package/src/index.ts +6 -0
  43. package/src/registry-consumer.ts +155 -34
  44. package/src/registry.ts +39 -0
  45. package/src/server.ts +14 -3
  46. package/src/types.ts +59 -0
@@ -37,6 +37,8 @@ import type {
37
37
  ResolvedRef,
38
38
  ResolvedRegistry,
39
39
  } from "./define-config.js";
40
+ import type { CallAgentRequest } from "./call-agent-schema.js";
41
+ import type { SecuritySchemeSummary } from "./types.js";
40
42
  import {
41
43
  isSecretUri,
42
44
  normalizeRef,
@@ -121,6 +123,60 @@ async function resolveTemplates<T>(
121
123
  return obj;
122
124
  }
123
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
+
124
180
  // ============================================
125
181
  // Registry Discovery Types
126
182
  // ============================================
@@ -152,6 +208,14 @@ export interface AgentListing {
152
208
  }>;
153
209
  /** Whether it requires auth */
154
210
  requiresAuth?: boolean;
211
+ /** Security scheme summary (machine-readable auth type) */
212
+ security?: SecuritySchemeSummary;
213
+ /** Available resources (e.g., AUTH.md) */
214
+ resources?: Array<{
215
+ uri: string;
216
+ name?: string;
217
+ mimeType?: string;
218
+ }>;
155
219
  /** Integration config if applicable */
156
220
  integration?: {
157
221
  provider: string;
@@ -441,6 +505,15 @@ export interface RegistryConsumer {
441
505
  /** Discover a registry's configuration */
442
506
  discover(registryUrl: string): Promise<RegistryConfiguration>;
443
507
 
508
+ /** Browse agents from a specific registry (or all if url omitted) */
509
+ browse(registryUrl?: 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
+
444
517
  /** Resolve a secret URL to its value */
445
518
  resolveSecret(url: string): Promise<string>;
446
519
 
@@ -481,12 +554,15 @@ export async function createRegistryConsumer(
481
554
  const discoveryCache = new Map<string, RegistryConfiguration>();
482
555
 
483
556
  // Discover a registry
484
- async function discover(registryUrl: string): Promise<RegistryConfiguration> {
557
+ async function discover(registryUrl: string, registry?: ResolvedRegistry): Promise<RegistryConfiguration> {
485
558
  const cached = discoveryCache.get(registryUrl);
486
559
  if (cached) return cached;
487
560
 
488
561
  const url = `${registryUrl.replace(/\/$/, "")}/.well-known/configuration`;
489
- 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 });
490
566
  if (!res.ok) {
491
567
  throw new Error(
492
568
  `Failed to discover registry ${registryUrl}: ${res.status}`,
@@ -501,17 +577,12 @@ export async function createRegistryConsumer(
501
577
  async function listFromRegistry(
502
578
  registry: ResolvedRegistry,
503
579
  ): Promise<AgentListing[]> {
504
- const configuration = await discover(registry.url);
580
+ const configuration = await discover(registry.url, registry);
505
581
  const listUrl =
506
582
  configuration.agents_endpoint ??
507
583
  `${registry.url.replace(/\/$/, "")}/list`;
508
584
 
509
- const headers: Record<string, string> = {};
510
- if (registry.auth.type === "bearer" && "token" in registry.auth) {
511
- headers.Authorization = `Bearer ${registry.auth.token}`;
512
- } else if (options.token) {
513
- headers.Authorization = `Bearer ${options.token}`;
514
- }
585
+ const headers = buildRegistryAuthHeaders(registry, options.token);
515
586
 
516
587
  const res = await fetchFn(listUrl, { headers });
517
588
  if (!res.ok) {
@@ -537,26 +608,19 @@ export async function createRegistryConsumer(
537
608
  }));
538
609
  }
539
610
 
540
- // Call a tool via a registry using MCP JSON-RPC (tools/call)
541
- async function callTool(
611
+ // Send any call_agent request through a registry's MCP endpoint
612
+ async function callRegistry(
542
613
  registry: ResolvedRegistry,
543
- agentPath: string,
544
- tool: string,
545
- params: Record<string, unknown>,
614
+ request: CallAgentRequest,
546
615
  ): Promise<unknown> {
547
- const configuration = await discover(registry.url);
548
- // MCP endpoint is the base URL (POST /), not /call
616
+ const configuration = await discover(registry.url, registry);
549
617
  const mcpUrl =
550
618
  configuration.call_endpoint ?? registry.url.replace(/\/$/, "");
551
619
 
552
620
  const headers: Record<string, string> = {
553
621
  "Content-Type": "application/json",
622
+ ...buildRegistryAuthHeaders(registry, options.token),
554
623
  };
555
- if (registry.auth.type === "bearer" && "token" in registry.auth) {
556
- headers.Authorization = `Bearer ${registry.auth.token}`;
557
- } else if (options.token) {
558
- headers.Authorization = `Bearer ${options.token}`;
559
- }
560
624
 
561
625
  const requestId = `call-${Date.now()}`;
562
626
  const res = await fetchFn(mcpUrl, {
@@ -568,14 +632,7 @@ export async function createRegistryConsumer(
568
632
  method: "tools/call",
569
633
  params: {
570
634
  name: "call_agent",
571
- arguments: {
572
- request: {
573
- action: "execute_tool",
574
- path: agentPath,
575
- tool,
576
- params,
577
- },
578
- },
635
+ arguments: { request },
579
636
  },
580
637
  }),
581
638
  });
@@ -583,7 +640,7 @@ export async function createRegistryConsumer(
583
640
  if (!res.ok) {
584
641
  const text = await res.text().catch(() => "unknown error");
585
642
  throw new Error(
586
- `Tool call failed (${registry.url}/${agentPath}/${tool}): ${res.status} ${text}`,
643
+ `Registry call failed (${registry.url}): ${res.status} ${text}`,
587
644
  );
588
645
  }
589
646
 
@@ -599,7 +656,7 @@ export async function createRegistryConsumer(
599
656
 
600
657
  if (rpcResponse.error) {
601
658
  throw new Error(
602
- `Tool call RPC error (${agentPath}/${tool}): ${rpcResponse.error.message}`,
659
+ `Registry RPC error: ${rpcResponse.error.message}`,
603
660
  );
604
661
  }
605
662
 
@@ -607,10 +664,10 @@ export async function createRegistryConsumer(
607
664
  if (mcpResult?.isError) {
608
665
  const errorText =
609
666
  mcpResult.content?.map((c) => c.text).join("\n") ?? "Unknown error";
610
- throw new Error(`Tool call error (${agentPath}/${tool}): ${errorText}`);
667
+ throw new Error(`Registry call error: ${errorText}`);
611
668
  }
612
669
 
613
- // Parse text content - call_agent returns JSON-stringified results
670
+ // Parse text content
614
671
  const textContent = mcpResult?.content?.find((c) => c.type === "text");
615
672
  if (textContent?.text) {
616
673
  try {
@@ -623,6 +680,21 @@ export async function createRegistryConsumer(
623
680
  return mcpResult;
624
681
  }
625
682
 
683
+ // Call a tool via a registry (convenience wrapper)
684
+ async function callTool(
685
+ registry: ResolvedRegistry,
686
+ agentPath: string,
687
+ tool: string,
688
+ params: Record<string, unknown>,
689
+ ): Promise<unknown> {
690
+ return callRegistry(registry, {
691
+ action: "execute_tool",
692
+ path: agentPath,
693
+ tool,
694
+ params,
695
+ });
696
+ }
697
+
626
698
  // Build the consumer
627
699
  const consumer: RegistryConsumer = {
628
700
  async list(): Promise<AgentListing[]> {
@@ -721,7 +793,56 @@ export async function createRegistryConsumer(
721
793
  return callTool(registry, ref.ref, tool, params);
722
794
  },
723
795
 
724
- discover,
796
+ discover(registryUrl: string) {
797
+ // Find matching resolved registry for auth headers
798
+ const registry = resolvedRegistries.find((r) => r.url === registryUrl);
799
+ return discover(registryUrl, registry);
800
+ },
801
+
802
+ async browse(registryUrl?: string): Promise<AgentListing[]> {
803
+ // List agents from a specific registry, or all registries if not specified
804
+ const targets = registryUrl
805
+ ? resolvedRegistries.filter(
806
+ (r) => r.url === registryUrl || r.name === registryUrl,
807
+ )
808
+ : resolvedRegistries;
809
+ const results = await Promise.allSettled(targets.map(listFromRegistry));
810
+ return results.flatMap((r) =>
811
+ r.status === "fulfilled" ? r.value : [],
812
+ );
813
+ },
814
+
815
+ async inspect(
816
+ agentPath: string,
817
+ registryUrl?: string,
818
+ ): Promise<AgentListing | null> {
819
+ const targetRegistries = registryUrl
820
+ ? resolvedRegistries.filter((r) => r.url === registryUrl || r.name === registryUrl)
821
+ : resolvedRegistries;
822
+
823
+ // Parallel O(1) lookups via describe_tools
824
+ const results = await Promise.allSettled(
825
+ targetRegistries.map(async (registry) => {
826
+ const data = (await callRegistry(registry, {
827
+ action: "describe_tools",
828
+ path: agentPath,
829
+ tools: [],
830
+ })) as { tools?: unknown[]; description?: string } | null;
831
+ if (!data) return null;
832
+ return {
833
+ path: agentPath,
834
+ publisher: registry.publisher,
835
+ tools: data.tools,
836
+ description: data.description,
837
+ } as AgentListing;
838
+ }),
839
+ );
840
+
841
+ for (const r of results) {
842
+ if (r.status === "fulfilled" && r.value) return r.value;
843
+ }
844
+ return null;
845
+ },
725
846
 
726
847
  async resolveSecret(url: string): Promise<string> {
727
848
  return resolveSecretFn(url, { token: options.token });
package/src/registry.ts CHANGED
@@ -17,6 +17,8 @@ import type {
17
17
  CallAgentExecuteToolResponse,
18
18
  CallAgentLoadRequest,
19
19
  CallAgentLoadResponse,
20
+ CallAgentListResourcesResponse,
21
+ CallAgentReadResourcesResponse,
20
22
  CallAgentRequest,
21
23
  CallAgentResponse,
22
24
  ToolContext,
@@ -31,6 +33,8 @@ const DEFAULT_SUPPORTED_ACTIONS: AgentAction[] = [
31
33
  "execute_tool",
32
34
  "describe_tools",
33
35
  "load",
36
+ "list_resources",
37
+ "read_resources",
34
38
  ];
35
39
 
36
40
  // ============================================
@@ -729,6 +733,41 @@ export function createAgentRegistry(
729
733
  return defaultLoad(agent, request);
730
734
  }
731
735
 
736
+ case "list_resources": {
737
+ const resources = (agent.config?.resources ?? []).map((r) => ({
738
+ uri: r.uri,
739
+ name: r.name,
740
+ mimeType: r.mimeType,
741
+ }));
742
+ return {
743
+ success: true,
744
+ agentPath: agent.path,
745
+ resources,
746
+ } as CallAgentListResourcesResponse;
747
+ }
748
+
749
+ case "read_resources": {
750
+ const uris = request.uris;
751
+ const agentResources = agent.config?.resources ?? [];
752
+ const results = uris.map((uri) => {
753
+ const resource = agentResources.find((r) => r.uri === uri);
754
+ if (!resource) {
755
+ return { uri, error: `Resource not found: ${uri}` };
756
+ }
757
+ return {
758
+ uri: resource.uri,
759
+ name: resource.name,
760
+ mimeType: resource.mimeType,
761
+ content: resource.content,
762
+ };
763
+ });
764
+ return {
765
+ success: true,
766
+ agentPath: agent.path,
767
+ resources: results,
768
+ } as CallAgentReadResourcesResponse;
769
+ }
770
+
732
771
  default: {
733
772
  // TypeScript exhaustiveness check
734
773
  const _exhaustive: never = request;
package/src/server.ts CHANGED
@@ -476,7 +476,7 @@ function getToolDefinitions() {
476
476
  {
477
477
  name: "call_agent",
478
478
  description:
479
- "Execute a tool on a registered agent. Provide the agent path and tool name.",
479
+ "Execute a tool on a registered agent. Provide the agent path and tool name.\n\nSupported actions:\n- invoke: Fire-and-forget agent invocation\n- ask: Invoke and wait for response\n- execute_tool: Call a specific tool on an agent\n- describe_tools: Get tool schemas for an agent\n- load: Get agent definition/system prompt\n- list_resources: List all resources available on an agent (docs, auth instructions, config schemas, etc.)\n- read_resources: Fetch one or more resources by URI",
480
480
  inputSchema: callAgentInputSchema,
481
481
  },
482
482
  {
@@ -669,6 +669,14 @@ export function createAgentServer(
669
669
  description: agent.config?.description,
670
670
  supportedActions: agent.config?.supportedActions,
671
671
  integration: agent.config?.integration || null,
672
+ security: agent.config?.security
673
+ ? { type: agent.config.security.type }
674
+ : undefined,
675
+ resources: agent.config?.resources?.map((r) => ({
676
+ uri: r.uri,
677
+ name: r.name,
678
+ mimeType: r.mimeType,
679
+ })),
672
680
  tools: agent.tools
673
681
  .filter((t) => {
674
682
  const tv = t.visibility ?? "internal";
@@ -1258,9 +1266,12 @@ export function createAgentServer(
1258
1266
  // Public registries (e.g. registry.slash.com) skip this entirely.
1259
1267
  if (
1260
1268
  path === "/.well-known/oauth-authorization-server" &&
1261
- req.method === "GET" &&
1262
- (options.registry?.oauthCallbackUrl || serverSigningKeys.length > 0)
1269
+ req.method === "GET"
1263
1270
  ) {
1271
+ if (!(options.registry?.oauthCallbackUrl || serverSigningKeys.length > 0)) {
1272
+ const res = new Response("Not Found", { status: 404 });
1273
+ return cors ? addCors(res) : res;
1274
+ }
1264
1275
  const baseUrl = resolveBaseUrl(req);
1265
1276
  const res = jsonResponse({
1266
1277
  issuer: baseUrl,
package/src/types.ts CHANGED
@@ -259,6 +259,32 @@ export type SecurityScheme =
259
259
  | HttpSecurityScheme
260
260
  | NoneSecurityScheme;
261
261
 
262
+ /**
263
+ * Lightweight security summary for agent listings.
264
+ * The full SecurityScheme has all the details; this is the
265
+ * directory-level overview (e.g., in list_agents responses).
266
+ */
267
+ export interface SecuritySchemeSummary {
268
+ type: SecurityScheme['type'];
269
+ [key: string]: unknown;
270
+ }
271
+
272
+ /**
273
+ * A static resource exposed by an agent.
274
+ * Well-known resources:
275
+ * - `AUTH.md` — LLM-readable auth/connection setup instructions
276
+ */
277
+ export interface AgentResource {
278
+ /** Resource URI (e.g., 'AUTH.md') */
279
+ uri: string;
280
+ /** Human-readable name */
281
+ name?: string;
282
+ /** MIME type (defaults to text/markdown for .md) */
283
+ mimeType?: string;
284
+ /** The resource content (populated on read_resources, omitted on list) */
285
+ content?: string;
286
+ }
287
+
262
288
  // ============================================
263
289
  // Agent Configuration
264
290
  // ============================================
@@ -313,6 +339,13 @@ export interface AgentConfig {
313
339
  */
314
340
  security?: SecurityScheme;
315
341
 
342
+ /**
343
+ * Agent resources — static files/documents the agent exposes.
344
+ * Well-known resources:
345
+ * - `AUTH.md` — LLM-readable auth setup instructions
346
+ */
347
+ resources?: AgentResource[];
348
+
316
349
  /** Additional configuration */
317
350
  /** Agent refs (paths to other agents this agent can call) */
318
351
  refs?: Record<string, { description?: string }>;
@@ -747,6 +780,30 @@ export interface CallAgentCallbackResponse {
747
780
  callbackId: string;
748
781
  }
749
782
 
783
+ /** List resources response */
784
+ export interface CallAgentListResourcesResponse {
785
+ success: true;
786
+ agentPath: string;
787
+ resources: Array<{
788
+ uri: string;
789
+ name?: string;
790
+ mimeType?: string;
791
+ }>;
792
+ }
793
+
794
+ /** Read resources response */
795
+ export interface CallAgentReadResourcesResponse {
796
+ success: true;
797
+ agentPath: string;
798
+ resources: Array<{
799
+ uri: string;
800
+ name?: string;
801
+ mimeType?: string;
802
+ content?: string;
803
+ error?: string;
804
+ }>;
805
+ }
806
+
750
807
  /** Error response */
751
808
  export interface CallAgentErrorResponse {
752
809
  success: false;
@@ -762,4 +819,6 @@ export type CallAgentResponse =
762
819
  | CallAgentDescribeToolsResponse
763
820
  | CallAgentLoadResponse
764
821
  | CallAgentCallbackResponse
822
+ | CallAgentListResourcesResponse
823
+ | CallAgentReadResourcesResponse
765
824
  | CallAgentErrorResponse;