@slashfi/agents-sdk 0.80.0 → 0.82.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.
@@ -32,37 +32,40 @@ const SECRET_PREFIX = "secret:";
32
32
  * we evaluate satisfaction against the entry's current `config`.
33
33
  *
34
34
  * Behavior:
35
- * - `mode: 'proxy'` refs → always true. Auth lives server-side; the
36
- * proxy is the source of truth, no entry-side fields involved.
37
35
  * - Cache miss (no `authFields` for this ref yet) → returns `null`,
38
36
  * signaling "I don't know — caller should fall back to its own
39
37
  * heuristic or call `auth-status` to populate the cache".
40
38
  * - Cache hit → for every required, non-automated field, checks
41
- * presence in `entry.config`. Mirrors the `present || resolvable`
42
- * check in `auth-status` but evaluates against current config.
43
- * `automated` fields (e.g. dynamic OAuth client_id) count as
44
- * satisfied even when absent adk supplies them at call time.
39
+ * presence in `entry.config` OR (if `opts.resolvableFields`
40
+ * includes the field name) treats it as satisfied externally.
41
+ * Mirrors the `present || resolvable` check in `auth-status`.
42
+ * `automated` fields (e.g. dynamic OAuth client_id minted by the
43
+ * registry) always count as satisfied.
45
44
  *
46
45
  * Returning `null` for cache miss is intentional. A boolean would
47
46
  * force callers to choose a default that's wrong half the time;
48
47
  * `null` lets them branch explicitly.
49
48
  */
50
- export function isRefAuthComplete(entry, cacheEntry) {
49
+ export function isRefAuthComplete(entry, cacheEntry, opts) {
51
50
  if (typeof entry === "string")
52
51
  return false;
53
- if (entry.mode === "proxy")
54
- return true;
55
52
  const authFields = cacheEntry?.authFields;
56
53
  if (!authFields)
57
54
  return null;
58
55
  const config = entry.config ?? {};
56
+ const resolvable = opts?.resolvableFields && opts.resolvableFields.length > 0
57
+ ? new Set(opts.resolvableFields)
58
+ : null;
59
59
  for (const [field, info] of Object.entries(authFields)) {
60
60
  if (!info.required)
61
61
  continue;
62
62
  if (info.automated)
63
63
  continue;
64
- if (!(field in config))
65
- return false;
64
+ if (field in config)
65
+ continue;
66
+ if (resolvable && resolvable.has(field))
67
+ continue;
68
+ return false;
66
69
  }
67
70
  return true;
68
71
  }
@@ -617,78 +620,6 @@ export function createAdk(fs, options = {}) {
617
620
  return fallback;
618
621
  }
619
622
  // ==========================================
620
- // Proxy Routing
621
- // ==========================================
622
- /**
623
- * Find the configured RegistryEntry for a ref, consulting `sourceRegistry`
624
- * first and falling back to the first registry in config. Returns `null` when
625
- * the ref is sourced from a raw URL (no registry), in which case proxy routing
626
- * does not apply.
627
- */
628
- async function findRegistryEntryForRef(entry) {
629
- const sourceUrl = entry.sourceRegistry?.url;
630
- if (!sourceUrl)
631
- return null;
632
- const config = await readConfig();
633
- const match = (config.registries ?? []).find((r) => {
634
- if (typeof r === "string")
635
- return r === sourceUrl;
636
- return r.url === sourceUrl;
637
- });
638
- if (!match || typeof match === "string")
639
- return null;
640
- return match;
641
- }
642
- /**
643
- * Returns the proxy settings for a ref when its source registry has
644
- * `proxy` configured. `null` means "run locally".
645
- *
646
- * Callers pass `{ preferLocal: true }` to opt out of `mode: 'optional'`
647
- * proxying when they already hold credentials locally. `mode: 'required'`
648
- * cannot be bypassed — the registry owns auth server-side and there is
649
- * nothing useful the local SDK can do.
650
- */
651
- async function resolveProxyForRef(entry, opts) {
652
- const reg = await findRegistryEntryForRef(entry);
653
- if (!reg?.proxy)
654
- return null;
655
- if (reg.proxy.mode === "optional" && opts?.preferLocal)
656
- return null;
657
- return { reg, agent: reg.proxy.agent ?? "@config" };
658
- }
659
- /**
660
- * Forward an `@config ref` operation to the proxy agent on a remote registry.
661
- *
662
- * The remote side speaks the standard adk-tools surface, so the call shape is
663
- * identical to what the local `ref` API would do — the only difference is
664
- * that tokens and secrets live server-side. `callRegistry` returns the
665
- * standard CallAgentResponse envelope: `{ success: true, result }` on
666
- * success or `{ success: false, error }` on failure. We unwrap once and
667
- * throw on error so callers get a result that matches the local signature.
668
- */
669
- async function forwardRefOpToProxy(reg, agent, operation, params) {
670
- const consumer = await buildConsumerForRef({
671
- ref: "",
672
- name: "",
673
- sourceRegistry: { url: reg.url, agentPath: agent },
674
- });
675
- const resolved = consumer.registries().find((r) => r.url === reg.url);
676
- if (!resolved)
677
- throw new Error(`Registry ${reg.url} not resolvable for proxy forwarding`);
678
- const response = await consumer.callRegistry(resolved, {
679
- action: "execute_tool",
680
- path: agent,
681
- tool: "ref",
682
- params: { operation, ...params },
683
- });
684
- if (!response.success) {
685
- const errResponse = response;
686
- const msg = errResponse.error ?? `Proxy ${agent}.ref(${operation}) failed`;
687
- throw new Error(msg);
688
- }
689
- return response.result;
690
- }
691
- // ==========================================
692
623
  // Registry API
693
624
  // ==========================================
694
625
  /**
@@ -701,42 +632,6 @@ export function createAdk(fs, options = {}) {
701
632
  return value;
702
633
  return `${SECRET_PREFIX}${await encryptSecret(value, options.encryptionKey)}`;
703
634
  }
704
- /**
705
- * Re-probe a registry with the current stored credentials to see whether it
706
- * advertises `capabilities.registry.proxy` in its MCP `initialize` response,
707
- * and persist the proxy config when it does. Safe to call after a successful
708
- * `auth()` / `authLocal()` — on the add path we skip the proxy probe when
709
- * auth is required, so this is the second chance to back-fill it.
710
- *
711
- * Respects explicit user config: if `proxy` is already set, we leave it
712
- * alone. Any discovery failure is swallowed — proxy is an optimization,
713
- * not a correctness requirement.
714
- */
715
- async function discoverProxyAfterAuth(nameOrUrl) {
716
- const config = await readConfig();
717
- const target = findRegistry(config.registries ?? [], nameOrUrl);
718
- if (!target || typeof target === "string")
719
- return;
720
- if (target.proxy)
721
- return;
722
- try {
723
- const consumer = await buildConsumer(nameOrUrl);
724
- const discovered = await consumer.discover(target.url);
725
- if (!discovered.proxy?.mode)
726
- return;
727
- await updateRegistryEntry(nameOrUrl, (existing) => {
728
- if (existing.proxy)
729
- return;
730
- existing.proxy = {
731
- mode: discovered.proxy.mode,
732
- ...(discovered.proxy.agent && { agent: discovered.proxy.agent }),
733
- };
734
- });
735
- }
736
- catch {
737
- // Proxy probe is best-effort — auth itself already succeeded.
738
- }
739
- }
740
635
  /**
741
636
  * Atomic read-modify-write on a registry entry by name or URL. Used by
742
637
  * `authLocal` to persist both `auth` and `oauth` together, which `auth()`
@@ -877,14 +772,10 @@ export function createAdk(fs, options = {}) {
877
772
  const config = await readConfig();
878
773
  const alias = entry.name ?? entry.url;
879
774
  const registries = (config.registries ?? []).filter((r) => registryDisplayName(r) !== alias);
880
- // Probe the registry before saving. Two things fall out of the probe:
881
- // 1. Auth challenge — 401 + WWW-Authenticate points at RFC 9728
882
- // resource metadata; we persist it on `authRequirement` so
883
- // subsequent ops can refuse early with a friendly message.
884
- // 2. Proxy capability — the MCP `initialize` response may advertise
885
- // `capabilities.registry.proxy`, which auto-populates `proxy`.
886
- // Users who set `proxy` or `auth` explicitly on the entry always win:
887
- // discovery only fills in blanks.
775
+ // Probe the registry before saving. If it returns 401 with a
776
+ // WWW-Authenticate / RFC 9728 resource metadata pointer, persist
777
+ // that on `authRequirement` so subsequent ops can refuse early
778
+ // with a friendly message.
888
779
  let final = entry;
889
780
  let authRequirement;
890
781
  const hasUsableAuth = entry.auth && entry.auth.type !== "none"
@@ -899,30 +790,6 @@ export function createAdk(fs, options = {}) {
899
790
  final = { ...final, authRequirement };
900
791
  }
901
792
  }
902
- if (!entry.proxy && !authRequirement) {
903
- try {
904
- const probeConsumer = await createRegistryConsumer({ registries: [entry], refs: [] }, { token: options.token, fetch: options.fetch });
905
- const resolved = probeConsumer.registries()[0];
906
- if (resolved) {
907
- const discovered = await probeConsumer.discover(resolved.url);
908
- if (discovered.proxy?.mode) {
909
- final = {
910
- ...final,
911
- proxy: {
912
- mode: discovered.proxy.mode,
913
- ...(discovered.proxy.agent && {
914
- agent: discovered.proxy.agent,
915
- }),
916
- },
917
- };
918
- }
919
- }
920
- }
921
- catch {
922
- // Discovery is best-effort — offline, unreachable, or non-adk
923
- // registries simply skip proxy auto-configuration.
924
- }
925
- }
926
793
  registries.push(final);
927
794
  await writeConfig({ ...config, registries });
928
795
  return authRequirement ? { authRequirement } : {};
@@ -968,8 +835,6 @@ export function createAdk(fs, options = {}) {
968
835
  existing.auth = updates.auth;
969
836
  if (updates.headers)
970
837
  existing.headers = { ...existing.headers, ...updates.headers };
971
- if (updates.proxy !== undefined)
972
- existing.proxy = updates.proxy;
973
838
  return existing;
974
839
  });
975
840
  if (!found)
@@ -1069,8 +934,6 @@ export function createAdk(fs, options = {}) {
1069
934
  }
1070
935
  delete existing.authRequirement;
1071
936
  });
1072
- if (updated)
1073
- await discoverProxyAfterAuth(nameOrUrl);
1074
937
  return updated;
1075
938
  },
1076
939
  async authLocal(nameOrUrl, opts) {
@@ -1217,7 +1080,6 @@ export function createAdk(fs, options = {}) {
1217
1080
  };
1218
1081
  delete existing.authRequirement;
1219
1082
  });
1220
- await discoverProxyAfterAuth(displayName);
1221
1083
  resOut.writeHead(200, { "Content-Type": "text/html" });
1222
1084
  resOut.end(renderAuthSuccess(displayName));
1223
1085
  server.close();
@@ -1596,12 +1458,6 @@ export function createAdk(fs, options = {}) {
1596
1458
  const entry = findRef(config.refs ?? [], name);
1597
1459
  if (!entry)
1598
1460
  throw new Error(`Ref "${name}" not found`);
1599
- // Registry-proxied refs: ask the remote @config for state (secrets live
1600
- // server-side so local inspection would always return "missing").
1601
- const proxy = await resolveProxyForRef(entry);
1602
- if (proxy) {
1603
- return forwardRefOpToProxy(proxy.reg, proxy.agent, "auth-status", { name });
1604
- }
1605
1461
  let security = null;
1606
1462
  try {
1607
1463
  const consumer = await buildConsumerForRef(entry);
@@ -1723,25 +1579,6 @@ export function createAdk(fs, options = {}) {
1723
1579
  const entry = findRef(config.refs ?? [], name);
1724
1580
  if (!entry)
1725
1581
  throw new Error(`Ref "${name}" not found`);
1726
- // Registry-proxied auth: forward the start-of-flow to the remote @config
1727
- // agent. The registry owns the client_id/secret and returns an authorize
1728
- // URL pointing at the registry's OAuth callback domain, so the user
1729
- // completes the flow against the registry instead of localhost.
1730
- const proxy = await resolveProxyForRef(entry, {
1731
- preferLocal: opts?.preferLocal,
1732
- });
1733
- if (proxy) {
1734
- const params = { name };
1735
- if (opts?.apiKey !== undefined)
1736
- params.apiKey = opts.apiKey;
1737
- if (opts?.credentials)
1738
- params.credentials = opts.credentials;
1739
- if (opts?.scopes)
1740
- params.scopes = opts.scopes;
1741
- if (opts?.stateContext)
1742
- params.stateContext = opts.stateContext;
1743
- return forwardRefOpToProxy(proxy.reg, proxy.agent, "auth", params);
1744
- }
1745
1582
  const status = await ref.authStatus(name);
1746
1583
  const security = status.security;
1747
1584
  const tryResolve = makeTryResolve({ name, entry, security });
@@ -1995,32 +1832,16 @@ export function createAdk(fs, options = {}) {
1995
1832
  return { type: security.type, complete: false };
1996
1833
  },
1997
1834
  async authLocal(name, opts) {
1998
- // `ref.auth` is already proxy-aware — for proxied refs it returns
1999
- // the authorizeUrl that the registry minted against its own
2000
- // callback domain. Everything below is identical for local and
2001
- // proxied refs except the last step (polling for the callback),
2002
- // which only makes sense when we own the redirect URI.
2003
1835
  const result = await ref.auth(name);
2004
1836
  if (result.complete)
2005
1837
  return { complete: true };
2006
- const config = await readConfig();
2007
- const entry = findRef(config.refs ?? [], name);
2008
- const proxy = entry ? await resolveProxyForRef(entry) : null;
2009
1838
  const port = options.oauthCallbackPort ?? 8919;
2010
1839
  const timeout = opts?.timeoutMs ?? 300_000;
2011
1840
  const { createServer } = await import("node:http");
2012
1841
  // API key / HTTP auth — local credential form.
2013
- //
2014
- // We refuse to serve the form for a proxied ref: the registry
2015
- // owns the credential store, so the user needs to submit via
2016
- // whatever UI the registry exposes. Supporting this through the
2017
- // proxy would need a remote form endpoint — out of scope here.
2018
1842
  if (result.fields &&
2019
1843
  result.fields.length > 0 &&
2020
1844
  result.type !== "oauth2") {
2021
- if (proxy) {
2022
- throw new Error(`Ref "${name}" is sourced from a proxied registry; submit credentials through ${proxy.agent} instead of a local form.`);
2023
- }
2024
1845
  return new Promise((resolve, reject) => {
2025
1846
  const server = createServer(async (req, res) => {
2026
1847
  const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
@@ -2082,13 +1903,8 @@ export function createAdk(fs, options = {}) {
2082
1903
  if (opts?.onAuthorizeUrl) {
2083
1904
  opts.onAuthorizeUrl(result.authorizeUrl);
2084
1905
  }
2085
- // Proxied refs: the registry owns the callback endpoint, so there's
2086
- // nothing to poll here. Callers poll `ref.authStatus` on their own
2087
- // schedule once the user finishes the remote consent screen.
2088
- if (proxy)
2089
- return { complete: false };
2090
- // Local refs: spin up the callback server on oauthCallbackPort and
2091
- // block until the OAuth provider redirects back.
1906
+ // Spin up the callback server on oauthCallbackPort and block
1907
+ // until the OAuth provider redirects back.
2092
1908
  return new Promise((resolve, reject) => {
2093
1909
  const server = createServer(async (req, res) => {
2094
1910
  const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
@@ -2127,14 +1943,6 @@ export function createAdk(fs, options = {}) {
2127
1943
  });
2128
1944
  },
2129
1945
  async refreshToken(name) {
2130
- // Registry-proxied refs: the remote @config holds the refresh_token.
2131
- const entryForProxy = await ref.get(name);
2132
- if (entryForProxy) {
2133
- const proxy = await resolveProxyForRef(entryForProxy);
2134
- if (proxy) {
2135
- return forwardRefOpToProxy(proxy.reg, proxy.agent, "refresh-token", { name });
2136
- }
2137
- }
2138
1946
  // Read stored refresh_token
2139
1947
  const refreshToken = await readRefSecret(name, "refresh_token");
2140
1948
  if (!refreshToken)