@slashfi/agents-sdk 0.40.1 → 0.41.1

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 (45) hide show
  1. package/dist/adk-error.d.ts +35 -0
  2. package/dist/adk-error.d.ts.map +1 -0
  3. package/dist/adk-error.js +68 -0
  4. package/dist/adk-error.js.map +1 -0
  5. package/dist/adk-tools.d.ts +31 -0
  6. package/dist/adk-tools.d.ts.map +1 -0
  7. package/dist/adk-tools.js +140 -0
  8. package/dist/adk-tools.js.map +1 -0
  9. package/dist/adk.js +114 -9
  10. package/dist/adk.js.map +1 -1
  11. package/dist/call-agent-schema.d.ts +96 -96
  12. package/dist/cjs/adk-error.js +75 -0
  13. package/dist/cjs/adk-error.js.map +1 -0
  14. package/dist/cjs/adk-tools.js +143 -0
  15. package/dist/cjs/adk-tools.js.map +1 -0
  16. package/dist/cjs/config-store.js +149 -53
  17. package/dist/cjs/config-store.js.map +1 -1
  18. package/dist/cjs/define-config.js.map +1 -1
  19. package/dist/cjs/index.js +8 -1
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/registry-consumer.js +1 -0
  22. package/dist/cjs/registry-consumer.js.map +1 -1
  23. package/dist/config-store.d.ts +46 -7
  24. package/dist/config-store.d.ts.map +1 -1
  25. package/dist/config-store.js +149 -53
  26. package/dist/config-store.js.map +1 -1
  27. package/dist/define-config.d.ts +17 -0
  28. package/dist/define-config.d.ts.map +1 -1
  29. package/dist/define-config.js.map +1 -1
  30. package/dist/index.d.ts +5 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/registry-consumer.d.ts +2 -0
  35. package/dist/registry-consumer.d.ts.map +1 -1
  36. package/dist/registry-consumer.js +1 -0
  37. package/dist/registry-consumer.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/adk-error.ts +80 -0
  40. package/src/adk-tools.ts +156 -0
  41. package/src/adk.ts +104 -7
  42. package/src/config-store.ts +216 -59
  43. package/src/define-config.ts +23 -0
  44. package/src/index.ts +8 -0
  45. package/src/registry-consumer.ts +4 -0
@@ -20,6 +20,7 @@
20
20
  import type { FsStore } from "./agent-definitions/config.js";
21
21
  import type {
22
22
  ConsumerConfig,
23
+ ProxyEntry,
23
24
  RefEntry,
24
25
  RegistryEntry,
25
26
  ResolvedRef,
@@ -33,6 +34,7 @@ import type {
33
34
  } from "./registry-consumer.js";
34
35
  import type { CallAgentResponse, SecuritySchemeSummary } from "./types.js";
35
36
  import { decryptSecret, encryptSecret } from "./crypto.js";
37
+ import { AdkError } from "./adk-error.js";
36
38
  import {
37
39
  discoverOAuthMetadata,
38
40
  dynamicClientRegistration,
@@ -47,6 +49,29 @@ const SECRET_PREFIX = "secret:";
47
49
  // Types
48
50
  // ============================================
49
51
 
52
+ /** Context passed to the resolveCredentials callback */
53
+ export interface ResolveCredentialsContext {
54
+ /** Ref name */
55
+ ref: string;
56
+ /** Credential field being resolved (e.g. "client_id", "client_secret", "api_key") */
57
+ field: string;
58
+ /** The full ref entry from config */
59
+ entry: RefEntry;
60
+ /** Security scheme from the registry */
61
+ security: SecuritySchemeSummary | null;
62
+ /** OAuth metadata if available (from discovery) */
63
+ oauthMetadata?: import("./mcp-client.js").OAuthServerMetadata | null;
64
+ }
65
+
66
+ /**
67
+ * Resolve a credential field for a ref.
68
+ * Called during auth() when the adk needs a credential it can't auto-obtain.
69
+ * Return the value, or null to indicate it's not available.
70
+ */
71
+ export type ResolveCredentials = (
72
+ ctx: ResolveCredentialsContext,
73
+ ) => Promise<string | null>;
74
+
50
75
  export interface AdkOptions {
51
76
  /** Passphrase for encrypting/decrypting secret: values */
52
77
  encryptionKey?: string;
@@ -60,8 +85,14 @@ export interface AdkOptions {
60
85
  oauthCallbackUrl?: string;
61
86
  /** Port for local OAuth callback server (default 8919) */
62
87
  oauthCallbackPort?: number;
63
- /** Client name for OAuth dynamic client registration (default: "Claude Code") */
88
+ /** Client name for OAuth dynamic client registration (default: "adk") */
64
89
  oauthClientName?: string;
90
+ /**
91
+ * Resolve preconfigured credentials for a ref.
92
+ * Used by atlas to inject platform-level or tenant-level credentials
93
+ * (e.g. client_id/client_secret) before the user auth flow runs.
94
+ */
95
+ resolveCredentials?: ResolveCredentials;
65
96
  }
66
97
 
67
98
  export interface RegistryTestResult {
@@ -83,16 +114,25 @@ export interface AdkRegistryApi {
83
114
  test(name?: string): Promise<RegistryTestResult[]>;
84
115
  }
85
116
 
117
+ /** Describes a single credential field requirement */
118
+ export interface CredentialField {
119
+ required: boolean;
120
+ /** Can be obtained automatically (dynamic registration, OAuth flow) */
121
+ automated: boolean;
122
+ /** Already present in the ref's config */
123
+ present: boolean;
124
+ /** Available via resolveCredentials callback */
125
+ resolvable: boolean;
126
+ }
127
+
86
128
  /** Describes what auth a ref needs and what's already provided */
87
129
  export interface RefAuthStatus {
88
130
  name: string;
89
131
  security: SecuritySchemeSummary | null;
90
- /** All required secret fields are present (may be untested) */
132
+ /** All required fields are either present, resolvable, or automated */
91
133
  complete: boolean;
92
- /** Fields that still need to be provided */
93
- missing: string[];
94
- /** Fields already stored */
95
- present: string[];
134
+ /** Per-field breakdown */
135
+ fields: Record<string, CredentialField>;
96
136
  }
97
137
 
98
138
  export interface OAuthResult {
@@ -143,7 +183,14 @@ export interface AdkRefApi {
143
183
  }): Promise<{ complete: boolean }>;
144
184
  }
145
185
 
186
+ export interface AdkProxyApi {
187
+ add(entry: ProxyEntry): Promise<void>;
188
+ remove(name: string): Promise<boolean>;
189
+ list(): Promise<ProxyEntry[]>;
190
+ }
191
+
146
192
  export interface Adk {
193
+ proxy: AdkProxyApi;
147
194
  registry: AdkRegistryApi;
148
195
  ref: AdkRefApi;
149
196
  readConfig(): Promise<ConsumerConfig>;
@@ -547,21 +594,48 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
547
594
 
548
595
  const ref: AdkRefApi = {
549
596
  async add(entry: RefEntry): Promise<{ security: SecuritySchemeSummary | null }> {
597
+ let security: SecuritySchemeSummary | null = null;
598
+
550
599
  const config = await readConfig();
600
+ const hasRegistries = (config.registries ?? []).length > 0;
601
+
602
+ if (hasRegistries) {
603
+ try {
604
+ const consumer = await buildConsumer();
605
+ const info = await consumer.inspect(entry.ref);
606
+
607
+ const agentExists = info && (info.description || info.tools?.length || info.toolSummaries?.length);
608
+ if (!agentExists && entry.sourceRegistry) {
609
+ throw new AdkError({
610
+ code: "REF_NOT_FOUND",
611
+ message: `Agent "${entry.ref}" not found on registry ${entry.sourceRegistry.url}`,
612
+ hint: `Check available agents with: adk registry browse ${entry.sourceRegistry.url}`,
613
+ details: { ref: entry.ref, sourceRegistry: entry.sourceRegistry },
614
+ });
615
+ }
616
+
617
+ if (info?.security) security = info.security;
618
+ if (info?.upstream && !entry.url) {
619
+ entry.url = info.upstream as string;
620
+ entry.scheme = entry.scheme ?? "mcp";
621
+ }
622
+ } catch (err) {
623
+ if (err instanceof AdkError) throw err;
624
+ throw new AdkError({
625
+ code: "REGISTRY_UNREACHABLE",
626
+ message: `Could not reach registry to validate "${entry.ref}"`,
627
+ hint: "Check your registry connection with: adk registry test",
628
+ details: { ref: entry.ref, error: err instanceof Error ? err.message : String(err) },
629
+ cause: err,
630
+ });
631
+ }
632
+ }
633
+
551
634
  const name = refName(entry);
552
635
  const refs = (config.refs ?? []).filter((r) => refName(r) !== name);
553
636
  refs.push(entry);
554
637
  await writeConfig({ ...config, refs });
555
638
 
556
- // Check security requirements
557
- let security: SecuritySchemeSummary | null = null;
558
- try {
559
- const consumer = await buildConsumer();
560
- const info = await consumer.inspect(entry.ref);
561
- if (info?.security) security = info.security;
562
- } catch {
563
- // Non-fatal — registry might be unreachable
564
- }
565
639
  return { security };
566
640
  },
567
641
 
@@ -623,18 +697,8 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
623
697
 
624
698
  // If we have a direct access_token from OAuth, call the agent's MCP server
625
699
  // directly instead of going through the registry
626
- if (accessToken) {
627
- const info = await ref.inspect(name);
628
- // Use upstream URL from registry if available, fall back to deriving from OAuth URL
629
- const upstream = (info as { upstream?: string } | null)?.upstream;
630
- const security = info?.security as { type: string; flows?: { authorizationCode?: { authorizationUrl?: string } } } | undefined;
631
- const mcpUrl = upstream
632
- ?? (security?.flows?.authorizationCode?.authorizationUrl
633
- ? `${new URL(security.flows.authorizationCode.authorizationUrl).origin}/mcp`
634
- : null);
635
- if (mcpUrl) {
636
- return callMcpDirect(mcpUrl, tool, params ?? {}, accessToken);
637
- }
700
+ if (accessToken && entry.url) {
701
+ return callMcpDirect(entry.url, tool, params ?? {}, accessToken);
638
702
  }
639
703
 
640
704
  const consumer = await buildConsumer();
@@ -694,46 +758,112 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
694
758
  // Can't reach registry
695
759
  }
696
760
 
761
+ if (!security || security.type === "none") {
762
+ return { name, security, complete: true, fields: {} };
763
+ }
764
+
697
765
  const configKeys = Object.keys(entry.config ?? {});
766
+ const resolve = options.resolveCredentials;
698
767
 
699
- if (!security || security.type === "none") {
700
- return { name, security, complete: true, missing: [], present: configKeys };
768
+ async function canResolve(field: string, oauthMetadata?: import("./mcp-client.js").OAuthServerMetadata | null): Promise<boolean> {
769
+ if (!resolve || !entry) return false;
770
+ const val = await resolve({ ref: name, field, entry, security, oauthMetadata });
771
+ return val !== null;
701
772
  }
702
773
 
703
- const requiredFields = (() => {
704
- switch (security.type) {
705
- case "oauth2": return ["access_token"];
706
- case "apiKey": return ["api_key"];
707
- case "http": return ["token"];
708
- default: return [];
774
+ const fields: Record<string, CredentialField> = {};
775
+
776
+ if (security.type === "oauth2") {
777
+ const securityExt = security as {
778
+ dynamicRegistration?: boolean;
779
+ discoveryUrl?: string;
780
+ };
781
+ const hasRegistration = !!securityExt.dynamicRegistration;
782
+
783
+ let oauthMetadata: import("./mcp-client.js").OAuthServerMetadata | null = null;
784
+ let needsSecret = false;
785
+ if (securityExt.discoveryUrl) {
786
+ oauthMetadata = await tryFetchOAuthMetadata(securityExt.discoveryUrl);
787
+ if (oauthMetadata) {
788
+ const authMethods = oauthMetadata.token_endpoint_auth_methods_supported ?? [];
789
+ needsSecret = !authMethods.includes("none");
790
+ }
709
791
  }
710
- })();
711
792
 
712
- const missing = requiredFields.filter((f) => !configKeys.includes(f));
713
- const present = requiredFields.filter((f) => configKeys.includes(f));
793
+ fields.client_id = {
794
+ required: true,
795
+ automated: hasRegistration,
796
+ present: configKeys.includes("client_id"),
797
+ resolvable: await canResolve("client_id", oauthMetadata),
798
+ };
799
+ if (needsSecret) {
800
+ fields.client_secret = {
801
+ required: true,
802
+ automated: hasRegistration,
803
+ present: configKeys.includes("client_secret"),
804
+ resolvable: await canResolve("client_secret", oauthMetadata),
805
+ };
806
+ }
807
+ fields.access_token = {
808
+ required: true,
809
+ automated: true,
810
+ present: configKeys.includes("access_token"),
811
+ resolvable: false,
812
+ };
813
+ } else if (security.type === "apiKey") {
814
+ fields.api_key = {
815
+ required: true,
816
+ automated: false,
817
+ present: configKeys.includes("api_key"),
818
+ resolvable: await canResolve("api_key"),
819
+ };
820
+ } else if (security.type === "http") {
821
+ fields.token = {
822
+ required: true,
823
+ automated: false,
824
+ present: configKeys.includes("token"),
825
+ resolvable: await canResolve("token"),
826
+ };
827
+ }
714
828
 
715
- return { name, security, complete: missing.length === 0, missing, present };
829
+ const complete = Object.values(fields).every(
830
+ (f) => !f.required || f.present || f.resolvable || f.automated,
831
+ );
832
+
833
+ return { name, security, complete, fields };
716
834
  },
717
835
 
718
836
  async auth(name: string, opts?: {
719
837
  apiKey?: string;
720
838
  }): Promise<AuthStartResult> {
839
+ const config = await readConfig();
840
+ const entry = (config.refs ?? []).find((r) => refName(r) === name);
841
+ if (!entry) throw new Error(`Ref "${name}" not found`);
842
+
721
843
  const status = await ref.authStatus(name);
722
844
  const security = status.security;
845
+ const resolve = options.resolveCredentials;
846
+
847
+ async function tryResolve(field: string, oauthMetadata?: import("./mcp-client.js").OAuthServerMetadata | null): Promise<string | null> {
848
+ if (!resolve) return null;
849
+ return resolve({ ref: name, field, entry: entry!, security, oauthMetadata });
850
+ }
723
851
 
724
852
  if (!security || security.type === "none") {
725
853
  return { type: "none", complete: true };
726
854
  }
727
855
 
728
856
  if (security.type === "apiKey") {
729
- if (!opts?.apiKey) return { type: "apiKey", complete: false };
730
- await storeRefSecret(name, "api_key", opts.apiKey);
857
+ const key = opts?.apiKey ?? await tryResolve("api_key");
858
+ if (!key) return { type: "apiKey", complete: false };
859
+ await storeRefSecret(name, "api_key", key);
731
860
  return { type: "apiKey", complete: true };
732
861
  }
733
862
 
734
863
  if (security.type === "http") {
735
- if (!opts?.apiKey) return { type: "http", complete: false };
736
- await storeRefSecret(name, "token", opts.apiKey);
864
+ const token = opts?.apiKey ?? await tryResolve("token");
865
+ if (!token) return { type: "http", complete: false };
866
+ await storeRefSecret(name, "token", token);
737
867
  return { type: "http", complete: true };
738
868
  }
739
869
 
@@ -744,11 +874,9 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
744
874
  return { type: "oauth2", complete: false };
745
875
  }
746
876
 
747
- // The authorizationUrl might be the discovery URL itself or a base URL
748
877
  const authUrl = authCodeFlow.authorizationUrl;
749
878
  let metadata = await tryFetchOAuthMetadata(authUrl);
750
879
  if (!metadata) {
751
- // Try base origin
752
880
  const origin = new URL(authUrl).origin;
753
881
  metadata = await discoverOAuthMetadata(origin);
754
882
  }
@@ -758,10 +886,14 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
758
886
 
759
887
  const redirectUri = callbackUrl();
760
888
 
761
- // Dynamic client registration if supported
762
- let clientId: string;
763
- let clientSecret: string | undefined;
764
- if (metadata.registration_endpoint) {
889
+ // Resolve client credentials: callback → stored → dynamic registration
890
+ let clientId = await tryResolve("client_id", metadata)
891
+ ?? await readRefSecret(name, "client_id");
892
+ let clientSecret = await tryResolve("client_secret", metadata)
893
+ ?? await readRefSecret(name, "client_secret")
894
+ ?? undefined;
895
+
896
+ if (!clientId && metadata.registration_endpoint) {
765
897
  const supportedAuthMethods = metadata.token_endpoint_auth_methods_supported ?? ["none"];
766
898
  const preferredMethod = supportedAuthMethods.includes("none")
767
899
  ? "none"
@@ -780,15 +912,12 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
780
912
  if (clientSecret) {
781
913
  await storeRefSecret(name, "client_secret", clientSecret);
782
914
  }
783
- } else {
784
- const stored = await readRefSecret(name, "client_id");
785
- if (!stored) {
786
- throw new Error(
787
- "OAuth server doesn't support dynamic client registration. " +
788
- "Store a client_id first.",
789
- );
790
- }
791
- clientId = stored;
915
+ }
916
+
917
+ if (!clientId) {
918
+ throw new Error(
919
+ "Could not obtain client_id. Provide via resolveCredentials callback or store manually.",
920
+ );
792
921
  }
793
922
 
794
923
  // State ties the callback back to this ref
@@ -906,5 +1035,33 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
906
1035
  return { refName: pending.refName, complete: true };
907
1036
  }
908
1037
 
909
- return { registry, ref, readConfig, writeConfig, handleCallback };
1038
+ // ==========================================
1039
+ // Proxy API
1040
+ // ==========================================
1041
+
1042
+ const proxy: AdkProxyApi = {
1043
+ async add(entry: ProxyEntry): Promise<void> {
1044
+ const config = await readConfig();
1045
+ const proxies = (config.proxies ?? []).filter((p) => p.name !== entry.name);
1046
+ proxies.push(entry);
1047
+ await writeConfig({ ...config, proxies });
1048
+ },
1049
+
1050
+ async remove(name: string): Promise<boolean> {
1051
+ const config = await readConfig();
1052
+ if (!config.proxies?.length) return false;
1053
+ const before = config.proxies.length;
1054
+ const proxies = config.proxies.filter((p) => p.name !== name);
1055
+ if (proxies.length === before) return false;
1056
+ await writeConfig({ ...config, proxies });
1057
+ return true;
1058
+ },
1059
+
1060
+ async list(): Promise<ProxyEntry[]> {
1061
+ const config = await readConfig();
1062
+ return config.proxies ?? [];
1063
+ },
1064
+ };
1065
+
1066
+ return { proxy, registry, ref, readConfig, writeConfig, handleCallback };
910
1067
  }
@@ -86,12 +86,35 @@ export type RefEntry = {
86
86
  status?: 'active' | 'inactive' | 'error';
87
87
  };
88
88
 
89
+ // ============================================
90
+ // Proxy Config
91
+ // ============================================
92
+
93
+ /** A proxy target — remote adk server that handles ref/registry operations */
94
+ export interface ProxyEntry {
95
+ /** Human-readable name */
96
+ name: string;
97
+ /** URL of the remote server */
98
+ url: string;
99
+ /** Connection type: 'mcp' (direct MCP server) or 'registry' (agent on a registry) */
100
+ type: 'mcp' | 'registry';
101
+ /** For type 'registry': the agent path that implements adk tools (e.g. '@config') */
102
+ agent?: string;
103
+ /** Auth for connecting to the proxy */
104
+ auth?: RegistryAuth;
105
+ /** Whether this is the default proxy when no local refs/registries exist */
106
+ default?: boolean;
107
+ }
108
+
89
109
  // ============================================
90
110
  // Consumer Config
91
111
  // ============================================
92
112
 
93
113
  /** The full consumer configuration */
94
114
  export interface ConsumerConfig {
115
+ /** Remote adk proxies — forward operations to a remote server */
116
+ proxies?: ProxyEntry[];
117
+
95
118
  /** Registries to connect to, in resolution order */
96
119
  registries?: (string | RegistryEntry)[];
97
120
 
package/src/index.ts CHANGED
@@ -292,6 +292,7 @@ export {
292
292
  export type {
293
293
  RegistryAuth,
294
294
  RegistryEntry,
295
+ ProxyEntry,
295
296
  RefConfig,
296
297
  RefEntry,
297
298
  ConsumerConfig,
@@ -453,11 +454,18 @@ export { createAdk } from "./config-store.js";
453
454
  export type {
454
455
  Adk,
455
456
  AdkOptions,
457
+ AdkProxyApi,
456
458
  AdkRegistryApi,
457
459
  AdkRefApi,
458
460
  RefAuthStatus,
461
+ CredentialField,
459
462
  AuthStartResult,
460
463
  OAuthResult,
464
+ ResolveCredentials,
465
+ ResolveCredentialsContext,
461
466
  RegistryTestResult,
462
467
  } from "./config-store.js";
463
468
  export { createLocalFsStore, getLocalEncryptionKey } from "./local-fs.js";
469
+ export { AdkError, getError, getRecentErrors } from "./adk-error.js";
470
+ export { createAdkTools } from "./adk-tools.js";
471
+ export type { CreateAdkToolsOptions } from "./adk-tools.js";
@@ -224,6 +224,8 @@ export interface AgentListing {
224
224
  }>;
225
225
  /** Context hint from describe_tools (e.g., "Use full: true for complete schemas") */
226
226
  context?: string;
227
+ /** Upstream MCP/API URL for direct connections */
228
+ upstream?: string;
227
229
  /** Integration config if applicable */
228
230
  integration?: {
229
231
  provider: string;
@@ -928,6 +930,7 @@ export async function createRegistryConsumer(
928
930
  security?: SecuritySchemeSummary;
929
931
  resources?: Array<{ uri: string; name?: string; mimeType?: string }>;
930
932
  context?: string;
933
+ upstream?: string;
931
934
  } | null;
932
935
  if (!data) return null;
933
936
  return {
@@ -939,6 +942,7 @@ export async function createRegistryConsumer(
939
942
  security: data.security,
940
943
  resources: data.resources,
941
944
  context: data.context,
945
+ ...(data.upstream && { upstream: data.upstream }),
942
946
  } as AgentListing;
943
947
  }),
944
948
  );