@slashfi/agents-sdk 0.77.2 → 0.78.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.
@@ -52,12 +52,13 @@ var __importStar = (this && this.__importStar) || (function () {
52
52
  })();
53
53
  Object.defineProperty(exports, "__esModule", { value: true });
54
54
  exports.createAdk = createAdk;
55
- const define_config_js_1 = require("./define-config.js");
56
- const registry_consumer_js_1 = require("./registry-consumer.js");
57
- const crypto_js_1 = require("./crypto.js");
58
55
  const adk_error_js_1 = require("./adk-error.js");
56
+ const crypto_js_1 = require("./crypto.js");
57
+ const define_config_js_1 = require("./define-config.js");
59
58
  const mcp_client_js_1 = require("./mcp-client.js");
59
+ const registry_consumer_js_1 = require("./registry-consumer.js");
60
60
  const CONFIG_PATH = "consumer-config.json";
61
+ const REGISTRY_CACHE_PATH = "registry-cache.json";
61
62
  const SECRET_PREFIX = "secret:";
62
63
  // ============================================
63
64
  // Internal helpers
@@ -110,7 +111,9 @@ async function decryptConfigSecrets(obj, encryptionKey) {
110
111
  if (typeof value === "string" && value.startsWith(SECRET_PREFIX)) {
111
112
  result[key] = await (0, crypto_js_1.decryptSecret)(value.slice(SECRET_PREFIX.length), encryptionKey);
112
113
  }
113
- else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
114
+ else if (value !== null &&
115
+ typeof value === "object" &&
116
+ !Array.isArray(value)) {
114
117
  result[key] = await decryptConfigSecrets(value, encryptionKey);
115
118
  }
116
119
  else {
@@ -128,7 +131,7 @@ async function decryptConfigSecrets(obj, encryptionKey) {
128
131
  * Fallback: _httpStatus from tool result body
129
132
  */
130
133
  function isUnauthorized(result) {
131
- if (!result || typeof result !== 'object')
134
+ if (!result || typeof result !== "object")
132
135
  return false;
133
136
  const r = result;
134
137
  // Primary: HTTP status forwarded by the registry and set by callRegistry
@@ -143,17 +146,21 @@ function isUnauthorized(result) {
143
146
  // ============================================
144
147
  // Local auth form HTML
145
148
  // ============================================
146
- const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
149
+ const esc = (s) => s
150
+ .replace(/&/g, "&amp;")
151
+ .replace(/</g, "&lt;")
152
+ .replace(/>/g, "&gt;")
153
+ .replace(/"/g, "&quot;");
147
154
  function renderCredentialForm(name, fields, error) {
148
- const fieldHtml = fields.map((f) => `
155
+ const fieldHtml = fields
156
+ .map((f) => `
149
157
  <div class="field">
150
158
  <label for="${esc(f.name)}">${esc(f.label)}</label>
151
159
  ${f.description ? `<p class="desc">${esc(f.description)}</p>` : ""}
152
160
  <input id="${esc(f.name)}" name="${esc(f.name)}" type="${f.secret ? "password" : "text"}" required autocomplete="off" spellcheck="false" />
153
- </div>`).join("");
154
- const errorHtml = error
155
- ? `<div class="error">${esc(error)}</div>`
156
- : "";
161
+ </div>`)
162
+ .join("");
163
+ const errorHtml = error ? `<div class="error">${esc(error)}</div>` : "";
157
164
  return `<!DOCTYPE html>
158
165
  <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
159
166
  <title>Authenticate \u2014 ${esc(name)}</title>
@@ -216,6 +223,95 @@ function createAdk(fs, options = {}) {
216
223
  async function writeConfig(config) {
217
224
  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
218
225
  }
226
+ // -------------------------------------------------------------------------
227
+ // Registry cache helpers
228
+ //
229
+ // The cache is purely an internal optimization for the adk's read paths
230
+ // (`ref.list()`, `ref.get()`). Writes happen as side-effects of methods
231
+ // that already call the registry (`ref.add()`, `ref.inspect()`); the
232
+ // public surface never grows new methods. Cache failures (missing file,
233
+ // malformed JSON, fs errors during write) are swallowed so the registry
234
+ // cache can never break a registry operation.
235
+ // -------------------------------------------------------------------------
236
+ async function readRegistryCache() {
237
+ try {
238
+ const content = await fs.readFile(REGISTRY_CACHE_PATH);
239
+ if (!content)
240
+ return { refs: {} };
241
+ const parsed = JSON.parse(content);
242
+ return { refs: parsed.refs ?? {} };
243
+ }
244
+ catch {
245
+ return { refs: {} };
246
+ }
247
+ }
248
+ async function writeRegistryCache(cache) {
249
+ try {
250
+ await fs.writeFile(REGISTRY_CACHE_PATH, JSON.stringify(cache, null, 2));
251
+ }
252
+ catch {
253
+ // Best-effort. A failed cache write should never break the operation
254
+ // that triggered it.
255
+ }
256
+ }
257
+ /**
258
+ * Project an inspect/list response into the slim shape we cache. Drops
259
+ * `inputSchema` (too large) and `fullTokens` (registry-internal). Returns
260
+ * undefined if the response carries nothing worth caching.
261
+ */
262
+ function buildCacheEntry(ref, info) {
263
+ if (!info)
264
+ return undefined;
265
+ const toolSource = info.tools ?? info.toolSummaries;
266
+ const tools = toolSource?.map((t) => {
267
+ const slim = { name: t.name };
268
+ if (t.description !== undefined)
269
+ slim.description = t.description;
270
+ return slim;
271
+ });
272
+ if (info.description === undefined && (!tools || tools.length === 0)) {
273
+ return undefined;
274
+ }
275
+ const entry = {
276
+ ref,
277
+ fetchedAt: new Date().toISOString(),
278
+ };
279
+ if (info.description !== undefined)
280
+ entry.description = info.description;
281
+ if (tools && tools.length > 0)
282
+ entry.tools = tools;
283
+ return entry;
284
+ }
285
+ async function upsertRegistryCacheEntry(name, entry) {
286
+ if (!entry)
287
+ return;
288
+ const cache = await readRegistryCache();
289
+ cache.refs[name] = entry;
290
+ await writeRegistryCache(cache);
291
+ }
292
+ async function removeRegistryCacheEntry(name) {
293
+ const cache = await readRegistryCache();
294
+ if (!(name in cache.refs))
295
+ return;
296
+ delete cache.refs[name];
297
+ await writeRegistryCache(cache);
298
+ }
299
+ /**
300
+ * Hydrate a `ResolvedRef` with cached registry metadata when available.
301
+ * Pure: never mutates input. Leaves `description` / `tools` undefined when
302
+ * the cache has no entry, so callers can apply their own UX fallback.
303
+ */
304
+ function hydrateFromCache(ref, cache) {
305
+ const cached = cache.refs[ref.name];
306
+ if (!cached)
307
+ return ref;
308
+ const next = { ...ref };
309
+ if (cached.description !== undefined)
310
+ next.description = cached.description;
311
+ if (cached.tools !== undefined)
312
+ next.tools = cached.tools;
313
+ return next;
314
+ }
219
315
  /**
220
316
  * Store a secret value in a ref's config, encrypted if encryptionKey is set.
221
317
  * The value is stored inline as "secret:<encrypted>" in consumer-config.json.
@@ -244,6 +340,47 @@ function createAdk(fs, options = {}) {
244
340
  }
245
341
  return value;
246
342
  }
343
+ /**
344
+ * Build a `tryResolve(field, oauthMetadata?)` function bound to a
345
+ * specific ref + entry + security context. Wraps the host-injected
346
+ * `resolveCredentials` callback (e.g. atlas's env/static/tenant chain
347
+ * for first-party agents). Errors propagate to the caller.
348
+ */
349
+ function makeTryResolve(ctx) {
350
+ return async (field, oauthMetadata) => {
351
+ const resolve = options.resolveCredentials;
352
+ if (!resolve)
353
+ return null;
354
+ return resolve({
355
+ ref: ctx.name,
356
+ field,
357
+ entry: ctx.entry,
358
+ security: ctx.security,
359
+ oauthMetadata,
360
+ });
361
+ };
362
+ }
363
+ /**
364
+ * Resolve OAuth client credentials (client_id + client_secret) for a
365
+ * ref. Walks: `resolveCredentials` callback → per-ref VCS storage.
366
+ * Used by both `auth` (initial OAuth flow) and `refreshToken` (token
367
+ * refresh) — must be a single function so the two paths can never
368
+ * disagree about where credentials live.
369
+ *
370
+ * Returns null when no client_id is available anywhere; caller decides
371
+ * whether to attempt dynamic registration (`auth`) or bail (`refresh`).
372
+ */
373
+ async function resolveOAuthClient(ctx) {
374
+ const tryResolve = makeTryResolve(ctx);
375
+ const clientId = (await tryResolve("client_id", ctx.metadata)) ??
376
+ (await readRefSecret(ctx.name, "client_id"));
377
+ if (!clientId)
378
+ return null;
379
+ const clientSecret = (await tryResolve("client_secret", ctx.metadata)) ??
380
+ (await readRefSecret(ctx.name, "client_secret")) ??
381
+ undefined;
382
+ return { clientId, ...(clientSecret && { clientSecret }) };
383
+ }
247
384
  const PENDING_OAUTH_PATH = "pending-oauth.json";
248
385
  async function readPendingOAuth() {
249
386
  const content = await fs.readFile(PENDING_OAUTH_PATH);
@@ -285,7 +422,10 @@ function createAdk(fs, options = {}) {
285
422
  let reqId = 0;
286
423
  let sessionId;
287
424
  async function rpc(method, rpcParams) {
288
- const reqHeaders = { ...headers, ...(sessionId ? { "Mcp-Session-Id": sessionId } : {}) };
425
+ const reqHeaders = {
426
+ ...headers,
427
+ ...(sessionId ? { "Mcp-Session-Id": sessionId } : {}),
428
+ };
289
429
  const res = await globalThis.fetch(url, {
290
430
  method: "POST",
291
431
  headers: reqHeaders,
@@ -326,7 +466,7 @@ function createAdk(fs, options = {}) {
326
466
  }
327
467
  return undefined;
328
468
  }
329
- const json = await res.json();
469
+ const json = (await res.json());
330
470
  if (json.error)
331
471
  throw new Error(`MCP RPC error: ${json.error.message}`);
332
472
  return json.result;
@@ -338,20 +478,32 @@ function createAdk(fs, options = {}) {
338
478
  clientInfo: { name: "adk", version: "1.0.0" },
339
479
  });
340
480
  await rpc("notifications/initialized").catch(() => { });
341
- const result = await rpc("tools/call", { name: toolName, arguments: params });
481
+ const result = (await rpc("tools/call", {
482
+ name: toolName,
483
+ arguments: params,
484
+ }));
342
485
  const textContent = result?.content?.find((c) => c.type === "text");
343
486
  if (textContent?.text) {
344
487
  try {
345
- return { success: true, result: JSON.parse(textContent.text) };
488
+ return {
489
+ success: true,
490
+ result: JSON.parse(textContent.text),
491
+ };
346
492
  }
347
493
  catch {
348
- return { success: true, result: textContent.text };
494
+ return {
495
+ success: true,
496
+ result: textContent.text,
497
+ };
349
498
  }
350
499
  }
351
500
  return { success: true, result };
352
501
  }
353
502
  catch (err) {
354
- return { success: false, error: err instanceof Error ? err.message : String(err) };
503
+ return {
504
+ success: false,
505
+ error: err instanceof Error ? err.message : String(err),
506
+ };
355
507
  }
356
508
  }
357
509
  function callbackUrl() {
@@ -364,7 +516,7 @@ function createAdk(fs, options = {}) {
364
516
  const res = await globalThis.fetch(url);
365
517
  if (!res.ok)
366
518
  return null;
367
- const data = await res.json();
519
+ const data = (await res.json());
368
520
  if (data.authorization_endpoint && data.token_endpoint) {
369
521
  return data;
370
522
  }
@@ -734,7 +886,9 @@ function createAdk(fs, options = {}) {
734
886
  ...final,
735
887
  proxy: {
736
888
  mode: discovered.proxy.mode,
737
- ...(discovered.proxy.agent && { agent: discovered.proxy.agent }),
889
+ ...(discovered.proxy.agent && {
890
+ agent: discovered.proxy.agent,
891
+ }),
738
892
  },
739
893
  };
740
894
  }
@@ -861,7 +1015,12 @@ function createAdk(fs, options = {}) {
861
1015
  }));
862
1016
  return results.map((r) => r.status === "fulfilled"
863
1017
  ? r.value
864
- : { name: "unknown", url: "unknown", status: "error", error: "unknown" });
1018
+ : {
1019
+ name: "unknown",
1020
+ url: "unknown",
1021
+ status: "error",
1022
+ error: "unknown",
1023
+ });
865
1024
  },
866
1025
  async auth(nameOrUrl, credential) {
867
1026
  // Encrypt the secret value up-front so the write path is uniform;
@@ -1141,7 +1300,10 @@ function createAdk(fs, options = {}) {
1141
1300
  entry = { ...entry, scheme: "registry" };
1142
1301
  }
1143
1302
  else if (entry.url) {
1144
- entry = { ...entry, scheme: entry.url.startsWith("http") ? "https" : "mcp" };
1303
+ entry = {
1304
+ ...entry,
1305
+ scheme: entry.url.startsWith("http") ? "https" : "mcp",
1306
+ };
1145
1307
  }
1146
1308
  else {
1147
1309
  throw new adk_error_js_1.AdkError({
@@ -1169,6 +1331,7 @@ function createAdk(fs, options = {}) {
1169
1331
  details: { ref: entry.ref, scheme: entry.scheme },
1170
1332
  });
1171
1333
  }
1334
+ let cacheEntry;
1172
1335
  if (hasRegistries || entry.sourceRegistry?.url) {
1173
1336
  try {
1174
1337
  const consumer = await buildConsumerForRef(entry);
@@ -1176,9 +1339,10 @@ function createAdk(fs, options = {}) {
1176
1339
  const info = await consumer.inspect(agentToInspect);
1177
1340
  const requiresValidation = !!entry.sourceRegistry;
1178
1341
  if (requiresValidation) {
1179
- const hasContent = info && (info.description ||
1180
- (info.tools && info.tools.length > 0) ||
1181
- (info.toolSummaries && info.toolSummaries.length > 0));
1342
+ const hasContent = info &&
1343
+ (info.description ||
1344
+ (info.tools && info.tools.length > 0) ||
1345
+ (info.toolSummaries && info.toolSummaries.length > 0));
1182
1346
  if (!hasContent) {
1183
1347
  // Inspect returned empty — fall back to browse to check if agent exists
1184
1348
  const registryUrl = entry.sourceRegistry?.url;
@@ -1200,7 +1364,11 @@ function createAdk(fs, options = {}) {
1200
1364
  code: "REF_NOT_FOUND",
1201
1365
  message: `Agent "${entry.ref}" not found on ${registryHint}`,
1202
1366
  hint: "Check available agents with: adk registry browse",
1203
- details: { ref: entry.ref, sourceRegistry: entry.sourceRegistry, scheme: entry.scheme },
1367
+ details: {
1368
+ ref: entry.ref,
1369
+ sourceRegistry: entry.sourceRegistry,
1370
+ scheme: entry.scheme,
1371
+ },
1204
1372
  });
1205
1373
  }
1206
1374
  }
@@ -1210,10 +1378,11 @@ function createAdk(fs, options = {}) {
1210
1378
  const agentMode = info?.mode;
1211
1379
  if (agentMode)
1212
1380
  entry.mode = agentMode;
1213
- if (info?.upstream && !entry.url && agentMode !== 'api') {
1381
+ if (info?.upstream && !entry.url && agentMode !== "api") {
1214
1382
  entry.url = info.upstream;
1215
1383
  entry.scheme = entry.scheme ?? "mcp";
1216
1384
  }
1385
+ cacheEntry = buildCacheEntry(entry.ref, info);
1217
1386
  }
1218
1387
  catch (err) {
1219
1388
  if (err instanceof adk_error_js_1.AdkError)
@@ -1222,13 +1391,17 @@ function createAdk(fs, options = {}) {
1222
1391
  code: "REGISTRY_UNREACHABLE",
1223
1392
  message: `Could not reach registry to validate "${entry.ref}"`,
1224
1393
  hint: "Check your registry connection with: adk registry test",
1225
- details: { ref: entry.ref, error: err instanceof Error ? err.message : String(err) },
1394
+ details: {
1395
+ ref: entry.ref,
1396
+ error: err instanceof Error ? err.message : String(err),
1397
+ },
1226
1398
  cause: err,
1227
1399
  });
1228
1400
  }
1229
1401
  }
1230
1402
  const refs = [...(config.refs ?? []), entry];
1231
1403
  await writeConfig({ ...config, refs });
1404
+ await upsertRegistryCacheEntry(name, cacheEntry);
1232
1405
  return { security };
1233
1406
  },
1234
1407
  async remove(name) {
@@ -1240,15 +1413,27 @@ function createAdk(fs, options = {}) {
1240
1413
  if (refs.length === before)
1241
1414
  return false;
1242
1415
  await writeConfig({ ...config, refs });
1416
+ await removeRegistryCacheEntry(name);
1243
1417
  return true;
1244
1418
  },
1245
1419
  async list() {
1246
- const config = await readConfig();
1247
- return (config.refs ?? []).map(define_config_js_1.normalizeRef);
1420
+ const [config, cache] = await Promise.all([
1421
+ readConfig(),
1422
+ readRegistryCache(),
1423
+ ]);
1424
+ return (config.refs ?? [])
1425
+ .map(define_config_js_1.normalizeRef)
1426
+ .map((r) => hydrateFromCache(r, cache));
1248
1427
  },
1249
1428
  async get(name) {
1250
- const config = await readConfig();
1251
- return findRef(config.refs ?? [], name) ?? null;
1429
+ const [config, cache] = await Promise.all([
1430
+ readConfig(),
1431
+ readRegistryCache(),
1432
+ ]);
1433
+ const found = findRef(config.refs ?? [], name);
1434
+ if (!found)
1435
+ return null;
1436
+ return hydrateFromCache(found, cache);
1252
1437
  },
1253
1438
  async update(name, updates) {
1254
1439
  const config = await readConfig();
@@ -1294,27 +1479,35 @@ function createAdk(fs, options = {}) {
1294
1479
  if (!entry)
1295
1480
  throw new Error(`Ref "${name}" not found`);
1296
1481
  const consumer = await buildConsumerForRef(entry);
1297
- return consumer.inspect(entry.sourceRegistry?.agentPath ?? entry.ref, entry.sourceRegistry?.url, opts);
1482
+ const result = await consumer.inspect(entry.sourceRegistry?.agentPath ?? entry.ref, entry.sourceRegistry?.url, opts);
1483
+ // Side-effect: refresh the registry cache so subsequent ref.list()
1484
+ // / ref.get() calls see the latest description and tool summaries
1485
+ // without another network round-trip. Strips inputSchema (caller's
1486
+ // `result` is unaffected — it still carries the full data).
1487
+ await upsertRegistryCacheEntry(name, buildCacheEntry(entry.ref, result));
1488
+ return result;
1298
1489
  },
1299
1490
  async call(name, tool, params) {
1300
1491
  const config = await readConfig();
1301
1492
  const entry = findRef(config.refs ?? [], name);
1302
1493
  if (!entry)
1303
1494
  throw new Error(`Ref "${name}" not found`);
1304
- let accessToken = await readRefSecret(name, "access_token")
1305
- ?? await readRefSecret(name, "api_key")
1306
- ?? await readRefSecret(name, "token");
1495
+ const accessToken = (await readRefSecret(name, "access_token")) ??
1496
+ (await readRefSecret(name, "api_key")) ??
1497
+ (await readRefSecret(name, "token"));
1307
1498
  // Resolve custom headers from config (e.g. { "X-API-Key": "secret:..." })
1308
1499
  const refConfig = (entry.config ?? {});
1309
1500
  const rawHeaders = refConfig.headers;
1310
1501
  let resolvedHeaders;
1311
- if (rawHeaders && typeof rawHeaders === 'object') {
1502
+ if (rawHeaders && typeof rawHeaders === "object") {
1312
1503
  resolvedHeaders = {};
1313
1504
  for (const [k, v] of Object.entries(rawHeaders)) {
1314
- if (typeof v === 'string' && v.startsWith(SECRET_PREFIX) && options.encryptionKey) {
1505
+ if (typeof v === "string" &&
1506
+ v.startsWith(SECRET_PREFIX) &&
1507
+ options.encryptionKey) {
1315
1508
  resolvedHeaders[k] = await (0, crypto_js_1.decryptSecret)(v.slice(SECRET_PREFIX.length), options.encryptionKey);
1316
1509
  }
1317
- else if (typeof v === 'string') {
1510
+ else if (typeof v === "string") {
1318
1511
  resolvedHeaders[k] = v;
1319
1512
  }
1320
1513
  }
@@ -1322,8 +1515,8 @@ function createAdk(fs, options = {}) {
1322
1515
  const doCall = async (token) => {
1323
1516
  // Direct MCP only for redirect/proxy agents with an MCP upstream.
1324
1517
  // API-mode agents must go through the registry (it does REST translation).
1325
- const agentMode = entry.mode ?? 'redirect';
1326
- if (token && entry.url && agentMode !== 'api') {
1518
+ const agentMode = entry.mode ?? "redirect";
1519
+ if (token && entry.url && agentMode !== "api") {
1327
1520
  return callMcpDirect(entry.url, tool, params ?? {}, token, resolvedHeaders);
1328
1521
  }
1329
1522
  const consumer = await buildConsumerForRef(entry);
@@ -1399,12 +1592,9 @@ function createAdk(fs, options = {}) {
1399
1592
  return { name, security, complete: true, fields: {} };
1400
1593
  }
1401
1594
  const configKeys = Object.keys(entry.config ?? {});
1402
- const resolve = options.resolveCredentials;
1595
+ const tryResolveField = makeTryResolve({ name, entry, security });
1403
1596
  async function canResolve(field, oauthMetadata) {
1404
- if (!resolve || !entry)
1405
- return false;
1406
- const val = await resolve({ ref: name, field, entry, security, oauthMetadata });
1407
- return val !== null;
1597
+ return (await tryResolveField(field, oauthMetadata)) !== null;
1408
1598
  }
1409
1599
  const fields = {};
1410
1600
  if (security.type === "oauth2") {
@@ -1442,10 +1632,15 @@ function createAdk(fs, options = {}) {
1442
1632
  }
1443
1633
  else if (security.type === "apiKey") {
1444
1634
  const apiKeySec = security;
1445
- const toStorageKey = (headerName) => headerName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1635
+ const toStorageKey = (headerName) => headerName
1636
+ .toLowerCase()
1637
+ .replace(/[^a-z0-9]+/g, "_")
1638
+ .replace(/^_|_$/g, "");
1446
1639
  // config.headers: { "Header-Name": "value" } — check by header name (case-insensitive)
1447
1640
  const configHeaders = entry?.config?.headers;
1448
- const configHeaderKeys = configHeaders ? Object.keys(configHeaders) : [];
1641
+ const configHeaderKeys = configHeaders
1642
+ ? Object.keys(configHeaders)
1643
+ : [];
1449
1644
  const hasConfigHeader = (name) => configHeaderKeys.some((k) => k.toLowerCase() === name.toLowerCase());
1450
1645
  // Collect all declared header names from the security scheme
1451
1646
  const declaredHeaders = apiKeySec.headers
@@ -1494,7 +1689,9 @@ function createAdk(fs, options = {}) {
1494
1689
  // agent. The registry owns the client_id/secret and returns an authorize
1495
1690
  // URL pointing at the registry's OAuth callback domain, so the user
1496
1691
  // completes the flow against the registry instead of localhost.
1497
- const proxy = await resolveProxyForRef(entry, { preferLocal: opts?.preferLocal });
1692
+ const proxy = await resolveProxyForRef(entry, {
1693
+ preferLocal: opts?.preferLocal,
1694
+ });
1498
1695
  if (proxy) {
1499
1696
  const params = { name };
1500
1697
  if (opts?.apiKey !== undefined)
@@ -1509,23 +1706,24 @@ function createAdk(fs, options = {}) {
1509
1706
  }
1510
1707
  const status = await ref.authStatus(name);
1511
1708
  const security = status.security;
1512
- const resolve = options.resolveCredentials;
1513
- async function tryResolve(field, oauthMetadata) {
1514
- if (!resolve)
1515
- return null;
1516
- return resolve({ ref: name, field, entry: entry, security, oauthMetadata });
1517
- }
1709
+ const tryResolve = makeTryResolve({ name, entry, security });
1518
1710
  if (!security || security.type === "none") {
1519
1711
  return { type: "none", complete: true };
1520
1712
  }
1521
1713
  if (security.type === "apiKey") {
1522
1714
  const apiKeySec = security;
1523
- const toStorageKey = (headerName) => headerName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1715
+ const toStorageKey = (headerName) => headerName
1716
+ .toLowerCase()
1717
+ .replace(/[^a-z0-9]+/g, "_")
1718
+ .replace(/^_|_$/g, "");
1524
1719
  // Check existing config.headers
1525
1720
  const existingHeaders = (entry.config ?? {}).headers;
1526
1721
  // Collect declared headers: from security.headers or security.name
1527
1722
  const declaredHeaders = apiKeySec.headers
1528
- ? Object.entries(apiKeySec.headers).map(([h, meta]) => ({ headerName: h, description: meta.description }))
1723
+ ? Object.entries(apiKeySec.headers).map(([h, meta]) => ({
1724
+ headerName: h,
1725
+ description: meta.description,
1726
+ }))
1529
1727
  : apiKeySec.name
1530
1728
  ? [{ headerName: apiKeySec.name }]
1531
1729
  : [];
@@ -1535,12 +1733,13 @@ function createAdk(fs, options = {}) {
1535
1733
  for (const { headerName, description } of declaredHeaders) {
1536
1734
  const storageKey = toStorageKey(headerName);
1537
1735
  // Check: credentials param → existing config.headers → legacy config key → resolve callback
1538
- const value = opts?.credentials?.[storageKey]
1539
- ?? opts?.credentials?.[headerName]
1540
- ?? (existingHeaders && Object.entries(existingHeaders).find(([k]) => k.toLowerCase() === headerName.toLowerCase())?.[1])
1541
- ?? opts?.apiKey
1542
- ?? await readRefSecret(name, storageKey)
1543
- ?? await tryResolve(storageKey);
1736
+ const value = opts?.credentials?.[storageKey] ??
1737
+ opts?.credentials?.[headerName] ??
1738
+ (existingHeaders &&
1739
+ Object.entries(existingHeaders).find(([k]) => k.toLowerCase() === headerName.toLowerCase())?.[1]) ??
1740
+ opts?.apiKey ??
1741
+ (await readRefSecret(name, storageKey)) ??
1742
+ (await tryResolve(storageKey));
1544
1743
  if (value) {
1545
1744
  resolvedHeaders[headerName] = value;
1546
1745
  }
@@ -1560,22 +1759,28 @@ function createAdk(fs, options = {}) {
1560
1759
  const encKey = options.encryptionKey;
1561
1760
  const headersToStore = {};
1562
1761
  for (const [h, v] of Object.entries(resolvedHeaders)) {
1563
- headersToStore[h] = encKey ? `${SECRET_PREFIX}${await (0, crypto_js_1.encryptSecret)(v, encKey)}` : v;
1762
+ headersToStore[h] = encKey
1763
+ ? `${SECRET_PREFIX}${await (0, crypto_js_1.encryptSecret)(v, encKey)}`
1764
+ : v;
1564
1765
  }
1565
1766
  await ref.update(name, { config: { headers: headersToStore } });
1566
1767
  return { type: "apiKey", complete: true };
1567
1768
  }
1568
1769
  // Fallback: no headers declared → generic api_key
1569
- const key = opts?.credentials?.["api_key"] ?? opts?.apiKey ?? await tryResolve("api_key");
1770
+ const key = opts?.credentials?.["api_key"] ??
1771
+ opts?.apiKey ??
1772
+ (await tryResolve("api_key"));
1570
1773
  if (!key) {
1571
1774
  return {
1572
1775
  type: "apiKey",
1573
1776
  complete: false,
1574
- fields: [{
1777
+ fields: [
1778
+ {
1575
1779
  name: "api_key",
1576
1780
  label: "API Key",
1577
1781
  secret: true,
1578
- }],
1782
+ },
1783
+ ],
1579
1784
  };
1580
1785
  }
1581
1786
  await storeRefSecret(name, "api_key", key);
@@ -1585,14 +1790,22 @@ function createAdk(fs, options = {}) {
1585
1790
  const httpSec = security;
1586
1791
  const isBasic = httpSec.scheme === "basic";
1587
1792
  if (isBasic) {
1588
- const username = opts?.credentials?.["username"] ?? await tryResolve("username");
1589
- const password = opts?.credentials?.["password"] ?? await tryResolve("password");
1793
+ const username = opts?.credentials?.["username"] ?? (await tryResolve("username"));
1794
+ const password = opts?.credentials?.["password"] ?? (await tryResolve("password"));
1590
1795
  if (!username || !password) {
1591
1796
  const missingFields = [];
1592
1797
  if (!username)
1593
- missingFields.push({ name: "username", label: "Username", secret: false });
1798
+ missingFields.push({
1799
+ name: "username",
1800
+ label: "Username",
1801
+ secret: false,
1802
+ });
1594
1803
  if (!password)
1595
- missingFields.push({ name: "password", label: "Password", secret: true });
1804
+ missingFields.push({
1805
+ name: "password",
1806
+ label: "Password",
1807
+ secret: true,
1808
+ });
1596
1809
  return { type: "http", complete: false, fields: missingFields };
1597
1810
  }
1598
1811
  // Store as base64 encoded basic auth token
@@ -1601,7 +1814,9 @@ function createAdk(fs, options = {}) {
1601
1814
  return { type: "http", complete: true };
1602
1815
  }
1603
1816
  // Bearer token
1604
- const token = opts?.credentials?.["token"] ?? opts?.apiKey ?? await tryResolve("token");
1817
+ const token = opts?.credentials?.["token"] ??
1818
+ opts?.apiKey ??
1819
+ (await tryResolve("token"));
1605
1820
  if (!token) {
1606
1821
  return {
1607
1822
  type: "http",
@@ -1646,17 +1861,21 @@ function createAdk(fs, options = {}) {
1646
1861
  }
1647
1862
  const redirectUri = callbackUrl();
1648
1863
  // Resolve client credentials: callback → stored → dynamic registration
1649
- let clientId = await tryResolve("client_id", metadata)
1650
- ?? await readRefSecret(name, "client_id");
1651
- let clientSecret = await tryResolve("client_secret", metadata)
1652
- ?? await readRefSecret(name, "client_secret")
1653
- ?? undefined;
1864
+ const fromHelper = await resolveOAuthClient({
1865
+ name,
1866
+ entry,
1867
+ security,
1868
+ metadata,
1869
+ });
1870
+ let clientId = fromHelper?.clientId;
1871
+ let clientSecret = fromHelper?.clientSecret;
1654
1872
  if (!clientId && metadata.registration_endpoint) {
1655
1873
  const supportedAuthMethods = metadata.token_endpoint_auth_methods_supported ?? ["none"];
1656
1874
  const preferredMethod = supportedAuthMethods.includes("none")
1657
1875
  ? "none"
1658
- : supportedAuthMethods[0] ?? "client_secret_post";
1659
- const securityClientName = security.clientName;
1876
+ : (supportedAuthMethods[0] ?? "client_secret_post");
1877
+ const securityClientName = security
1878
+ .clientName;
1660
1879
  const reg = await (0, mcp_client_js_1.dynamicClientRegistration)(metadata.registration_endpoint, {
1661
1880
  clientName: securityClientName ?? options.oauthClientName ?? "adk",
1662
1881
  redirectUris: [redirectUri],
@@ -1674,10 +1893,18 @@ function createAdk(fs, options = {}) {
1674
1893
  // Return fields telling the caller what OAuth credentials to provide
1675
1894
  const missingFields = [];
1676
1895
  if (!clientId) {
1677
- missingFields.push({ name: "client_id", label: "Client ID", secret: false });
1896
+ missingFields.push({
1897
+ name: "client_id",
1898
+ label: "Client ID",
1899
+ secret: false,
1900
+ });
1678
1901
  }
1679
1902
  // Always ask for client_secret alongside client_id — most providers need it
1680
- missingFields.push({ name: "client_secret", label: "Client Secret", secret: true });
1903
+ missingFields.push({
1904
+ name: "client_secret",
1905
+ label: "Client Secret",
1906
+ secret: true,
1907
+ });
1681
1908
  return { type: "oauth2", complete: false, fields: missingFields };
1682
1909
  }
1683
1910
  // State ties the callback back to this ref. Encode as base64 JSON
@@ -1699,7 +1926,9 @@ function createAdk(fs, options = {}) {
1699
1926
  const scopes = agentScopes.length > 0
1700
1927
  ? [
1701
1928
  ...agentScopes,
1702
- ...(metadata.scopes_supported?.includes('openid') ? ['openid'] : []),
1929
+ ...(metadata.scopes_supported?.includes("openid")
1930
+ ? ["openid"]
1931
+ : []),
1703
1932
  ]
1704
1933
  : metadata.scopes_supported;
1705
1934
  // Read provider-specific authorization params from the agent's security section
@@ -1748,7 +1977,9 @@ function createAdk(fs, options = {}) {
1748
1977
  // owns the credential store, so the user needs to submit via
1749
1978
  // whatever UI the registry exposes. Supporting this through the
1750
1979
  // proxy would need a remote form endpoint — out of scope here.
1751
- if (result.fields && result.fields.length > 0 && result.type !== "oauth2") {
1980
+ if (result.fields &&
1981
+ result.fields.length > 0 &&
1982
+ result.type !== "oauth2") {
1752
1983
  if (proxy) {
1753
1984
  throw new Error(`Ref "${name}" is sourced from a proxied registry; submit credentials through ${proxy.agent} instead of a local form.`);
1754
1985
  }
@@ -1870,22 +2101,28 @@ function createAdk(fs, options = {}) {
1870
2101
  const refreshToken = await readRefSecret(name, "refresh_token");
1871
2102
  if (!refreshToken)
1872
2103
  return null;
1873
- // Read client credentials
1874
- const clientId = await readRefSecret(name, "client_id");
1875
- if (!clientId)
1876
- return null;
1877
- const clientSecret = await readRefSecret(name, "client_secret");
1878
- // Get the agent's token endpoint from its security metadata
2104
+ // Resolve token endpoint + OAuth client via the host's
2105
+ // `resolveCredentials` chain. Same chain `auth` uses (see
2106
+ // `resolveOAuthClient`) — kept symmetric so refresh works on every
2107
+ // ref `auth` works on, including first-party registry-hosted
2108
+ // clients whose creds live in env / tenant scope, not the user's
2109
+ // per-ref config.
1879
2110
  const entry = await ref.get(name);
1880
2111
  if (!entry)
1881
2112
  return null;
1882
- const info = await ref.inspect(name);
1883
- const security = info?.security;
1884
- const flows = security?.flows;
2113
+ const status = await ref.authStatus(name);
2114
+ const security = status.security;
2115
+ const flows = security && "flows" in security
2116
+ ? security.flows
2117
+ : undefined;
1885
2118
  const authCodeFlow = flows?.authorizationCode;
1886
- const tokenUrl = (authCodeFlow?.refreshUrl ?? authCodeFlow?.tokenUrl);
2119
+ const tokenUrl = authCodeFlow?.refreshUrl ?? authCodeFlow?.tokenUrl;
1887
2120
  if (!tokenUrl)
1888
2121
  return null;
2122
+ const oauthClient = await resolveOAuthClient({ name, entry, security });
2123
+ if (!oauthClient)
2124
+ return null;
2125
+ const { clientId, clientSecret } = oauthClient;
1889
2126
  // POST to the token endpoint with grant_type=refresh_token
1890
2127
  const body = new URLSearchParams({
1891
2128
  grant_type: "refresh_token",
@@ -1902,7 +2139,7 @@ function createAdk(fs, options = {}) {
1902
2139
  });
1903
2140
  if (!res.ok)
1904
2141
  return null;
1905
- const data = await res.json();
2142
+ const data = (await res.json());
1906
2143
  const newAccessToken = data.access_token;
1907
2144
  if (!newAccessToken)
1908
2145
  return null;
@@ -1937,7 +2174,9 @@ function createAdk(fs, options = {}) {
1937
2174
  try {
1938
2175
  stateContext = JSON.parse(atob(params.state));
1939
2176
  }
1940
- catch { /* state wasn't base64 JSON — legacy format */ }
2177
+ catch {
2178
+ /* state wasn't base64 JSON — legacy format */
2179
+ }
1941
2180
  return { refName: pending.refName, complete: true, stateContext };
1942
2181
  }
1943
2182
  return { registry, ref, readConfig, writeConfig, handleCallback };