@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.
- 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 +121 -43
- 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 +121 -43
- 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 +176 -57
- 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
|
// ============================================
|
|
@@ -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
|
|
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
|
|
515
|
-
configuration.
|
|
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 =
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
//
|
|
550
|
-
async function
|
|
615
|
+
// Send any call_agent request through a registry's MCP endpoint
|
|
616
|
+
async function callRegistry(
|
|
551
617
|
registry: ResolvedRegistry,
|
|
552
|
-
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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(`
|
|
671
|
+
throw new Error(`Registry call error: ${errorText}`);
|
|
620
672
|
}
|
|
621
673
|
|
|
622
|
-
// Parse text content
|
|
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 });
|