@slashfi/agents-sdk 0.28.3 → 0.30.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 (59) hide show
  1. package/dist/agent-definitions/auth.d.ts +2 -6
  2. package/dist/agent-definitions/auth.d.ts.map +1 -1
  3. package/dist/agent-definitions/auth.js +5 -5
  4. package/dist/agent-definitions/auth.js.map +1 -1
  5. package/dist/agent-definitions/integrations.d.ts.map +1 -1
  6. package/dist/agent-definitions/integrations.js +0 -1
  7. package/dist/agent-definitions/integrations.js.map +1 -1
  8. package/dist/cjs/agent-definitions/auth.js +5 -5
  9. package/dist/cjs/agent-definitions/auth.js.map +1 -1
  10. package/dist/cjs/agent-definitions/integrations.js +0 -1
  11. package/dist/cjs/agent-definitions/integrations.js.map +1 -1
  12. package/dist/cjs/define-config.js +1 -5
  13. package/dist/cjs/define-config.js.map +1 -1
  14. package/dist/cjs/define.js +2 -0
  15. package/dist/cjs/define.js.map +1 -1
  16. package/dist/cjs/index.js +4 -3
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/registry-consumer.js +81 -48
  19. package/dist/cjs/registry-consumer.js.map +1 -1
  20. package/dist/cjs/secret-collection.js.map +1 -1
  21. package/dist/cjs/server.js +20 -27
  22. package/dist/cjs/server.js.map +1 -1
  23. package/dist/define-config.d.ts +18 -21
  24. package/dist/define-config.d.ts.map +1 -1
  25. package/dist/define-config.js +1 -5
  26. package/dist/define-config.js.map +1 -1
  27. package/dist/define.d.ts +11 -0
  28. package/dist/define.d.ts.map +1 -1
  29. package/dist/define.js +2 -0
  30. package/dist/define.js.map +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/registry-consumer.d.ts +2 -2
  36. package/dist/registry-consumer.d.ts.map +1 -1
  37. package/dist/registry-consumer.js +82 -49
  38. package/dist/registry-consumer.js.map +1 -1
  39. package/dist/secret-collection.d.ts +0 -1
  40. package/dist/secret-collection.d.ts.map +1 -1
  41. package/dist/secret-collection.js.map +1 -1
  42. package/dist/server.d.ts +3 -2
  43. package/dist/server.d.ts.map +1 -1
  44. package/dist/server.js +19 -27
  45. package/dist/server.js.map +1 -1
  46. package/dist/types.d.ts +11 -0
  47. package/dist/types.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/agent-definitions/auth.ts +5 -11
  50. package/src/agent-definitions/integrations.ts +0 -1
  51. package/src/consumer.test.ts +8 -8
  52. package/src/define-config.ts +17 -31
  53. package/src/define.ts +15 -0
  54. package/src/index.ts +2 -1
  55. package/src/registry-consumer.ts +104 -56
  56. package/src/secret-collection.ts +0 -1
  57. package/src/server.test.ts +3 -3
  58. package/src/server.ts +20 -31
  59. package/src/types.ts +13 -0
@@ -38,7 +38,7 @@ import type {
38
38
  ResolvedRegistry,
39
39
  } from "./define-config.js";
40
40
  import {
41
- isSecretUrl,
41
+ isSecretUri,
42
42
  normalizeRef,
43
43
  normalizeRegistry,
44
44
  } from "./define-config.js";
@@ -55,6 +55,72 @@ export const REGISTRY_TYPE_HTTPS = "https";
55
55
  /** Built-in registry types that bypass normal registry resolution */
56
56
  const DIRECT_REGISTRY_TYPES = new Set([REGISTRY_TYPE_MCP, REGISTRY_TYPE_HTTPS]);
57
57
 
58
+ /** Regex for {{secret-uri}} template syntax */
59
+ const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
60
+
61
+ /** Check if a string contains {{...}} template expressions */
62
+ function hasTemplates(value: string): boolean {
63
+ return TEMPLATE_REGEX.test(value);
64
+ }
65
+
66
+ /**
67
+ * Resolve {{secret-uri}} templates in a string.
68
+ * E.g. "Bearer {{file:///.secrets/key}}" → "Bearer actual-key-value"
69
+ */
70
+ async function resolveTemplateString(
71
+ value: string,
72
+ resolver: SecretResolver,
73
+ auth?: { token?: string },
74
+ ): Promise<string> {
75
+ // Reset regex state
76
+ TEMPLATE_REGEX.lastIndex = 0;
77
+ const matches = [...value.matchAll(/\{\{(.+?)\}\}/g)];
78
+ if (matches.length === 0) return value;
79
+
80
+ let result = value;
81
+ for (const match of matches) {
82
+ const uri = match[1]!.trim();
83
+ const resolved = await resolver(uri, auth);
84
+ result = result.replace(match[0], resolved);
85
+ }
86
+ return result;
87
+ }
88
+
89
+ /**
90
+ * Recursively resolve {{secret-uri}} templates in an object.
91
+ * Walks all string values at any depth.
92
+ */
93
+ async function resolveTemplates<T>(
94
+ obj: T,
95
+ resolver: SecretResolver,
96
+ auth?: { token?: string },
97
+ ): Promise<T> {
98
+ if (typeof obj === 'string') {
99
+ // Handle {{secret-uri}} templates
100
+ if (hasTemplates(obj)) {
101
+ return (await resolveTemplateString(obj, resolver, auth)) as T;
102
+ }
103
+ // Handle raw secret URIs (backward compat)
104
+ if (isSecretUri(obj)) {
105
+ return (await resolver(obj, auth)) as T;
106
+ }
107
+ return obj;
108
+ }
109
+ if (Array.isArray(obj)) {
110
+ return (await Promise.all(
111
+ obj.map((item) => resolveTemplates(item, resolver, auth)),
112
+ )) as T;
113
+ }
114
+ if (obj !== null && typeof obj === 'object') {
115
+ const result: Record<string, unknown> = {};
116
+ for (const [key, value] of Object.entries(obj)) {
117
+ result[key] = await resolveTemplates(value, resolver, auth);
118
+ }
119
+ return result as T;
120
+ }
121
+ return obj;
122
+ }
123
+
58
124
  // ============================================
59
125
  // Registry Discovery Types
60
126
  // ============================================
@@ -378,10 +444,10 @@ export interface RegistryConsumer {
378
444
  /** Resolve a secret URL to its value */
379
445
  resolveSecret(url: string): Promise<string>;
380
446
 
381
- /** Resolve all secret URLs in a config object, returning resolved values */
447
+ /** Resolve {{secret-uri}} templates in a config object (recursive) */
382
448
  resolveConfig(
383
449
  config: RefConfig,
384
- ): Promise<Record<string, string | number | boolean>>;
450
+ ): Promise<RefConfig>;
385
451
 
386
452
  /** Produce the indexed/serialized config output */
387
453
  index(): ResolvedConfig;
@@ -408,13 +474,7 @@ export async function createRegistryConsumer(
408
474
 
409
475
  // Normalize refs
410
476
  const resolvedRefs: ResolvedRef[] = (config.refs ?? []).map((entry) => {
411
- const normalized = normalizeRef(entry);
412
- return {
413
- ref: normalized.ref,
414
- name: normalized.name,
415
- registry: normalized.registry ?? resolvedRegistries[0]?.url ?? "unknown",
416
- config: normalized.config,
417
- };
477
+ return normalizeRef(entry);
418
478
  });
419
479
 
420
480
  // Cache for registry configurations
@@ -526,25 +586,19 @@ export async function createRegistryConsumer(
526
586
 
527
587
  // Also collect from direct MCP/HTTPS refs
528
588
  for (const ref of resolvedRefs) {
529
- if (!DIRECT_REGISTRY_TYPES.has(ref.registry)) continue;
530
- const refEntry = (config.refs ?? []).find((r) => {
531
- const n = normalizeRef(r);
532
- return n.name === ref.name;
533
- });
534
- const url =
535
- typeof refEntry === "object" ? refEntry?.url : undefined;
536
- if (!url) continue;
589
+ if (!ref.scheme || !DIRECT_REGISTRY_TYPES.has(ref.scheme)) continue;
590
+ if (!ref.url) continue;
537
591
 
538
592
  try {
539
- if (ref.registry === REGISTRY_TYPE_MCP) {
593
+ if (ref.scheme === REGISTRY_TYPE_MCP) {
540
594
  const mcpListings = await listFromMcpServer(
541
- url,
595
+ ref.url,
542
596
  { token: options.token },
543
597
  fetchFn,
544
598
  );
545
599
  listings.push(...mcpListings);
546
- } else if (ref.registry === REGISTRY_TYPE_HTTPS) {
547
- listings.push(...listFromHttpsApi(url));
600
+ } else if (ref.scheme === REGISTRY_TYPE_HTTPS) {
601
+ listings.push(...listFromHttpsApi(ref.url));
548
602
  }
549
603
  } catch {
550
604
  // Skip unreachable direct refs during list
@@ -574,39 +628,43 @@ export async function createRegistryConsumer(
574
628
  );
575
629
  }
576
630
 
631
+ // Resolve config headers ({{secret-uri}} templates)
632
+ const configHeaders = ref.config?.headers as
633
+ | Record<string, string>
634
+ | undefined;
635
+ const resolvedHeaders = configHeaders
636
+ ? await resolveTemplates(configHeaders, resolveSecretFn, {
637
+ token: options.token,
638
+ })
639
+ : undefined;
640
+ const auth = { token: options.token, headers: resolvedHeaders };
641
+
577
642
  // Direct MCP ref — bypass registry, call MCP server directly
578
- if (ref.registry === REGISTRY_TYPE_MCP) {
579
- const refEntry = (config.refs ?? []).find((r) => {
580
- const n = normalizeRef(r);
581
- return n.name === ref.name;
582
- });
583
- const url = typeof refEntry === "object" ? refEntry?.url : undefined;
584
- if (!url) {
643
+ if (ref.scheme === REGISTRY_TYPE_MCP) {
644
+ if (!ref.url) {
585
645
  throw new Error(`MCP ref "${refName}" has no url`);
586
646
  }
587
- return callMcpTool(url, tool, params, { token: options.token }, fetchFn);
647
+ return callMcpTool(ref.url, tool, params, auth, fetchFn);
588
648
  }
589
649
 
590
650
  // Direct HTTPS ref — bypass registry, call REST API directly
591
- if (ref.registry === REGISTRY_TYPE_HTTPS) {
592
- const refEntry = (config.refs ?? []).find((r) => {
593
- const n = normalizeRef(r);
594
- return n.name === ref.name;
595
- });
596
- const url = typeof refEntry === "object" ? refEntry?.url : undefined;
597
- if (!url) {
651
+ if (ref.scheme === REGISTRY_TYPE_HTTPS) {
652
+ if (!ref.url) {
598
653
  throw new Error(`HTTPS ref "${refName}" has no url`);
599
654
  }
600
- return callHttpsTool(url, tool, params, { token: options.token }, fetchFn);
655
+ return callHttpsTool(ref.url, tool, params, auth, fetchFn);
601
656
  }
602
657
 
603
658
  // Standard registry ref
604
- const registry = resolvedRegistries.find(
605
- (r) => r.url === ref.registry || r.name === ref.registry,
606
- );
659
+ const registryUrl = ref.sourceRegistry?.url;
660
+ const registry = registryUrl
661
+ ? resolvedRegistries.find(
662
+ (r) => r.url === registryUrl || r.name === registryUrl,
663
+ )
664
+ : resolvedRegistries[0]; // Default to first registry if no source specified
607
665
  if (!registry) {
608
666
  throw new Error(
609
- `Registry "${ref.registry}" not found for ref "${refName}"`,
667
+ `Registry not found for ref "${refName}"${registryUrl ? ` (source: ${registryUrl})` : ''}`,
610
668
  );
611
669
  }
612
670
 
@@ -619,20 +677,10 @@ export async function createRegistryConsumer(
619
677
  return resolveSecretFn(url, { token: options.token });
620
678
  },
621
679
 
622
- async resolveConfig(
623
- config: RefConfig,
624
- ): Promise<Record<string, string | number | boolean>> {
625
- const resolved: Record<string, string | number | boolean> = {};
626
- for (const [key, value] of Object.entries(config)) {
627
- if (isSecretUrl(value)) {
628
- resolved[key] = await resolveSecretFn(value as string, {
629
- token: options.token,
630
- });
631
- } else {
632
- resolved[key] = value;
633
- }
634
- }
635
- return resolved;
680
+ async resolveConfig(config: RefConfig): Promise<RefConfig> {
681
+ return resolveTemplates(config, resolveSecretFn, {
682
+ token: options.token,
683
+ });
636
684
  },
637
685
 
638
686
  index(): ResolvedConfig {
@@ -27,7 +27,6 @@ export interface PendingCollection {
27
27
  callerId: string;
28
28
  callerType: string;
29
29
  scopes?: string[];
30
- isRoot?: boolean;
31
30
  };
32
31
  createdAt: number;
33
32
  }
@@ -241,7 +241,7 @@ describe("atlas ↔ registry E2E", () => {
241
241
  const consumer = await createRegistryConsumer(
242
242
  {
243
243
  registries: [REGISTRY_URL],
244
- refs: ["notion"],
244
+ refs: [{ ref: "notion" }],
245
245
  },
246
246
  { token: authToken },
247
247
  );
@@ -256,7 +256,7 @@ describe("atlas ↔ registry E2E", () => {
256
256
  const consumer = await createRegistryConsumer(
257
257
  {
258
258
  registries: [REGISTRY_URL],
259
- refs: ["linear"],
259
+ refs: [{ ref: "linear" }],
260
260
  },
261
261
  { token: authToken },
262
262
  );
@@ -269,7 +269,7 @@ describe("atlas ↔ registry E2E", () => {
269
269
  const consumer = await createRegistryConsumer(
270
270
  {
271
271
  registries: [REGISTRY_URL],
272
- refs: ["notion"],
272
+ refs: [{ ref: "notion" }],
273
273
  },
274
274
  { token: authToken },
275
275
  );
package/src/server.ts CHANGED
@@ -180,7 +180,7 @@ interface JsonRpcResponse {
180
180
 
181
181
  export interface AuthConfig {
182
182
  store?: AuthStore;
183
- rootKey?: string;
183
+ /** @deprecated Use JWT scopes instead. Will be removed in a future version. */
184
184
  tokenTtl?: number;
185
185
  }
186
186
 
@@ -189,11 +189,16 @@ export interface ResolvedAuth {
189
189
  callerId: string;
190
190
  callerType: "agent" | "user" | "system";
191
191
  scopes: string[];
192
- isRoot: boolean;
193
192
  /** All JWT claims from the verified token (passthrough) */
194
193
  claims: Record<string, unknown>;
195
194
  }
196
195
 
196
+ /** Check if auth has admin-level access (wildcard or admin scope) */
197
+ export function hasAdminScope(auth: ResolvedAuth | null): boolean {
198
+ if (!auth) return false;
199
+ return auth.scopes.includes("*") || auth.scopes.includes("admin");
200
+ }
201
+
197
202
  // ============================================
198
203
  // HTTP Helpers
199
204
  // ============================================
@@ -265,16 +270,14 @@ export function detectAuth(registry: AgentRegistry): AuthConfig {
265
270
  const authAgent = registry.get("@auth") as
266
271
  | (AgentDefinition & {
267
272
  __authStore?: AuthStore;
268
- __rootKey?: string;
269
273
  __tokenTtl?: number;
270
274
  })
271
275
  | undefined;
272
276
 
273
- if (!authAgent?.__authStore || !authAgent.__rootKey) return {};
277
+ if (!authAgent?.__authStore) return {};
274
278
 
275
279
  return {
276
280
  store: authAgent.__authStore,
277
- rootKey: authAgent.__rootKey,
278
281
  tokenTtl: authAgent.__tokenTtl ?? 3600,
279
282
  };
280
283
  }
@@ -293,17 +296,6 @@ export async function resolveAuth(
293
296
  const [scheme, credential] = authHeader.split(" ", 2);
294
297
  if (scheme?.toLowerCase() !== "bearer" || !credential) return null;
295
298
 
296
- // Root key check
297
- if (authConfig.rootKey && credential === authConfig.rootKey) {
298
- return {
299
- callerId: "root",
300
- callerType: "system",
301
- scopes: ["*"],
302
- isRoot: true,
303
- claims: {},
304
- };
305
- }
306
-
307
299
  // Try ES256 verification against own signing keys
308
300
  const parts = credential.split(".");
309
301
  if (parts.length === 3 && jwksOptions?.signingKeys?.length) {
@@ -315,7 +307,6 @@ export async function resolveAuth(
315
307
  callerId: verified.sub ?? verified.name ?? "unknown",
316
308
  callerType: "agent",
317
309
  scopes: verified.scopes ?? ["*"],
318
- isRoot: false,
319
310
  claims: verified as unknown as Record<string, unknown>,
320
311
  };
321
312
  }
@@ -347,7 +338,6 @@ export async function resolveAuth(
347
338
  callerId: verified.sub ?? verified.name ?? "unknown",
348
339
  callerType: isSystem ? "system" : "agent",
349
340
  scopes,
350
- isRoot: isSystem,
351
341
  claims: verified as unknown as Record<string, unknown>,
352
342
  };
353
343
  }
@@ -379,7 +369,6 @@ export async function resolveAuth(
379
369
  callerId: verified.name || client.name,
380
370
  callerType: "agent",
381
371
  scopes: verified.scopes,
382
- isRoot: false,
383
372
  claims: verified as unknown as Record<string, unknown>,
384
373
  };
385
374
  }
@@ -400,7 +389,6 @@ export async function resolveAuth(
400
389
  callerId: client?.name ?? token.clientId,
401
390
  callerType: "agent",
402
391
  scopes: token.scopes,
403
- isRoot: false,
404
392
  claims: {},
405
393
  };
406
394
  }
@@ -412,7 +400,7 @@ export function canSeeAgent(
412
400
  const visibility = ((agent as any).visibility ??
413
401
  agent.config?.visibility ??
414
402
  "internal") as Visibility;
415
- if (auth?.isRoot) return true;
403
+ if (hasAdminScope(auth)) return true;
416
404
  if (visibility === "public") return true;
417
405
  if (visibility === "internal" && auth) return true;
418
406
  return false;
@@ -445,10 +433,10 @@ function getVisibleTools(
445
433
  "internal") as Visibility;
446
434
  return agent.tools.filter((t) => {
447
435
  const tv = t.visibility;
448
- if (auth?.isRoot) return true;
436
+ if (hasAdminScope(auth)) return true;
449
437
  // Tool has explicit visibility — respect it
450
438
  if (tv === "public") return true;
451
- if (tv === "private") return auth?.isRoot ?? false;
439
+ if (tv === "private") return hasAdminScope(auth) ?? false;
452
440
  if (tv === "internal" && auth) return true;
453
441
  // No explicit tool visibility — inherit from agent
454
442
  if (!tv && agentVisibility === "public") return true;
@@ -622,10 +610,9 @@ export function createAgentServer(
622
610
  req.callerType = auth.callerType;
623
611
  if (!req.metadata) req.metadata = {};
624
612
  req.metadata.scopes = auth.scopes;
625
- req.metadata.isRoot = auth.isRoot;
626
613
  if (auth.issuer) req.metadata.issuer = auth.issuer;
627
614
  }
628
- if (auth?.isRoot) {
615
+ if (hasAdminScope(auth)) {
629
616
  req.callerType = "system";
630
617
  }
631
618
 
@@ -663,7 +650,7 @@ export function createAgentServer(
663
650
  tools: agent.tools
664
651
  .filter((t) => {
665
652
  const tv = t.visibility ?? "internal";
666
- if (auth?.isRoot) return true;
653
+ if (hasAdminScope(auth)) return true;
667
654
  if (tv === "public") return true;
668
655
  if (
669
656
  tv === "authenticated" &&
@@ -707,7 +694,7 @@ export function createAgentServer(
707
694
  for (const agent of visible) {
708
695
  const visibleTools = agent.tools.filter((t) => {
709
696
  const tv = t.visibility ?? "internal";
710
- if (auth?.isRoot) return true;
697
+ if (hasAdminScope(auth)) return true;
711
698
  if (tv === "public") return true;
712
699
  if (
713
700
  tv === "authenticated" &&
@@ -1053,7 +1040,6 @@ export function createAgentServer(
1053
1040
  callerId: actorId,
1054
1041
  callerType: (actorType as any) ?? "agent",
1055
1042
  scopes: ["*"],
1056
- isRoot: false,
1057
1043
  claims: {},
1058
1044
  };
1059
1045
  }
@@ -1281,7 +1267,7 @@ export function createAgentServer(
1281
1267
  tools: agent.tools
1282
1268
  .filter((t) => {
1283
1269
  const tv = t.visibility ?? "internal";
1284
- if (effectiveAuth?.isRoot) return true;
1270
+ if (hasAdminScope(effectiveAuth)) return true;
1285
1271
  if (tv === "public") return true;
1286
1272
  if (tv === "internal" && effectiveAuth) return true;
1287
1273
  return false;
@@ -1309,6 +1295,8 @@ export function createAgentServer(
1309
1295
  name: agent.config?.name,
1310
1296
  description:
1311
1297
  agent.config?.description ?? agent.entrypoint?.slice(0, 200),
1298
+ mode: agent.mode ?? 'direct',
1299
+ ...(agent.upstream && { upstream: agent.upstream }),
1312
1300
  tools: getVisibleTools(agent, effectiveAuth).map((t) => ({
1313
1301
  name: t.name,
1314
1302
  description: t.description,
@@ -1334,6 +1322,8 @@ export function createAgentServer(
1334
1322
  name: agent.config?.name,
1335
1323
  description:
1336
1324
  agent.config?.description ?? agent.entrypoint?.slice(0, 200),
1325
+ mode: agent.mode ?? 'direct',
1326
+ ...(agent.upstream && { upstream: agent.upstream }),
1337
1327
  tools: getVisibleTools(agent, effectiveAuth).map((t) => ({
1338
1328
  name: t.name,
1339
1329
  description: t.description,
@@ -1379,7 +1369,7 @@ export function createAgentServer(
1379
1369
  callerId: effectiveAuth?.callerId,
1380
1370
  callerType: effectiveAuth?.callerType ?? "system",
1381
1371
  metadata: effectiveAuth
1382
- ? { scopes: effectiveAuth.scopes, isRoot: effectiveAuth.isRoot }
1372
+ ? { scopes: effectiveAuth.scopes }
1383
1373
  : undefined,
1384
1374
  });
1385
1375
  const res = jsonResponse({ success: true, result });
@@ -1464,7 +1454,6 @@ export function createAgentServer(
1464
1454
  metadata: effectiveAuth
1465
1455
  ? {
1466
1456
  scopes: effectiveAuth.scopes,
1467
- isRoot: effectiveAuth.isRoot,
1468
1457
  ...(effectiveAuth.issuer
1469
1458
  ? { issuer: effectiveAuth.issuer }
1470
1459
  : {}),
package/src/types.ts CHANGED
@@ -633,6 +633,19 @@ export interface AgentDefinition<TContext extends ToolContext = ToolContext> {
633
633
  /** Tools provided by this agent */
634
634
  tools: ToolDefinition<TContext, unknown, unknown>[];
635
635
 
636
+ /**
637
+ * Registry hosting mode:
638
+ * - 'direct': registry hosts and serves this agent's tools (default)
639
+ * - 'redirect': registry catalogs this agent but clients connect to `upstream` directly
640
+ */
641
+ mode?: 'direct' | 'redirect';
642
+
643
+ /**
644
+ * Upstream URL for redirect-mode agents.
645
+ * When mode is 'redirect', clients should connect to this URL instead of the registry.
646
+ */
647
+ upstream?: string;
648
+
636
649
  /**
637
650
  * Runtime hooks factory.
638
651
  * Called once to create the runtime for this agent.