@slashfi/agents-sdk 0.30.2 → 0.31.0-pr.dev.98958f4

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 +102 -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 +11 -0
  26. package/dist/registry-consumer.d.ts.map +1 -1
  27. package/dist/registry-consumer.js +102 -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 +116 -0
  41. package/src/define-config.ts +6 -0
  42. package/src/index.ts +6 -0
  43. package/src/registry-consumer.ts +139 -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,12 @@ export interface RegistryConsumer {
441
505
  /** Discover a registry's configuration */
442
506
  discover(registryUrl: string): Promise<RegistryConfiguration>;
443
507
 
508
+ /** Inspect a specific agent — returns tools, auth requirements, resources */
509
+ inspect(
510
+ agentPath: string,
511
+ registryUrl?: string,
512
+ ): Promise<AgentListing | null>;
513
+
444
514
  /** Resolve a secret URL to its value */
445
515
  resolveSecret(url: string): Promise<string>;
446
516
 
@@ -481,12 +551,15 @@ export async function createRegistryConsumer(
481
551
  const discoveryCache = new Map<string, RegistryConfiguration>();
482
552
 
483
553
  // Discover a registry
484
- async function discover(registryUrl: string): Promise<RegistryConfiguration> {
554
+ async function discover(registryUrl: string, registry?: ResolvedRegistry): Promise<RegistryConfiguration> {
485
555
  const cached = discoveryCache.get(registryUrl);
486
556
  if (cached) return cached;
487
557
 
488
558
  const url = `${registryUrl.replace(/\/$/, "")}/.well-known/configuration`;
489
- const res = await fetchFn(url);
559
+ const headers: Record<string, string> = registry
560
+ ? buildRegistryAuthHeaders(registry, options.token)
561
+ : (options.token ? { Authorization: `Bearer ${options.token}` } : {});
562
+ const res = await fetchFn(url, { headers });
490
563
  if (!res.ok) {
491
564
  throw new Error(
492
565
  `Failed to discover registry ${registryUrl}: ${res.status}`,
@@ -501,17 +574,12 @@ export async function createRegistryConsumer(
501
574
  async function listFromRegistry(
502
575
  registry: ResolvedRegistry,
503
576
  ): Promise<AgentListing[]> {
504
- const configuration = await discover(registry.url);
577
+ const configuration = await discover(registry.url, registry);
505
578
  const listUrl =
506
579
  configuration.agents_endpoint ??
507
580
  `${registry.url.replace(/\/$/, "")}/list`;
508
581
 
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
- }
582
+ const headers = buildRegistryAuthHeaders(registry, options.token);
515
583
 
516
584
  const res = await fetchFn(listUrl, { headers });
517
585
  if (!res.ok) {
@@ -537,26 +605,19 @@ export async function createRegistryConsumer(
537
605
  }));
538
606
  }
539
607
 
540
- // Call a tool via a registry using MCP JSON-RPC (tools/call)
541
- async function callTool(
608
+ // Send any call_agent request through a registry's MCP endpoint
609
+ async function callRegistry(
542
610
  registry: ResolvedRegistry,
543
- agentPath: string,
544
- tool: string,
545
- params: Record<string, unknown>,
611
+ request: CallAgentRequest,
546
612
  ): Promise<unknown> {
547
- const configuration = await discover(registry.url);
548
- // MCP endpoint is the base URL (POST /), not /call
613
+ const configuration = await discover(registry.url, registry);
549
614
  const mcpUrl =
550
615
  configuration.call_endpoint ?? registry.url.replace(/\/$/, "");
551
616
 
552
617
  const headers: Record<string, string> = {
553
618
  "Content-Type": "application/json",
619
+ ...buildRegistryAuthHeaders(registry, options.token),
554
620
  };
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
621
 
561
622
  const requestId = `call-${Date.now()}`;
562
623
  const res = await fetchFn(mcpUrl, {
@@ -568,14 +629,7 @@ export async function createRegistryConsumer(
568
629
  method: "tools/call",
569
630
  params: {
570
631
  name: "call_agent",
571
- arguments: {
572
- request: {
573
- action: "execute_tool",
574
- path: agentPath,
575
- tool,
576
- params,
577
- },
578
- },
632
+ arguments: { request },
579
633
  },
580
634
  }),
581
635
  });
@@ -583,7 +637,7 @@ export async function createRegistryConsumer(
583
637
  if (!res.ok) {
584
638
  const text = await res.text().catch(() => "unknown error");
585
639
  throw new Error(
586
- `Tool call failed (${registry.url}/${agentPath}/${tool}): ${res.status} ${text}`,
640
+ `Registry call failed (${registry.url}): ${res.status} ${text}`,
587
641
  );
588
642
  }
589
643
 
@@ -599,7 +653,7 @@ export async function createRegistryConsumer(
599
653
 
600
654
  if (rpcResponse.error) {
601
655
  throw new Error(
602
- `Tool call RPC error (${agentPath}/${tool}): ${rpcResponse.error.message}`,
656
+ `Registry RPC error: ${rpcResponse.error.message}`,
603
657
  );
604
658
  }
605
659
 
@@ -607,10 +661,10 @@ export async function createRegistryConsumer(
607
661
  if (mcpResult?.isError) {
608
662
  const errorText =
609
663
  mcpResult.content?.map((c) => c.text).join("\n") ?? "Unknown error";
610
- throw new Error(`Tool call error (${agentPath}/${tool}): ${errorText}`);
664
+ throw new Error(`Registry call error: ${errorText}`);
611
665
  }
612
666
 
613
- // Parse text content - call_agent returns JSON-stringified results
667
+ // Parse text content
614
668
  const textContent = mcpResult?.content?.find((c) => c.type === "text");
615
669
  if (textContent?.text) {
616
670
  try {
@@ -623,6 +677,21 @@ export async function createRegistryConsumer(
623
677
  return mcpResult;
624
678
  }
625
679
 
680
+ // Call a tool via a registry (convenience wrapper)
681
+ async function callTool(
682
+ registry: ResolvedRegistry,
683
+ agentPath: string,
684
+ tool: string,
685
+ params: Record<string, unknown>,
686
+ ): Promise<unknown> {
687
+ return callRegistry(registry, {
688
+ action: "execute_tool",
689
+ path: agentPath,
690
+ tool,
691
+ params,
692
+ });
693
+ }
694
+
626
695
  // Build the consumer
627
696
  const consumer: RegistryConsumer = {
628
697
  async list(): Promise<AgentListing[]> {
@@ -721,7 +790,43 @@ export async function createRegistryConsumer(
721
790
  return callTool(registry, ref.ref, tool, params);
722
791
  },
723
792
 
724
- discover,
793
+ discover(registryUrl: string) {
794
+ // Find matching resolved registry for auth headers
795
+ const registry = resolvedRegistries.find((r) => r.url === registryUrl);
796
+ return discover(registryUrl, registry);
797
+ },
798
+
799
+ async inspect(
800
+ agentPath: string,
801
+ registryUrl?: string,
802
+ ): Promise<AgentListing | null> {
803
+ const targetRegistries = registryUrl
804
+ ? resolvedRegistries.filter((r) => r.url === registryUrl || r.name === registryUrl)
805
+ : resolvedRegistries;
806
+
807
+ // Parallel O(1) lookups via describe_tools
808
+ const results = await Promise.allSettled(
809
+ targetRegistries.map(async (registry) => {
810
+ const data = (await callRegistry(registry, {
811
+ action: "describe_tools",
812
+ path: agentPath,
813
+ tools: [],
814
+ })) as { tools?: unknown[]; description?: string } | null;
815
+ if (!data) return null;
816
+ return {
817
+ path: agentPath,
818
+ publisher: registry.publisher,
819
+ tools: data.tools,
820
+ description: data.description,
821
+ } as AgentListing;
822
+ }),
823
+ );
824
+
825
+ for (const r of results) {
826
+ if (r.status === "fulfilled" && r.value) return r.value;
827
+ }
828
+ return null;
829
+ },
725
830
 
726
831
  async resolveSecret(url: string): Promise<string> {
727
832
  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;