@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.
Files changed (44) hide show
  1. package/dist/auth-governance.d.ts +37 -0
  2. package/dist/auth-governance.d.ts.map +1 -0
  3. package/dist/auth-governance.js +73 -0
  4. package/dist/auth-governance.js.map +1 -0
  5. package/dist/call-agent-schema.d.ts +20 -0
  6. package/dist/call-agent-schema.d.ts.map +1 -1
  7. package/dist/call-agent-schema.js +19 -0
  8. package/dist/call-agent-schema.js.map +1 -1
  9. package/dist/cjs/auth-governance.js +79 -0
  10. package/dist/cjs/auth-governance.js.map +1 -0
  11. package/dist/cjs/call-agent-schema.js +20 -1
  12. package/dist/cjs/call-agent-schema.js.map +1 -1
  13. package/dist/cjs/index.js +4 -2
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/cjs/key-manager.js +9 -4
  16. package/dist/cjs/key-manager.js.map +1 -1
  17. package/dist/cjs/registry-consumer.js +15 -7
  18. package/dist/cjs/registry-consumer.js.map +1 -1
  19. package/dist/cjs/server.js +144 -207
  20. package/dist/cjs/server.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  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/key-manager.d.ts.map +1 -1
  26. package/dist/key-manager.js +9 -4
  27. package/dist/key-manager.js.map +1 -1
  28. package/dist/registry-consumer.d.ts +2 -2
  29. package/dist/registry-consumer.d.ts.map +1 -1
  30. package/dist/registry-consumer.js +15 -7
  31. package/dist/registry-consumer.js.map +1 -1
  32. package/dist/server.d.ts +3 -13
  33. package/dist/server.d.ts.map +1 -1
  34. package/dist/server.js +131 -197
  35. package/dist/server.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/auth-governance.ts +94 -0
  38. package/src/call-agent-schema.ts +33 -0
  39. package/src/consumer.test.ts +1 -1
  40. package/src/index.ts +2 -0
  41. package/src/key-manager.test.ts +17 -0
  42. package/src/key-manager.ts +10 -4
  43. package/src/registry-consumer.ts +20 -8
  44. 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
+ }
@@ -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
+ );
@@ -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[0].path).toBe("@math");
666
+ expect(agents.some((a) => a.path === "@math")).toBe(true);
667
667
  });
668
668
  });
package/src/index.ts CHANGED
@@ -136,6 +136,8 @@ export {
136
136
  detectAuth,
137
137
  resolveAuth,
138
138
  canSeeAgent,
139
+ canSeeTool,
140
+ getVisibleTools,
139
141
  hasAdminScope,
140
142
  } from "./server.js";
141
143
  export type {
@@ -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 () => {
@@ -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
- return new SignJWT({ ...claims } as any)
239
+ let builder = new SignJWT({ ...claims } as any)
240
240
  .setProtectedHeader({ alg: ALG, kid: key.kid })
241
241
  .setIssuer(issuer)
242
- .setIssuedAt()
243
- .setExpirationTime(`${tokenTtlSeconds}s`)
244
- .sign(key.privateKey);
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> {
@@ -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
- const listUrl =
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 agents = (await res.json()) as Array<{
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
- const results = await Promise.allSettled(targets.map(listFromRegistry));
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
  );