@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.
- package/dist/adk-tools.d.ts.map +1 -1
- package/dist/adk-tools.js +104 -253
- package/dist/adk-tools.js.map +1 -1
- package/dist/call-agent-schema.d.ts +12 -12
- package/dist/cjs/adk-tools.js +104 -253
- package/dist/cjs/adk-tools.js.map +1 -1
- package/dist/cjs/config-store.js +333 -94
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/define-config.js.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/registry.js +2 -33
- package/dist/cjs/registry.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/config-store.d.ts +34 -4
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +333 -94
- package/dist/config-store.js.map +1 -1
- package/dist/define-config.d.ts +28 -4
- package/dist/define-config.d.ts.map +1 -1
- package/dist/define-config.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -33
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +3 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/adk-tools.ts +113 -282
- package/src/config-store.test.ts +345 -28
- package/src/config-store.ts +829 -274
- package/src/define-config.ts +47 -21
- package/src/index.ts +16 -13
- package/src/ref-naming.test.ts +1 -49
- package/src/registry.ts +2 -40
- package/src/types.ts +6 -21
package/dist/cjs/config-store.js
CHANGED
|
@@ -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 &&
|
|
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 !==
|
|
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
|
|
149
|
+
const esc = (s) => s
|
|
150
|
+
.replace(/&/g, "&")
|
|
151
|
+
.replace(/</g, "<")
|
|
152
|
+
.replace(/>/g, ">")
|
|
153
|
+
.replace(/"/g, """);
|
|
147
154
|
function renderCredentialForm(name, fields, error) {
|
|
148
|
-
const fieldHtml = fields
|
|
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>`)
|
|
154
|
-
|
|
155
|
-
|
|
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 = {
|
|
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", {
|
|
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 {
|
|
488
|
+
return {
|
|
489
|
+
success: true,
|
|
490
|
+
result: JSON.parse(textContent.text),
|
|
491
|
+
};
|
|
346
492
|
}
|
|
347
493
|
catch {
|
|
348
|
-
return {
|
|
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 {
|
|
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 && {
|
|
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
|
-
: {
|
|
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 = {
|
|
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 &&
|
|
1180
|
-
(info.
|
|
1181
|
-
|
|
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: {
|
|
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 !==
|
|
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: {
|
|
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
|
|
1247
|
-
|
|
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
|
|
1251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
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 ===
|
|
1502
|
+
if (rawHeaders && typeof rawHeaders === "object") {
|
|
1312
1503
|
resolvedHeaders = {};
|
|
1313
1504
|
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
1314
|
-
if (typeof v ===
|
|
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 ===
|
|
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 ??
|
|
1326
|
-
if (token && entry.url && agentMode !==
|
|
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
|
|
1595
|
+
const tryResolveField = makeTryResolve({ name, entry, security });
|
|
1403
1596
|
async function canResolve(field, oauthMetadata) {
|
|
1404
|
-
|
|
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
|
|
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
|
|
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, {
|
|
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
|
|
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
|
|
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]) => ({
|
|
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
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
??
|
|
1543
|
-
|
|
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
|
|
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"] ??
|
|
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({
|
|
1798
|
+
missingFields.push({
|
|
1799
|
+
name: "username",
|
|
1800
|
+
label: "Username",
|
|
1801
|
+
secret: false,
|
|
1802
|
+
});
|
|
1594
1803
|
if (!password)
|
|
1595
|
-
missingFields.push({
|
|
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"] ??
|
|
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
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
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
|
|
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({
|
|
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({
|
|
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(
|
|
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 &&
|
|
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
|
-
//
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
//
|
|
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
|
|
1883
|
-
const security =
|
|
1884
|
-
const flows = security
|
|
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 =
|
|
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 {
|
|
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 };
|