@slashfi/agents-sdk 0.77.3 → 0.79.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.
@@ -16,13 +16,56 @@
16
16
  * await adk.ref.call('notion', 'notion-search', { query: 'hello' });
17
17
  * ```
18
18
  */
19
+ import { AdkError } from "./adk-error.js";
20
+ import { decryptSecret, encryptSecret } from "./crypto.js";
19
21
  import { normalizeRef } from "./define-config.js";
22
+ import { buildOAuthAuthorizeUrl, discoverOAuthMetadata, dynamicClientRegistration, exchangeCodeForTokens, probeRegistryAuth, refreshAccessToken, } from "./mcp-client.js";
20
23
  import { createRegistryConsumer } from "./registry-consumer.js";
21
- import { decryptSecret, encryptSecret } from "./crypto.js";
22
- import { AdkError } from "./adk-error.js";
23
- import { discoverOAuthMetadata, dynamicClientRegistration, buildOAuthAuthorizeUrl, exchangeCodeForTokens, probeRegistryAuth, refreshAccessToken, } from "./mcp-client.js";
24
24
  const CONFIG_PATH = "consumer-config.json";
25
+ const REGISTRY_CACHE_PATH = "registry-cache.json";
25
26
  const SECRET_PREFIX = "secret:";
27
+ /**
28
+ * "Is this ref ready to call?" answered locally using the cached
29
+ * security-scheme requirements. Mirrors the `complete` boolean
30
+ * `auth-status` returns, but doesn't need a network round-trip — the
31
+ * cached `authFields` capture what the registry said is required, and
32
+ * we evaluate satisfaction against the entry's current `config`.
33
+ *
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
+ * - Cache miss (no `authFields` for this ref yet) → returns `null`,
38
+ * signaling "I don't know — caller should fall back to its own
39
+ * heuristic or call `auth-status` to populate the cache".
40
+ * - 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.
45
+ *
46
+ * Returning `null` for cache miss is intentional. A boolean would
47
+ * force callers to choose a default that's wrong half the time;
48
+ * `null` lets them branch explicitly.
49
+ */
50
+ export function isRefAuthComplete(entry, cacheEntry) {
51
+ if (typeof entry === "string")
52
+ return false;
53
+ if (entry.mode === "proxy")
54
+ return true;
55
+ const authFields = cacheEntry?.authFields;
56
+ if (!authFields)
57
+ return null;
58
+ const config = entry.config ?? {};
59
+ for (const [field, info] of Object.entries(authFields)) {
60
+ if (!info.required)
61
+ continue;
62
+ if (info.automated)
63
+ continue;
64
+ if (!(field in config))
65
+ return false;
66
+ }
67
+ return true;
68
+ }
26
69
  // ============================================
27
70
  // Internal helpers
28
71
  // ============================================
@@ -74,7 +117,9 @@ async function decryptConfigSecrets(obj, encryptionKey) {
74
117
  if (typeof value === "string" && value.startsWith(SECRET_PREFIX)) {
75
118
  result[key] = await decryptSecret(value.slice(SECRET_PREFIX.length), encryptionKey);
76
119
  }
77
- else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
120
+ else if (value !== null &&
121
+ typeof value === "object" &&
122
+ !Array.isArray(value)) {
78
123
  result[key] = await decryptConfigSecrets(value, encryptionKey);
79
124
  }
80
125
  else {
@@ -92,7 +137,7 @@ async function decryptConfigSecrets(obj, encryptionKey) {
92
137
  * Fallback: _httpStatus from tool result body
93
138
  */
94
139
  function isUnauthorized(result) {
95
- if (!result || typeof result !== 'object')
140
+ if (!result || typeof result !== "object")
96
141
  return false;
97
142
  const r = result;
98
143
  // Primary: HTTP status forwarded by the registry and set by callRegistry
@@ -107,17 +152,21 @@ function isUnauthorized(result) {
107
152
  // ============================================
108
153
  // Local auth form HTML
109
154
  // ============================================
110
- const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
155
+ const esc = (s) => s
156
+ .replace(/&/g, "&amp;")
157
+ .replace(/</g, "&lt;")
158
+ .replace(/>/g, "&gt;")
159
+ .replace(/"/g, "&quot;");
111
160
  function renderCredentialForm(name, fields, error) {
112
- const fieldHtml = fields.map((f) => `
161
+ const fieldHtml = fields
162
+ .map((f) => `
113
163
  <div class="field">
114
164
  <label for="${esc(f.name)}">${esc(f.label)}</label>
115
165
  ${f.description ? `<p class="desc">${esc(f.description)}</p>` : ""}
116
166
  <input id="${esc(f.name)}" name="${esc(f.name)}" type="${f.secret ? "password" : "text"}" required autocomplete="off" spellcheck="false" />
117
- </div>`).join("");
118
- const errorHtml = error
119
- ? `<div class="error">${esc(error)}</div>`
120
- : "";
167
+ </div>`)
168
+ .join("");
169
+ const errorHtml = error ? `<div class="error">${esc(error)}</div>` : "";
121
170
  return `<!DOCTYPE html>
122
171
  <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
123
172
  <title>Authenticate \u2014 ${esc(name)}</title>
@@ -180,6 +229,113 @@ export function createAdk(fs, options = {}) {
180
229
  async function writeConfig(config) {
181
230
  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
182
231
  }
232
+ // -------------------------------------------------------------------------
233
+ // Registry cache helpers
234
+ //
235
+ // The cache is purely an internal optimization for the adk's read paths
236
+ // (`ref.list()`, `ref.get()`). Writes happen as side-effects of methods
237
+ // that already call the registry (`ref.add()`, `ref.inspect()`); the
238
+ // public surface never grows new methods. Cache failures (missing file,
239
+ // malformed JSON, fs errors during write) are swallowed so the registry
240
+ // cache can never break a registry operation.
241
+ // -------------------------------------------------------------------------
242
+ async function readRegistryCache() {
243
+ try {
244
+ const content = await fs.readFile(REGISTRY_CACHE_PATH);
245
+ if (!content)
246
+ return { refs: {} };
247
+ const parsed = JSON.parse(content);
248
+ return { refs: parsed.refs ?? {} };
249
+ }
250
+ catch {
251
+ return { refs: {} };
252
+ }
253
+ }
254
+ async function writeRegistryCache(cache) {
255
+ try {
256
+ await fs.writeFile(REGISTRY_CACHE_PATH, JSON.stringify(cache, null, 2));
257
+ }
258
+ catch {
259
+ // Best-effort. A failed cache write should never break the operation
260
+ // that triggered it.
261
+ }
262
+ }
263
+ /**
264
+ * Project an inspect/list response into the slim shape we cache. Drops
265
+ * `inputSchema` (too large) and `fullTokens` (registry-internal). Returns
266
+ * undefined if the response carries nothing worth caching.
267
+ */
268
+ function buildCacheEntry(ref, info) {
269
+ if (!info)
270
+ return undefined;
271
+ const toolSource = info.tools ?? info.toolSummaries;
272
+ const tools = toolSource?.map((t) => {
273
+ const slim = { name: t.name };
274
+ if (t.description !== undefined)
275
+ slim.description = t.description;
276
+ return slim;
277
+ });
278
+ if (info.description === undefined && (!tools || tools.length === 0)) {
279
+ return undefined;
280
+ }
281
+ const entry = {
282
+ ref,
283
+ fetchedAt: new Date().toISOString(),
284
+ };
285
+ if (info.description !== undefined)
286
+ entry.description = info.description;
287
+ if (tools && tools.length > 0)
288
+ entry.tools = tools;
289
+ return entry;
290
+ }
291
+ async function upsertRegistryCacheEntry(name, entry) {
292
+ if (!entry)
293
+ return;
294
+ const cache = await readRegistryCache();
295
+ cache.refs[name] = entry;
296
+ await writeRegistryCache(cache);
297
+ }
298
+ /**
299
+ * Merge `authFields` into an existing cache entry without clobbering
300
+ * description/tools, or create a minimal entry if one doesn't exist
301
+ * yet. Called from `authStatus` so the slim {required, automated}
302
+ * shape is always available for `isRefAuthComplete` to answer
303
+ * locally on subsequent calls.
304
+ */
305
+ async function upsertRegistryCacheAuthFields(name, ref, authFields) {
306
+ const cache = await readRegistryCache();
307
+ const existing = cache.refs[name];
308
+ cache.refs[name] = {
309
+ ...(existing ?? { ref, fetchedAt: new Date().toISOString() }),
310
+ authFields,
311
+ // Refresh fetchedAt so freshness telemetry stays accurate.
312
+ fetchedAt: new Date().toISOString(),
313
+ };
314
+ await writeRegistryCache(cache);
315
+ }
316
+ async function removeRegistryCacheEntry(name) {
317
+ const cache = await readRegistryCache();
318
+ if (!(name in cache.refs))
319
+ return;
320
+ delete cache.refs[name];
321
+ await writeRegistryCache(cache);
322
+ }
323
+ /**
324
+ * Hydrate a `ResolvedRef` with cached registry metadata when available.
325
+ * Pure: never mutates input. Leaves `description` / `tools` undefined when
326
+ * the cache has no entry, so callers can apply their own UX fallback.
327
+ */
328
+ function hydrateFromCache(ref, cache) {
329
+ const cached = cache.refs[ref.name];
330
+ if (!cached)
331
+ return ref;
332
+ const next = { ...ref };
333
+ if (cached.description !== undefined)
334
+ next.description = cached.description;
335
+ if (cached.tools !== undefined)
336
+ next.tools = cached.tools;
337
+ return next;
338
+ }
183
339
  /**
184
340
  * Store a secret value in a ref's config, encrypted if encryptionKey is set.
185
341
  * The value is stored inline as "secret:<encrypted>" in consumer-config.json.
@@ -290,7 +446,10 @@ export function createAdk(fs, options = {}) {
290
446
  let reqId = 0;
291
447
  let sessionId;
292
448
  async function rpc(method, rpcParams) {
293
- const reqHeaders = { ...headers, ...(sessionId ? { "Mcp-Session-Id": sessionId } : {}) };
449
+ const reqHeaders = {
450
+ ...headers,
451
+ ...(sessionId ? { "Mcp-Session-Id": sessionId } : {}),
452
+ };
294
453
  const res = await globalThis.fetch(url, {
295
454
  method: "POST",
296
455
  headers: reqHeaders,
@@ -331,7 +490,7 @@ export function createAdk(fs, options = {}) {
331
490
  }
332
491
  return undefined;
333
492
  }
334
- const json = await res.json();
493
+ const json = (await res.json());
335
494
  if (json.error)
336
495
  throw new Error(`MCP RPC error: ${json.error.message}`);
337
496
  return json.result;
@@ -343,20 +502,32 @@ export function createAdk(fs, options = {}) {
343
502
  clientInfo: { name: "adk", version: "1.0.0" },
344
503
  });
345
504
  await rpc("notifications/initialized").catch(() => { });
346
- const result = await rpc("tools/call", { name: toolName, arguments: params });
505
+ const result = (await rpc("tools/call", {
506
+ name: toolName,
507
+ arguments: params,
508
+ }));
347
509
  const textContent = result?.content?.find((c) => c.type === "text");
348
510
  if (textContent?.text) {
349
511
  try {
350
- return { success: true, result: JSON.parse(textContent.text) };
512
+ return {
513
+ success: true,
514
+ result: JSON.parse(textContent.text),
515
+ };
351
516
  }
352
517
  catch {
353
- return { success: true, result: textContent.text };
518
+ return {
519
+ success: true,
520
+ result: textContent.text,
521
+ };
354
522
  }
355
523
  }
356
524
  return { success: true, result };
357
525
  }
358
526
  catch (err) {
359
- return { success: false, error: err instanceof Error ? err.message : String(err) };
527
+ return {
528
+ success: false,
529
+ error: err instanceof Error ? err.message : String(err),
530
+ };
360
531
  }
361
532
  }
362
533
  function callbackUrl() {
@@ -369,7 +540,7 @@ export function createAdk(fs, options = {}) {
369
540
  const res = await globalThis.fetch(url);
370
541
  if (!res.ok)
371
542
  return null;
372
- const data = await res.json();
543
+ const data = (await res.json());
373
544
  if (data.authorization_endpoint && data.token_endpoint) {
374
545
  return data;
375
546
  }
@@ -739,7 +910,9 @@ export function createAdk(fs, options = {}) {
739
910
  ...final,
740
911
  proxy: {
741
912
  mode: discovered.proxy.mode,
742
- ...(discovered.proxy.agent && { agent: discovered.proxy.agent }),
913
+ ...(discovered.proxy.agent && {
914
+ agent: discovered.proxy.agent,
915
+ }),
743
916
  },
744
917
  };
745
918
  }
@@ -866,7 +1039,12 @@ export function createAdk(fs, options = {}) {
866
1039
  }));
867
1040
  return results.map((r) => r.status === "fulfilled"
868
1041
  ? r.value
869
- : { name: "unknown", url: "unknown", status: "error", error: "unknown" });
1042
+ : {
1043
+ name: "unknown",
1044
+ url: "unknown",
1045
+ status: "error",
1046
+ error: "unknown",
1047
+ });
870
1048
  },
871
1049
  async auth(nameOrUrl, credential) {
872
1050
  // Encrypt the secret value up-front so the write path is uniform;
@@ -1146,7 +1324,10 @@ export function createAdk(fs, options = {}) {
1146
1324
  entry = { ...entry, scheme: "registry" };
1147
1325
  }
1148
1326
  else if (entry.url) {
1149
- entry = { ...entry, scheme: entry.url.startsWith("http") ? "https" : "mcp" };
1327
+ entry = {
1328
+ ...entry,
1329
+ scheme: entry.url.startsWith("http") ? "https" : "mcp",
1330
+ };
1150
1331
  }
1151
1332
  else {
1152
1333
  throw new AdkError({
@@ -1174,6 +1355,7 @@ export function createAdk(fs, options = {}) {
1174
1355
  details: { ref: entry.ref, scheme: entry.scheme },
1175
1356
  });
1176
1357
  }
1358
+ let cacheEntry;
1177
1359
  if (hasRegistries || entry.sourceRegistry?.url) {
1178
1360
  try {
1179
1361
  const consumer = await buildConsumerForRef(entry);
@@ -1181,9 +1363,10 @@ export function createAdk(fs, options = {}) {
1181
1363
  const info = await consumer.inspect(agentToInspect);
1182
1364
  const requiresValidation = !!entry.sourceRegistry;
1183
1365
  if (requiresValidation) {
1184
- const hasContent = info && (info.description ||
1185
- (info.tools && info.tools.length > 0) ||
1186
- (info.toolSummaries && info.toolSummaries.length > 0));
1366
+ const hasContent = info &&
1367
+ (info.description ||
1368
+ (info.tools && info.tools.length > 0) ||
1369
+ (info.toolSummaries && info.toolSummaries.length > 0));
1187
1370
  if (!hasContent) {
1188
1371
  // Inspect returned empty — fall back to browse to check if agent exists
1189
1372
  const registryUrl = entry.sourceRegistry?.url;
@@ -1205,7 +1388,11 @@ export function createAdk(fs, options = {}) {
1205
1388
  code: "REF_NOT_FOUND",
1206
1389
  message: `Agent "${entry.ref}" not found on ${registryHint}`,
1207
1390
  hint: "Check available agents with: adk registry browse",
1208
- details: { ref: entry.ref, sourceRegistry: entry.sourceRegistry, scheme: entry.scheme },
1391
+ details: {
1392
+ ref: entry.ref,
1393
+ sourceRegistry: entry.sourceRegistry,
1394
+ scheme: entry.scheme,
1395
+ },
1209
1396
  });
1210
1397
  }
1211
1398
  }
@@ -1215,10 +1402,11 @@ export function createAdk(fs, options = {}) {
1215
1402
  const agentMode = info?.mode;
1216
1403
  if (agentMode)
1217
1404
  entry.mode = agentMode;
1218
- if (info?.upstream && !entry.url && agentMode !== 'api') {
1405
+ if (info?.upstream && !entry.url && agentMode !== "api") {
1219
1406
  entry.url = info.upstream;
1220
1407
  entry.scheme = entry.scheme ?? "mcp";
1221
1408
  }
1409
+ cacheEntry = buildCacheEntry(entry.ref, info);
1222
1410
  }
1223
1411
  catch (err) {
1224
1412
  if (err instanceof AdkError)
@@ -1227,13 +1415,17 @@ export function createAdk(fs, options = {}) {
1227
1415
  code: "REGISTRY_UNREACHABLE",
1228
1416
  message: `Could not reach registry to validate "${entry.ref}"`,
1229
1417
  hint: "Check your registry connection with: adk registry test",
1230
- details: { ref: entry.ref, error: err instanceof Error ? err.message : String(err) },
1418
+ details: {
1419
+ ref: entry.ref,
1420
+ error: err instanceof Error ? err.message : String(err),
1421
+ },
1231
1422
  cause: err,
1232
1423
  });
1233
1424
  }
1234
1425
  }
1235
1426
  const refs = [...(config.refs ?? []), entry];
1236
1427
  await writeConfig({ ...config, refs });
1428
+ await upsertRegistryCacheEntry(name, cacheEntry);
1237
1429
  return { security };
1238
1430
  },
1239
1431
  async remove(name) {
@@ -1245,15 +1437,27 @@ export function createAdk(fs, options = {}) {
1245
1437
  if (refs.length === before)
1246
1438
  return false;
1247
1439
  await writeConfig({ ...config, refs });
1440
+ await removeRegistryCacheEntry(name);
1248
1441
  return true;
1249
1442
  },
1250
1443
  async list() {
1251
- const config = await readConfig();
1252
- return (config.refs ?? []).map(normalizeRef);
1444
+ const [config, cache] = await Promise.all([
1445
+ readConfig(),
1446
+ readRegistryCache(),
1447
+ ]);
1448
+ return (config.refs ?? [])
1449
+ .map(normalizeRef)
1450
+ .map((r) => hydrateFromCache(r, cache));
1253
1451
  },
1254
1452
  async get(name) {
1255
- const config = await readConfig();
1256
- return findRef(config.refs ?? [], name) ?? null;
1453
+ const [config, cache] = await Promise.all([
1454
+ readConfig(),
1455
+ readRegistryCache(),
1456
+ ]);
1457
+ const found = findRef(config.refs ?? [], name);
1458
+ if (!found)
1459
+ return null;
1460
+ return hydrateFromCache(found, cache);
1257
1461
  },
1258
1462
  async update(name, updates) {
1259
1463
  const config = await readConfig();
@@ -1299,27 +1503,35 @@ export function createAdk(fs, options = {}) {
1299
1503
  if (!entry)
1300
1504
  throw new Error(`Ref "${name}" not found`);
1301
1505
  const consumer = await buildConsumerForRef(entry);
1302
- return consumer.inspect(entry.sourceRegistry?.agentPath ?? entry.ref, entry.sourceRegistry?.url, opts);
1506
+ const result = await consumer.inspect(entry.sourceRegistry?.agentPath ?? entry.ref, entry.sourceRegistry?.url, opts);
1507
+ // Side-effect: refresh the registry cache so subsequent ref.list()
1508
+ // / ref.get() calls see the latest description and tool summaries
1509
+ // without another network round-trip. Strips inputSchema (caller's
1510
+ // `result` is unaffected — it still carries the full data).
1511
+ await upsertRegistryCacheEntry(name, buildCacheEntry(entry.ref, result));
1512
+ return result;
1303
1513
  },
1304
1514
  async call(name, tool, params) {
1305
1515
  const config = await readConfig();
1306
1516
  const entry = findRef(config.refs ?? [], name);
1307
1517
  if (!entry)
1308
1518
  throw new Error(`Ref "${name}" not found`);
1309
- let accessToken = await readRefSecret(name, "access_token")
1310
- ?? await readRefSecret(name, "api_key")
1311
- ?? await readRefSecret(name, "token");
1519
+ const accessToken = (await readRefSecret(name, "access_token")) ??
1520
+ (await readRefSecret(name, "api_key")) ??
1521
+ (await readRefSecret(name, "token"));
1312
1522
  // Resolve custom headers from config (e.g. { "X-API-Key": "secret:..." })
1313
1523
  const refConfig = (entry.config ?? {});
1314
1524
  const rawHeaders = refConfig.headers;
1315
1525
  let resolvedHeaders;
1316
- if (rawHeaders && typeof rawHeaders === 'object') {
1526
+ if (rawHeaders && typeof rawHeaders === "object") {
1317
1527
  resolvedHeaders = {};
1318
1528
  for (const [k, v] of Object.entries(rawHeaders)) {
1319
- if (typeof v === 'string' && v.startsWith(SECRET_PREFIX) && options.encryptionKey) {
1529
+ if (typeof v === "string" &&
1530
+ v.startsWith(SECRET_PREFIX) &&
1531
+ options.encryptionKey) {
1320
1532
  resolvedHeaders[k] = await decryptSecret(v.slice(SECRET_PREFIX.length), options.encryptionKey);
1321
1533
  }
1322
- else if (typeof v === 'string') {
1534
+ else if (typeof v === "string") {
1323
1535
  resolvedHeaders[k] = v;
1324
1536
  }
1325
1537
  }
@@ -1327,8 +1539,8 @@ export function createAdk(fs, options = {}) {
1327
1539
  const doCall = async (token) => {
1328
1540
  // Direct MCP only for redirect/proxy agents with an MCP upstream.
1329
1541
  // API-mode agents must go through the registry (it does REST translation).
1330
- const agentMode = entry.mode ?? 'redirect';
1331
- if (token && entry.url && agentMode !== 'api') {
1542
+ const agentMode = entry.mode ?? "redirect";
1543
+ if (token && entry.url && agentMode !== "api") {
1332
1544
  return callMcpDirect(entry.url, tool, params ?? {}, token, resolvedHeaders);
1333
1545
  }
1334
1546
  const consumer = await buildConsumerForRef(entry);
@@ -1444,10 +1656,15 @@ export function createAdk(fs, options = {}) {
1444
1656
  }
1445
1657
  else if (security.type === "apiKey") {
1446
1658
  const apiKeySec = security;
1447
- const toStorageKey = (headerName) => headerName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1659
+ const toStorageKey = (headerName) => headerName
1660
+ .toLowerCase()
1661
+ .replace(/[^a-z0-9]+/g, "_")
1662
+ .replace(/^_|_$/g, "");
1448
1663
  // config.headers: { "Header-Name": "value" } — check by header name (case-insensitive)
1449
1664
  const configHeaders = entry?.config?.headers;
1450
- const configHeaderKeys = configHeaders ? Object.keys(configHeaders) : [];
1665
+ const configHeaderKeys = configHeaders
1666
+ ? Object.keys(configHeaders)
1667
+ : [];
1451
1668
  const hasConfigHeader = (name) => configHeaderKeys.some((k) => k.toLowerCase() === name.toLowerCase());
1452
1669
  // Collect all declared header names from the security scheme
1453
1670
  const declaredHeaders = apiKeySec.headers
@@ -1485,6 +1702,20 @@ export function createAdk(fs, options = {}) {
1485
1702
  };
1486
1703
  }
1487
1704
  const complete = Object.values(fields).every((f) => !f.required || f.present || f.resolvable);
1705
+ // Persist the slim {required, automated} per-field shape into the
1706
+ // registry cache so `isRefAuthComplete` can answer subsequent
1707
+ // host-side "is this ref ready?" checks without re-fetching the
1708
+ // security scheme. We deliberately omit `present`/`resolvable`
1709
+ // because those are computed against the current entry.config and
1710
+ // host environment — caching them would go stale immediately.
1711
+ const authFields = {};
1712
+ for (const [field, info] of Object.entries(fields)) {
1713
+ authFields[field] = {
1714
+ required: info.required,
1715
+ automated: info.automated,
1716
+ };
1717
+ }
1718
+ await upsertRegistryCacheAuthFields(name, entry.ref, authFields);
1488
1719
  return { name, security, complete, fields };
1489
1720
  },
1490
1721
  async auth(name, opts) {
@@ -1496,7 +1727,9 @@ export function createAdk(fs, options = {}) {
1496
1727
  // agent. The registry owns the client_id/secret and returns an authorize
1497
1728
  // URL pointing at the registry's OAuth callback domain, so the user
1498
1729
  // completes the flow against the registry instead of localhost.
1499
- const proxy = await resolveProxyForRef(entry, { preferLocal: opts?.preferLocal });
1730
+ const proxy = await resolveProxyForRef(entry, {
1731
+ preferLocal: opts?.preferLocal,
1732
+ });
1500
1733
  if (proxy) {
1501
1734
  const params = { name };
1502
1735
  if (opts?.apiKey !== undefined)
@@ -1517,12 +1750,18 @@ export function createAdk(fs, options = {}) {
1517
1750
  }
1518
1751
  if (security.type === "apiKey") {
1519
1752
  const apiKeySec = security;
1520
- const toStorageKey = (headerName) => headerName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1753
+ const toStorageKey = (headerName) => headerName
1754
+ .toLowerCase()
1755
+ .replace(/[^a-z0-9]+/g, "_")
1756
+ .replace(/^_|_$/g, "");
1521
1757
  // Check existing config.headers
1522
1758
  const existingHeaders = (entry.config ?? {}).headers;
1523
1759
  // Collect declared headers: from security.headers or security.name
1524
1760
  const declaredHeaders = apiKeySec.headers
1525
- ? Object.entries(apiKeySec.headers).map(([h, meta]) => ({ headerName: h, description: meta.description }))
1761
+ ? Object.entries(apiKeySec.headers).map(([h, meta]) => ({
1762
+ headerName: h,
1763
+ description: meta.description,
1764
+ }))
1526
1765
  : apiKeySec.name
1527
1766
  ? [{ headerName: apiKeySec.name }]
1528
1767
  : [];
@@ -1532,12 +1771,13 @@ export function createAdk(fs, options = {}) {
1532
1771
  for (const { headerName, description } of declaredHeaders) {
1533
1772
  const storageKey = toStorageKey(headerName);
1534
1773
  // Check: credentials param → existing config.headers → legacy config key → resolve callback
1535
- const value = opts?.credentials?.[storageKey]
1536
- ?? opts?.credentials?.[headerName]
1537
- ?? (existingHeaders && Object.entries(existingHeaders).find(([k]) => k.toLowerCase() === headerName.toLowerCase())?.[1])
1538
- ?? opts?.apiKey
1539
- ?? await readRefSecret(name, storageKey)
1540
- ?? await tryResolve(storageKey);
1774
+ const value = opts?.credentials?.[storageKey] ??
1775
+ opts?.credentials?.[headerName] ??
1776
+ (existingHeaders &&
1777
+ Object.entries(existingHeaders).find(([k]) => k.toLowerCase() === headerName.toLowerCase())?.[1]) ??
1778
+ opts?.apiKey ??
1779
+ (await readRefSecret(name, storageKey)) ??
1780
+ (await tryResolve(storageKey));
1541
1781
  if (value) {
1542
1782
  resolvedHeaders[headerName] = value;
1543
1783
  }
@@ -1557,22 +1797,28 @@ export function createAdk(fs, options = {}) {
1557
1797
  const encKey = options.encryptionKey;
1558
1798
  const headersToStore = {};
1559
1799
  for (const [h, v] of Object.entries(resolvedHeaders)) {
1560
- headersToStore[h] = encKey ? `${SECRET_PREFIX}${await encryptSecret(v, encKey)}` : v;
1800
+ headersToStore[h] = encKey
1801
+ ? `${SECRET_PREFIX}${await encryptSecret(v, encKey)}`
1802
+ : v;
1561
1803
  }
1562
1804
  await ref.update(name, { config: { headers: headersToStore } });
1563
1805
  return { type: "apiKey", complete: true };
1564
1806
  }
1565
1807
  // Fallback: no headers declared → generic api_key
1566
- const key = opts?.credentials?.["api_key"] ?? opts?.apiKey ?? await tryResolve("api_key");
1808
+ const key = opts?.credentials?.["api_key"] ??
1809
+ opts?.apiKey ??
1810
+ (await tryResolve("api_key"));
1567
1811
  if (!key) {
1568
1812
  return {
1569
1813
  type: "apiKey",
1570
1814
  complete: false,
1571
- fields: [{
1815
+ fields: [
1816
+ {
1572
1817
  name: "api_key",
1573
1818
  label: "API Key",
1574
1819
  secret: true,
1575
- }],
1820
+ },
1821
+ ],
1576
1822
  };
1577
1823
  }
1578
1824
  await storeRefSecret(name, "api_key", key);
@@ -1582,14 +1828,22 @@ export function createAdk(fs, options = {}) {
1582
1828
  const httpSec = security;
1583
1829
  const isBasic = httpSec.scheme === "basic";
1584
1830
  if (isBasic) {
1585
- const username = opts?.credentials?.["username"] ?? await tryResolve("username");
1586
- const password = opts?.credentials?.["password"] ?? await tryResolve("password");
1831
+ const username = opts?.credentials?.["username"] ?? (await tryResolve("username"));
1832
+ const password = opts?.credentials?.["password"] ?? (await tryResolve("password"));
1587
1833
  if (!username || !password) {
1588
1834
  const missingFields = [];
1589
1835
  if (!username)
1590
- missingFields.push({ name: "username", label: "Username", secret: false });
1836
+ missingFields.push({
1837
+ name: "username",
1838
+ label: "Username",
1839
+ secret: false,
1840
+ });
1591
1841
  if (!password)
1592
- missingFields.push({ name: "password", label: "Password", secret: true });
1842
+ missingFields.push({
1843
+ name: "password",
1844
+ label: "Password",
1845
+ secret: true,
1846
+ });
1593
1847
  return { type: "http", complete: false, fields: missingFields };
1594
1848
  }
1595
1849
  // Store as base64 encoded basic auth token
@@ -1598,7 +1852,9 @@ export function createAdk(fs, options = {}) {
1598
1852
  return { type: "http", complete: true };
1599
1853
  }
1600
1854
  // Bearer token
1601
- const token = opts?.credentials?.["token"] ?? opts?.apiKey ?? await tryResolve("token");
1855
+ const token = opts?.credentials?.["token"] ??
1856
+ opts?.apiKey ??
1857
+ (await tryResolve("token"));
1602
1858
  if (!token) {
1603
1859
  return {
1604
1860
  type: "http",
@@ -1655,8 +1911,9 @@ export function createAdk(fs, options = {}) {
1655
1911
  const supportedAuthMethods = metadata.token_endpoint_auth_methods_supported ?? ["none"];
1656
1912
  const preferredMethod = supportedAuthMethods.includes("none")
1657
1913
  ? "none"
1658
- : supportedAuthMethods[0] ?? "client_secret_post";
1659
- const securityClientName = security.clientName;
1914
+ : (supportedAuthMethods[0] ?? "client_secret_post");
1915
+ const securityClientName = security
1916
+ .clientName;
1660
1917
  const reg = await dynamicClientRegistration(metadata.registration_endpoint, {
1661
1918
  clientName: securityClientName ?? options.oauthClientName ?? "adk",
1662
1919
  redirectUris: [redirectUri],
@@ -1674,10 +1931,18 @@ export function createAdk(fs, options = {}) {
1674
1931
  // Return fields telling the caller what OAuth credentials to provide
1675
1932
  const missingFields = [];
1676
1933
  if (!clientId) {
1677
- missingFields.push({ name: "client_id", label: "Client ID", secret: false });
1934
+ missingFields.push({
1935
+ name: "client_id",
1936
+ label: "Client ID",
1937
+ secret: false,
1938
+ });
1678
1939
  }
1679
1940
  // Always ask for client_secret alongside client_id — most providers need it
1680
- missingFields.push({ name: "client_secret", label: "Client Secret", secret: true });
1941
+ missingFields.push({
1942
+ name: "client_secret",
1943
+ label: "Client Secret",
1944
+ secret: true,
1945
+ });
1681
1946
  return { type: "oauth2", complete: false, fields: missingFields };
1682
1947
  }
1683
1948
  // State ties the callback back to this ref. Encode as base64 JSON
@@ -1699,7 +1964,9 @@ export function createAdk(fs, options = {}) {
1699
1964
  const scopes = agentScopes.length > 0
1700
1965
  ? [
1701
1966
  ...agentScopes,
1702
- ...(metadata.scopes_supported?.includes('openid') ? ['openid'] : []),
1967
+ ...(metadata.scopes_supported?.includes("openid")
1968
+ ? ["openid"]
1969
+ : []),
1703
1970
  ]
1704
1971
  : metadata.scopes_supported;
1705
1972
  // Read provider-specific authorization params from the agent's security section
@@ -1748,7 +2015,9 @@ export function createAdk(fs, options = {}) {
1748
2015
  // owns the credential store, so the user needs to submit via
1749
2016
  // whatever UI the registry exposes. Supporting this through the
1750
2017
  // proxy would need a remote form endpoint — out of scope here.
1751
- if (result.fields && result.fields.length > 0 && result.type !== "oauth2") {
2018
+ if (result.fields &&
2019
+ result.fields.length > 0 &&
2020
+ result.type !== "oauth2") {
1752
2021
  if (proxy) {
1753
2022
  throw new Error(`Ref "${name}" is sourced from a proxied registry; submit credentials through ${proxy.agent} instead of a local form.`);
1754
2023
  }
@@ -1908,7 +2177,7 @@ export function createAdk(fs, options = {}) {
1908
2177
  });
1909
2178
  if (!res.ok)
1910
2179
  return null;
1911
- const data = await res.json();
2180
+ const data = (await res.json());
1912
2181
  const newAccessToken = data.access_token;
1913
2182
  if (!newAccessToken)
1914
2183
  return null;
@@ -1943,7 +2212,9 @@ export function createAdk(fs, options = {}) {
1943
2212
  try {
1944
2213
  stateContext = JSON.parse(atob(params.state));
1945
2214
  }
1946
- catch { /* state wasn't base64 JSON — legacy format */ }
2215
+ catch {
2216
+ /* state wasn't base64 JSON — legacy format */
2217
+ }
1947
2218
  return { refName: pending.refName, complete: true, stateContext };
1948
2219
  }
1949
2220
  return { registry, ref, readConfig, writeConfig, handleCallback };