@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.
- package/dist/auth-governance.d.ts +37 -0
- package/dist/auth-governance.d.ts.map +1 -0
- package/dist/auth-governance.js +73 -0
- package/dist/auth-governance.js.map +1 -0
- package/dist/call-agent-schema.d.ts +20 -0
- package/dist/call-agent-schema.d.ts.map +1 -1
- package/dist/call-agent-schema.js +19 -0
- package/dist/call-agent-schema.js.map +1 -1
- package/dist/cjs/auth-governance.js +79 -0
- package/dist/cjs/auth-governance.js.map +1 -0
- package/dist/cjs/call-agent-schema.js +20 -1
- package/dist/cjs/call-agent-schema.js.map +1 -1
- package/dist/cjs/define-config.js +1 -0
- package/dist/cjs/define-config.js.map +1 -1
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/key-manager.js +9 -4
- package/dist/cjs/key-manager.js.map +1 -1
- package/dist/cjs/registry-consumer.js +122 -37
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/server.js +149 -209
- package/dist/cjs/server.js.map +1 -1
- package/dist/define-config.d.ts +8 -0
- package/dist/define-config.d.ts.map +1 -1
- package/dist/define-config.js +1 -0
- package/dist/define-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/key-manager.js +9 -4
- package/dist/key-manager.js.map +1 -1
- package/dist/registry-consumer.d.ts +4 -0
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js +122 -37
- package/dist/registry-consumer.js.map +1 -1
- package/dist/server.d.ts +3 -13
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +136 -199
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/auth-governance.ts +94 -0
- package/src/call-agent-schema.ts +33 -0
- package/src/codegen.test.ts +10 -0
- package/src/consumer.test.ts +132 -0
- package/src/define-config.ts +12 -0
- package/src/index.ts +2 -0
- package/src/key-manager.test.ts +17 -0
- package/src/key-manager.ts +10 -4
- package/src/registry-consumer.ts +161 -37
- package/src/server.ts +180 -215
package/src/registry-consumer.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
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
|
|
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
|
-
//
|
|
550
|
-
async function
|
|
620
|
+
// Send any call_agent request through a registry's MCP endpoint
|
|
621
|
+
async function callRegistry(
|
|
551
622
|
registry: ResolvedRegistry,
|
|
552
|
-
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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(`
|
|
676
|
+
throw new Error(`Registry call error: ${errorText}`);
|
|
620
677
|
}
|
|
621
678
|
|
|
622
|
-
// Parse text content
|
|
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 });
|