@slashfi/agents-sdk 0.31.0-pr.dev.9f92286 → 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/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 +15 -7
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/server.js +144 -207
- package/dist/cjs/server.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 +2 -2
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js +15 -7
- 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 +131 -197
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/src/auth-governance.ts +94 -0
- package/src/call-agent-schema.ts +33 -0
- package/src/consumer.test.ts +1 -1
- 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 +20 -8
- package/src/server.ts +175 -213
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Governance
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for visibility and access control decisions.
|
|
5
|
+
* Used by the server, middleware, and any custom implementations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Visibility, AgentDefinition } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// Types
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export interface ResolvedAuth {
|
|
15
|
+
issuer?: string;
|
|
16
|
+
callerId: string;
|
|
17
|
+
callerType: "agent" | "user" | "system";
|
|
18
|
+
scopes: string[];
|
|
19
|
+
/** All JWT claims from the verified token (passthrough) */
|
|
20
|
+
claims: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Governance Functions
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the auth context has admin scope.
|
|
29
|
+
*/
|
|
30
|
+
export function hasAdminScope(auth: ResolvedAuth | null): boolean {
|
|
31
|
+
if (!auth) return false;
|
|
32
|
+
return auth.scopes.includes("*") || auth.scopes.includes("admin");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if an agent is visible to the given auth context.
|
|
37
|
+
*/
|
|
38
|
+
export function canSeeAgent(
|
|
39
|
+
agent: AgentDefinition,
|
|
40
|
+
auth: ResolvedAuth | null,
|
|
41
|
+
): boolean {
|
|
42
|
+
const visibility = ((agent as any).visibility ??
|
|
43
|
+
agent.config?.visibility ??
|
|
44
|
+
"internal") as Visibility;
|
|
45
|
+
if (hasAdminScope(auth)) return true;
|
|
46
|
+
if (visibility === "public") return true;
|
|
47
|
+
if (visibility === "internal" && auth) return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a tool is visible to the given auth context.
|
|
53
|
+
*
|
|
54
|
+
* When agentVisibility is provided, tools without explicit visibility
|
|
55
|
+
* inherit from their parent agent.
|
|
56
|
+
*/
|
|
57
|
+
export function canSeeTool(
|
|
58
|
+
tool: { visibility?: Visibility },
|
|
59
|
+
auth: ResolvedAuth | null,
|
|
60
|
+
agentVisibility?: Visibility,
|
|
61
|
+
): boolean {
|
|
62
|
+
const tv = tool.visibility;
|
|
63
|
+
if (hasAdminScope(auth)) return true;
|
|
64
|
+
// Tool has explicit visibility — respect it
|
|
65
|
+
if (tv === "public") return true;
|
|
66
|
+
if (tv === "private") return false;
|
|
67
|
+
if (
|
|
68
|
+
tv === "authenticated" &&
|
|
69
|
+
auth?.callerId &&
|
|
70
|
+
auth.callerId !== "anonymous"
|
|
71
|
+
)
|
|
72
|
+
return true;
|
|
73
|
+
if (tv === "internal" && auth) return true;
|
|
74
|
+
// No explicit tool visibility — inherit from agent or default to internal
|
|
75
|
+
if (!tv) {
|
|
76
|
+
const inherited = agentVisibility ?? "internal";
|
|
77
|
+
if (inherited === "public") return true;
|
|
78
|
+
if (inherited === "internal" && auth) return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get visible tools for an agent, respecting visibility inheritance.
|
|
85
|
+
*/
|
|
86
|
+
export function getVisibleTools(
|
|
87
|
+
agent: AgentDefinition,
|
|
88
|
+
auth: ResolvedAuth | null,
|
|
89
|
+
): typeof agent.tools {
|
|
90
|
+
const agentVisibility = ((agent as any).visibility ??
|
|
91
|
+
agent.config?.visibility ??
|
|
92
|
+
"internal") as Visibility;
|
|
93
|
+
return agent.tools.filter((t) => canSeeTool(t, auth, agentVisibility));
|
|
94
|
+
}
|
package/src/call-agent-schema.ts
CHANGED
|
@@ -168,3 +168,36 @@ export const callAgentInputSchema = zodToJsonSchema(
|
|
|
168
168
|
callAgentToolInputSchema as any,
|
|
169
169
|
{ target: "openAi" }
|
|
170
170
|
);
|
|
171
|
+
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
// list_agents schema
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
export const listAgentsToolInputSchema = z.object({
|
|
177
|
+
query: z
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe(
|
|
181
|
+
"Search query. When provided, returns agents ranked by BM25 relevance over paths, names, descriptions, and tool names.",
|
|
182
|
+
),
|
|
183
|
+
limit: z
|
|
184
|
+
.number()
|
|
185
|
+
.optional()
|
|
186
|
+
.describe(
|
|
187
|
+
"Maximum number of results per page (default: 20)",
|
|
188
|
+
),
|
|
189
|
+
cursor: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe(
|
|
193
|
+
"Pagination cursor from a previous response's nextCursor field.",
|
|
194
|
+
),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export type ListAgentsInput = z.infer<typeof listAgentsToolInputSchema>;
|
|
198
|
+
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
+
export const listAgentsInputSchema = zodToJsonSchema(
|
|
201
|
+
listAgentsToolInputSchema as any,
|
|
202
|
+
{ target: "openAi" }
|
|
203
|
+
);
|
package/src/consumer.test.ts
CHANGED
|
@@ -663,6 +663,6 @@ describe("Registry Consumer — API Key Auth", () => {
|
|
|
663
663
|
|
|
664
664
|
const agents = await consumer.browse(`http://localhost:${PORT}`);
|
|
665
665
|
expect(agents.length).toBeGreaterThan(0);
|
|
666
|
-
expect(agents
|
|
666
|
+
expect(agents.some((a) => a.path === "@math")).toBe(true);
|
|
667
667
|
});
|
|
668
668
|
});
|
package/src/index.ts
CHANGED
package/src/key-manager.test.ts
CHANGED
|
@@ -118,6 +118,23 @@ describe("KeyManager", () => {
|
|
|
118
118
|
expect(payload.exp - payload.iat).toBe(60);
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
test("respects exp claim instead of default TTL", async () => {
|
|
122
|
+
const store = createMemoryKeyStore();
|
|
123
|
+
km = await createKeyManager({
|
|
124
|
+
store,
|
|
125
|
+
issuer: "http://test:3000",
|
|
126
|
+
tokenTtlSeconds: 300,
|
|
127
|
+
checkIntervalMs: 60_000,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const customExp = Math.floor(Date.now() / 1000) + 86400; // 24h from now
|
|
131
|
+
const token = await km.signJwt({ sub: "custom-exp", exp: customExp });
|
|
132
|
+
const payload = JSON.parse(
|
|
133
|
+
Buffer.from(token.split(".")[1], "base64url").toString(),
|
|
134
|
+
);
|
|
135
|
+
expect(payload.exp).toBe(customExp);
|
|
136
|
+
});
|
|
137
|
+
|
|
121
138
|
// ---- Rotation tests ----
|
|
122
139
|
|
|
123
140
|
test("rotation: creates new key when threshold exceeded", async () => {
|
package/src/key-manager.ts
CHANGED
|
@@ -236,12 +236,18 @@ export async function createKeyManager(
|
|
|
236
236
|
|
|
237
237
|
async signJwt(claims: Record<string, unknown>): Promise<string> {
|
|
238
238
|
const key = getActiveKey();
|
|
239
|
-
|
|
239
|
+
let builder = new SignJWT({ ...claims } as any)
|
|
240
240
|
.setProtectedHeader({ alg: ALG, kid: key.kid })
|
|
241
241
|
.setIssuer(issuer)
|
|
242
|
-
.setIssuedAt()
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
.setIssuedAt();
|
|
243
|
+
|
|
244
|
+
if (claims.exp != null) {
|
|
245
|
+
builder = builder.setExpirationTime(claims.exp as number);
|
|
246
|
+
} else {
|
|
247
|
+
builder = builder.setExpirationTime(`${tokenTtlSeconds}s`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return builder.sign(key.privateKey);
|
|
245
251
|
},
|
|
246
252
|
|
|
247
253
|
async rotate(): Promise<void> {
|
package/src/registry-consumer.ts
CHANGED
|
@@ -505,8 +505,8 @@ export interface RegistryConsumer {
|
|
|
505
505
|
/** Discover a registry's configuration */
|
|
506
506
|
discover(registryUrl: string): Promise<RegistryConfiguration>;
|
|
507
507
|
|
|
508
|
-
/** Browse agents from a specific registry (or all if url omitted) */
|
|
509
|
-
browse(registryUrl?: string): Promise<AgentListing[]>;
|
|
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
510
|
|
|
511
511
|
/** Inspect a specific agent — returns tools, auth requirements, resources */
|
|
512
512
|
inspect(
|
|
@@ -573,15 +573,22 @@ export async function createRegistryConsumer(
|
|
|
573
573
|
return configuration;
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
-
// List agents from a single registry
|
|
576
|
+
// List agents from a single registry, with optional search query
|
|
577
577
|
async function listFromRegistry(
|
|
578
578
|
registry: ResolvedRegistry,
|
|
579
|
+
query?: string,
|
|
579
580
|
): Promise<AgentListing[]> {
|
|
580
581
|
const configuration = await discover(registry.url, registry);
|
|
581
|
-
|
|
582
|
+
let listUrl =
|
|
582
583
|
configuration.agents_endpoint ??
|
|
583
584
|
`${registry.url.replace(/\/$/, "")}/list`;
|
|
584
585
|
|
|
586
|
+
// Append search query if provided
|
|
587
|
+
if (query) {
|
|
588
|
+
const sep = listUrl.includes("?") ? "&" : "?";
|
|
589
|
+
listUrl += `${sep}q=${encodeURIComponent(query)}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
585
592
|
const headers = buildRegistryAuthHeaders(registry, options.token);
|
|
586
593
|
|
|
587
594
|
const res = await fetchFn(listUrl, { headers });
|
|
@@ -591,7 +598,9 @@ export async function createRegistryConsumer(
|
|
|
591
598
|
);
|
|
592
599
|
}
|
|
593
600
|
|
|
594
|
-
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<{
|
|
595
604
|
path: string;
|
|
596
605
|
description?: string;
|
|
597
606
|
tools?: Array<{ name: string; description?: string }>;
|
|
@@ -700,7 +709,7 @@ export async function createRegistryConsumer(
|
|
|
700
709
|
async list(): Promise<AgentListing[]> {
|
|
701
710
|
// Collect from standard registries
|
|
702
711
|
const registryResults = await Promise.allSettled(
|
|
703
|
-
resolvedRegistries.map(listFromRegistry),
|
|
712
|
+
resolvedRegistries.map((r) => listFromRegistry(r)),
|
|
704
713
|
);
|
|
705
714
|
const listings = registryResults.flatMap((r) =>
|
|
706
715
|
r.status === "fulfilled" ? r.value : [],
|
|
@@ -799,14 +808,17 @@ export async function createRegistryConsumer(
|
|
|
799
808
|
return discover(registryUrl, registry);
|
|
800
809
|
},
|
|
801
810
|
|
|
802
|
-
async browse(registryUrl?: string): Promise<AgentListing[]> {
|
|
811
|
+
async browse(registryUrl?: string, query?: string): Promise<AgentListing[]> {
|
|
803
812
|
// List agents from a specific registry, or all registries if not specified
|
|
804
813
|
const targets = registryUrl
|
|
805
814
|
? resolvedRegistries.filter(
|
|
806
815
|
(r) => r.url === registryUrl || r.name === registryUrl,
|
|
807
816
|
)
|
|
808
817
|
: resolvedRegistries;
|
|
809
|
-
|
|
818
|
+
// Pass query to server for BM25 search
|
|
819
|
+
const results = await Promise.allSettled(
|
|
820
|
+
targets.map((t) => listFromRegistry(t, query)),
|
|
821
|
+
);
|
|
810
822
|
return results.flatMap((r) =>
|
|
811
823
|
r.status === "fulfilled" ? r.value : [],
|
|
812
824
|
);
|