@slashfi/agents-sdk 0.77.3 → 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/call-agent-schema.d.ts +12 -12
- package/dist/cjs/config-store.js +265 -68
- 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/config-store.d.ts +34 -4
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +265 -68
- 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/package.json +1 -1
- package/src/config-store.test.ts +345 -28
- package/src/config-store.ts +735 -250
- package/src/define-config.ts +47 -21
- package/src/index.ts +16 -13
package/dist/config-store.js
CHANGED
|
@@ -16,12 +16,13 @@
|
|
|
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:";
|
|
26
27
|
// ============================================
|
|
27
28
|
// Internal helpers
|
|
@@ -74,7 +75,9 @@ async function decryptConfigSecrets(obj, encryptionKey) {
|
|
|
74
75
|
if (typeof value === "string" && value.startsWith(SECRET_PREFIX)) {
|
|
75
76
|
result[key] = await decryptSecret(value.slice(SECRET_PREFIX.length), encryptionKey);
|
|
76
77
|
}
|
|
77
|
-
else if (value !== null &&
|
|
78
|
+
else if (value !== null &&
|
|
79
|
+
typeof value === "object" &&
|
|
80
|
+
!Array.isArray(value)) {
|
|
78
81
|
result[key] = await decryptConfigSecrets(value, encryptionKey);
|
|
79
82
|
}
|
|
80
83
|
else {
|
|
@@ -92,7 +95,7 @@ async function decryptConfigSecrets(obj, encryptionKey) {
|
|
|
92
95
|
* Fallback: _httpStatus from tool result body
|
|
93
96
|
*/
|
|
94
97
|
function isUnauthorized(result) {
|
|
95
|
-
if (!result || typeof result !==
|
|
98
|
+
if (!result || typeof result !== "object")
|
|
96
99
|
return false;
|
|
97
100
|
const r = result;
|
|
98
101
|
// Primary: HTTP status forwarded by the registry and set by callRegistry
|
|
@@ -107,17 +110,21 @@ function isUnauthorized(result) {
|
|
|
107
110
|
// ============================================
|
|
108
111
|
// Local auth form HTML
|
|
109
112
|
// ============================================
|
|
110
|
-
const esc = (s) => s
|
|
113
|
+
const esc = (s) => s
|
|
114
|
+
.replace(/&/g, "&")
|
|
115
|
+
.replace(/</g, "<")
|
|
116
|
+
.replace(/>/g, ">")
|
|
117
|
+
.replace(/"/g, """);
|
|
111
118
|
function renderCredentialForm(name, fields, error) {
|
|
112
|
-
const fieldHtml = fields
|
|
119
|
+
const fieldHtml = fields
|
|
120
|
+
.map((f) => `
|
|
113
121
|
<div class="field">
|
|
114
122
|
<label for="${esc(f.name)}">${esc(f.label)}</label>
|
|
115
123
|
${f.description ? `<p class="desc">${esc(f.description)}</p>` : ""}
|
|
116
124
|
<input id="${esc(f.name)}" name="${esc(f.name)}" type="${f.secret ? "password" : "text"}" required autocomplete="off" spellcheck="false" />
|
|
117
|
-
</div>`)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
: "";
|
|
125
|
+
</div>`)
|
|
126
|
+
.join("");
|
|
127
|
+
const errorHtml = error ? `<div class="error">${esc(error)}</div>` : "";
|
|
121
128
|
return `<!DOCTYPE html>
|
|
122
129
|
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
123
130
|
<title>Authenticate \u2014 ${esc(name)}</title>
|
|
@@ -180,6 +187,95 @@ export function createAdk(fs, options = {}) {
|
|
|
180
187
|
async function writeConfig(config) {
|
|
181
188
|
await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
182
189
|
}
|
|
190
|
+
// -------------------------------------------------------------------------
|
|
191
|
+
// Registry cache helpers
|
|
192
|
+
//
|
|
193
|
+
// The cache is purely an internal optimization for the adk's read paths
|
|
194
|
+
// (`ref.list()`, `ref.get()`). Writes happen as side-effects of methods
|
|
195
|
+
// that already call the registry (`ref.add()`, `ref.inspect()`); the
|
|
196
|
+
// public surface never grows new methods. Cache failures (missing file,
|
|
197
|
+
// malformed JSON, fs errors during write) are swallowed so the registry
|
|
198
|
+
// cache can never break a registry operation.
|
|
199
|
+
// -------------------------------------------------------------------------
|
|
200
|
+
async function readRegistryCache() {
|
|
201
|
+
try {
|
|
202
|
+
const content = await fs.readFile(REGISTRY_CACHE_PATH);
|
|
203
|
+
if (!content)
|
|
204
|
+
return { refs: {} };
|
|
205
|
+
const parsed = JSON.parse(content);
|
|
206
|
+
return { refs: parsed.refs ?? {} };
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return { refs: {} };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function writeRegistryCache(cache) {
|
|
213
|
+
try {
|
|
214
|
+
await fs.writeFile(REGISTRY_CACHE_PATH, JSON.stringify(cache, null, 2));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Best-effort. A failed cache write should never break the operation
|
|
218
|
+
// that triggered it.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Project an inspect/list response into the slim shape we cache. Drops
|
|
223
|
+
* `inputSchema` (too large) and `fullTokens` (registry-internal). Returns
|
|
224
|
+
* undefined if the response carries nothing worth caching.
|
|
225
|
+
*/
|
|
226
|
+
function buildCacheEntry(ref, info) {
|
|
227
|
+
if (!info)
|
|
228
|
+
return undefined;
|
|
229
|
+
const toolSource = info.tools ?? info.toolSummaries;
|
|
230
|
+
const tools = toolSource?.map((t) => {
|
|
231
|
+
const slim = { name: t.name };
|
|
232
|
+
if (t.description !== undefined)
|
|
233
|
+
slim.description = t.description;
|
|
234
|
+
return slim;
|
|
235
|
+
});
|
|
236
|
+
if (info.description === undefined && (!tools || tools.length === 0)) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const entry = {
|
|
240
|
+
ref,
|
|
241
|
+
fetchedAt: new Date().toISOString(),
|
|
242
|
+
};
|
|
243
|
+
if (info.description !== undefined)
|
|
244
|
+
entry.description = info.description;
|
|
245
|
+
if (tools && tools.length > 0)
|
|
246
|
+
entry.tools = tools;
|
|
247
|
+
return entry;
|
|
248
|
+
}
|
|
249
|
+
async function upsertRegistryCacheEntry(name, entry) {
|
|
250
|
+
if (!entry)
|
|
251
|
+
return;
|
|
252
|
+
const cache = await readRegistryCache();
|
|
253
|
+
cache.refs[name] = entry;
|
|
254
|
+
await writeRegistryCache(cache);
|
|
255
|
+
}
|
|
256
|
+
async function removeRegistryCacheEntry(name) {
|
|
257
|
+
const cache = await readRegistryCache();
|
|
258
|
+
if (!(name in cache.refs))
|
|
259
|
+
return;
|
|
260
|
+
delete cache.refs[name];
|
|
261
|
+
await writeRegistryCache(cache);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Hydrate a `ResolvedRef` with cached registry metadata when available.
|
|
265
|
+
* Pure: never mutates input. Leaves `description` / `tools` undefined when
|
|
266
|
+
* the cache has no entry, so callers can apply their own UX fallback.
|
|
267
|
+
*/
|
|
268
|
+
function hydrateFromCache(ref, cache) {
|
|
269
|
+
const cached = cache.refs[ref.name];
|
|
270
|
+
if (!cached)
|
|
271
|
+
return ref;
|
|
272
|
+
const next = { ...ref };
|
|
273
|
+
if (cached.description !== undefined)
|
|
274
|
+
next.description = cached.description;
|
|
275
|
+
if (cached.tools !== undefined)
|
|
276
|
+
next.tools = cached.tools;
|
|
277
|
+
return next;
|
|
278
|
+
}
|
|
183
279
|
/**
|
|
184
280
|
* Store a secret value in a ref's config, encrypted if encryptionKey is set.
|
|
185
281
|
* The value is stored inline as "secret:<encrypted>" in consumer-config.json.
|
|
@@ -290,7 +386,10 @@ export function createAdk(fs, options = {}) {
|
|
|
290
386
|
let reqId = 0;
|
|
291
387
|
let sessionId;
|
|
292
388
|
async function rpc(method, rpcParams) {
|
|
293
|
-
const reqHeaders = {
|
|
389
|
+
const reqHeaders = {
|
|
390
|
+
...headers,
|
|
391
|
+
...(sessionId ? { "Mcp-Session-Id": sessionId } : {}),
|
|
392
|
+
};
|
|
294
393
|
const res = await globalThis.fetch(url, {
|
|
295
394
|
method: "POST",
|
|
296
395
|
headers: reqHeaders,
|
|
@@ -331,7 +430,7 @@ export function createAdk(fs, options = {}) {
|
|
|
331
430
|
}
|
|
332
431
|
return undefined;
|
|
333
432
|
}
|
|
334
|
-
const json = await res.json();
|
|
433
|
+
const json = (await res.json());
|
|
335
434
|
if (json.error)
|
|
336
435
|
throw new Error(`MCP RPC error: ${json.error.message}`);
|
|
337
436
|
return json.result;
|
|
@@ -343,20 +442,32 @@ export function createAdk(fs, options = {}) {
|
|
|
343
442
|
clientInfo: { name: "adk", version: "1.0.0" },
|
|
344
443
|
});
|
|
345
444
|
await rpc("notifications/initialized").catch(() => { });
|
|
346
|
-
const result = await rpc("tools/call", {
|
|
445
|
+
const result = (await rpc("tools/call", {
|
|
446
|
+
name: toolName,
|
|
447
|
+
arguments: params,
|
|
448
|
+
}));
|
|
347
449
|
const textContent = result?.content?.find((c) => c.type === "text");
|
|
348
450
|
if (textContent?.text) {
|
|
349
451
|
try {
|
|
350
|
-
return {
|
|
452
|
+
return {
|
|
453
|
+
success: true,
|
|
454
|
+
result: JSON.parse(textContent.text),
|
|
455
|
+
};
|
|
351
456
|
}
|
|
352
457
|
catch {
|
|
353
|
-
return {
|
|
458
|
+
return {
|
|
459
|
+
success: true,
|
|
460
|
+
result: textContent.text,
|
|
461
|
+
};
|
|
354
462
|
}
|
|
355
463
|
}
|
|
356
464
|
return { success: true, result };
|
|
357
465
|
}
|
|
358
466
|
catch (err) {
|
|
359
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
success: false,
|
|
469
|
+
error: err instanceof Error ? err.message : String(err),
|
|
470
|
+
};
|
|
360
471
|
}
|
|
361
472
|
}
|
|
362
473
|
function callbackUrl() {
|
|
@@ -369,7 +480,7 @@ export function createAdk(fs, options = {}) {
|
|
|
369
480
|
const res = await globalThis.fetch(url);
|
|
370
481
|
if (!res.ok)
|
|
371
482
|
return null;
|
|
372
|
-
const data = await res.json();
|
|
483
|
+
const data = (await res.json());
|
|
373
484
|
if (data.authorization_endpoint && data.token_endpoint) {
|
|
374
485
|
return data;
|
|
375
486
|
}
|
|
@@ -739,7 +850,9 @@ export function createAdk(fs, options = {}) {
|
|
|
739
850
|
...final,
|
|
740
851
|
proxy: {
|
|
741
852
|
mode: discovered.proxy.mode,
|
|
742
|
-
...(discovered.proxy.agent && {
|
|
853
|
+
...(discovered.proxy.agent && {
|
|
854
|
+
agent: discovered.proxy.agent,
|
|
855
|
+
}),
|
|
743
856
|
},
|
|
744
857
|
};
|
|
745
858
|
}
|
|
@@ -866,7 +979,12 @@ export function createAdk(fs, options = {}) {
|
|
|
866
979
|
}));
|
|
867
980
|
return results.map((r) => r.status === "fulfilled"
|
|
868
981
|
? r.value
|
|
869
|
-
: {
|
|
982
|
+
: {
|
|
983
|
+
name: "unknown",
|
|
984
|
+
url: "unknown",
|
|
985
|
+
status: "error",
|
|
986
|
+
error: "unknown",
|
|
987
|
+
});
|
|
870
988
|
},
|
|
871
989
|
async auth(nameOrUrl, credential) {
|
|
872
990
|
// Encrypt the secret value up-front so the write path is uniform;
|
|
@@ -1146,7 +1264,10 @@ export function createAdk(fs, options = {}) {
|
|
|
1146
1264
|
entry = { ...entry, scheme: "registry" };
|
|
1147
1265
|
}
|
|
1148
1266
|
else if (entry.url) {
|
|
1149
|
-
entry = {
|
|
1267
|
+
entry = {
|
|
1268
|
+
...entry,
|
|
1269
|
+
scheme: entry.url.startsWith("http") ? "https" : "mcp",
|
|
1270
|
+
};
|
|
1150
1271
|
}
|
|
1151
1272
|
else {
|
|
1152
1273
|
throw new AdkError({
|
|
@@ -1174,6 +1295,7 @@ export function createAdk(fs, options = {}) {
|
|
|
1174
1295
|
details: { ref: entry.ref, scheme: entry.scheme },
|
|
1175
1296
|
});
|
|
1176
1297
|
}
|
|
1298
|
+
let cacheEntry;
|
|
1177
1299
|
if (hasRegistries || entry.sourceRegistry?.url) {
|
|
1178
1300
|
try {
|
|
1179
1301
|
const consumer = await buildConsumerForRef(entry);
|
|
@@ -1181,9 +1303,10 @@ export function createAdk(fs, options = {}) {
|
|
|
1181
1303
|
const info = await consumer.inspect(agentToInspect);
|
|
1182
1304
|
const requiresValidation = !!entry.sourceRegistry;
|
|
1183
1305
|
if (requiresValidation) {
|
|
1184
|
-
const hasContent = info &&
|
|
1185
|
-
(info.
|
|
1186
|
-
|
|
1306
|
+
const hasContent = info &&
|
|
1307
|
+
(info.description ||
|
|
1308
|
+
(info.tools && info.tools.length > 0) ||
|
|
1309
|
+
(info.toolSummaries && info.toolSummaries.length > 0));
|
|
1187
1310
|
if (!hasContent) {
|
|
1188
1311
|
// Inspect returned empty — fall back to browse to check if agent exists
|
|
1189
1312
|
const registryUrl = entry.sourceRegistry?.url;
|
|
@@ -1205,7 +1328,11 @@ export function createAdk(fs, options = {}) {
|
|
|
1205
1328
|
code: "REF_NOT_FOUND",
|
|
1206
1329
|
message: `Agent "${entry.ref}" not found on ${registryHint}`,
|
|
1207
1330
|
hint: "Check available agents with: adk registry browse",
|
|
1208
|
-
details: {
|
|
1331
|
+
details: {
|
|
1332
|
+
ref: entry.ref,
|
|
1333
|
+
sourceRegistry: entry.sourceRegistry,
|
|
1334
|
+
scheme: entry.scheme,
|
|
1335
|
+
},
|
|
1209
1336
|
});
|
|
1210
1337
|
}
|
|
1211
1338
|
}
|
|
@@ -1215,10 +1342,11 @@ export function createAdk(fs, options = {}) {
|
|
|
1215
1342
|
const agentMode = info?.mode;
|
|
1216
1343
|
if (agentMode)
|
|
1217
1344
|
entry.mode = agentMode;
|
|
1218
|
-
if (info?.upstream && !entry.url && agentMode !==
|
|
1345
|
+
if (info?.upstream && !entry.url && agentMode !== "api") {
|
|
1219
1346
|
entry.url = info.upstream;
|
|
1220
1347
|
entry.scheme = entry.scheme ?? "mcp";
|
|
1221
1348
|
}
|
|
1349
|
+
cacheEntry = buildCacheEntry(entry.ref, info);
|
|
1222
1350
|
}
|
|
1223
1351
|
catch (err) {
|
|
1224
1352
|
if (err instanceof AdkError)
|
|
@@ -1227,13 +1355,17 @@ export function createAdk(fs, options = {}) {
|
|
|
1227
1355
|
code: "REGISTRY_UNREACHABLE",
|
|
1228
1356
|
message: `Could not reach registry to validate "${entry.ref}"`,
|
|
1229
1357
|
hint: "Check your registry connection with: adk registry test",
|
|
1230
|
-
details: {
|
|
1358
|
+
details: {
|
|
1359
|
+
ref: entry.ref,
|
|
1360
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1361
|
+
},
|
|
1231
1362
|
cause: err,
|
|
1232
1363
|
});
|
|
1233
1364
|
}
|
|
1234
1365
|
}
|
|
1235
1366
|
const refs = [...(config.refs ?? []), entry];
|
|
1236
1367
|
await writeConfig({ ...config, refs });
|
|
1368
|
+
await upsertRegistryCacheEntry(name, cacheEntry);
|
|
1237
1369
|
return { security };
|
|
1238
1370
|
},
|
|
1239
1371
|
async remove(name) {
|
|
@@ -1245,15 +1377,27 @@ export function createAdk(fs, options = {}) {
|
|
|
1245
1377
|
if (refs.length === before)
|
|
1246
1378
|
return false;
|
|
1247
1379
|
await writeConfig({ ...config, refs });
|
|
1380
|
+
await removeRegistryCacheEntry(name);
|
|
1248
1381
|
return true;
|
|
1249
1382
|
},
|
|
1250
1383
|
async list() {
|
|
1251
|
-
const config = await
|
|
1252
|
-
|
|
1384
|
+
const [config, cache] = await Promise.all([
|
|
1385
|
+
readConfig(),
|
|
1386
|
+
readRegistryCache(),
|
|
1387
|
+
]);
|
|
1388
|
+
return (config.refs ?? [])
|
|
1389
|
+
.map(normalizeRef)
|
|
1390
|
+
.map((r) => hydrateFromCache(r, cache));
|
|
1253
1391
|
},
|
|
1254
1392
|
async get(name) {
|
|
1255
|
-
const config = await
|
|
1256
|
-
|
|
1393
|
+
const [config, cache] = await Promise.all([
|
|
1394
|
+
readConfig(),
|
|
1395
|
+
readRegistryCache(),
|
|
1396
|
+
]);
|
|
1397
|
+
const found = findRef(config.refs ?? [], name);
|
|
1398
|
+
if (!found)
|
|
1399
|
+
return null;
|
|
1400
|
+
return hydrateFromCache(found, cache);
|
|
1257
1401
|
},
|
|
1258
1402
|
async update(name, updates) {
|
|
1259
1403
|
const config = await readConfig();
|
|
@@ -1299,27 +1443,35 @@ export function createAdk(fs, options = {}) {
|
|
|
1299
1443
|
if (!entry)
|
|
1300
1444
|
throw new Error(`Ref "${name}" not found`);
|
|
1301
1445
|
const consumer = await buildConsumerForRef(entry);
|
|
1302
|
-
|
|
1446
|
+
const result = await consumer.inspect(entry.sourceRegistry?.agentPath ?? entry.ref, entry.sourceRegistry?.url, opts);
|
|
1447
|
+
// Side-effect: refresh the registry cache so subsequent ref.list()
|
|
1448
|
+
// / ref.get() calls see the latest description and tool summaries
|
|
1449
|
+
// without another network round-trip. Strips inputSchema (caller's
|
|
1450
|
+
// `result` is unaffected — it still carries the full data).
|
|
1451
|
+
await upsertRegistryCacheEntry(name, buildCacheEntry(entry.ref, result));
|
|
1452
|
+
return result;
|
|
1303
1453
|
},
|
|
1304
1454
|
async call(name, tool, params) {
|
|
1305
1455
|
const config = await readConfig();
|
|
1306
1456
|
const entry = findRef(config.refs ?? [], name);
|
|
1307
1457
|
if (!entry)
|
|
1308
1458
|
throw new Error(`Ref "${name}" not found`);
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1459
|
+
const accessToken = (await readRefSecret(name, "access_token")) ??
|
|
1460
|
+
(await readRefSecret(name, "api_key")) ??
|
|
1461
|
+
(await readRefSecret(name, "token"));
|
|
1312
1462
|
// Resolve custom headers from config (e.g. { "X-API-Key": "secret:..." })
|
|
1313
1463
|
const refConfig = (entry.config ?? {});
|
|
1314
1464
|
const rawHeaders = refConfig.headers;
|
|
1315
1465
|
let resolvedHeaders;
|
|
1316
|
-
if (rawHeaders && typeof rawHeaders ===
|
|
1466
|
+
if (rawHeaders && typeof rawHeaders === "object") {
|
|
1317
1467
|
resolvedHeaders = {};
|
|
1318
1468
|
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
1319
|
-
if (typeof v ===
|
|
1469
|
+
if (typeof v === "string" &&
|
|
1470
|
+
v.startsWith(SECRET_PREFIX) &&
|
|
1471
|
+
options.encryptionKey) {
|
|
1320
1472
|
resolvedHeaders[k] = await decryptSecret(v.slice(SECRET_PREFIX.length), options.encryptionKey);
|
|
1321
1473
|
}
|
|
1322
|
-
else if (typeof v ===
|
|
1474
|
+
else if (typeof v === "string") {
|
|
1323
1475
|
resolvedHeaders[k] = v;
|
|
1324
1476
|
}
|
|
1325
1477
|
}
|
|
@@ -1327,8 +1479,8 @@ export function createAdk(fs, options = {}) {
|
|
|
1327
1479
|
const doCall = async (token) => {
|
|
1328
1480
|
// Direct MCP only for redirect/proxy agents with an MCP upstream.
|
|
1329
1481
|
// API-mode agents must go through the registry (it does REST translation).
|
|
1330
|
-
const agentMode = entry.mode ??
|
|
1331
|
-
if (token && entry.url && agentMode !==
|
|
1482
|
+
const agentMode = entry.mode ?? "redirect";
|
|
1483
|
+
if (token && entry.url && agentMode !== "api") {
|
|
1332
1484
|
return callMcpDirect(entry.url, tool, params ?? {}, token, resolvedHeaders);
|
|
1333
1485
|
}
|
|
1334
1486
|
const consumer = await buildConsumerForRef(entry);
|
|
@@ -1444,10 +1596,15 @@ export function createAdk(fs, options = {}) {
|
|
|
1444
1596
|
}
|
|
1445
1597
|
else if (security.type === "apiKey") {
|
|
1446
1598
|
const apiKeySec = security;
|
|
1447
|
-
const toStorageKey = (headerName) => headerName
|
|
1599
|
+
const toStorageKey = (headerName) => headerName
|
|
1600
|
+
.toLowerCase()
|
|
1601
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
1602
|
+
.replace(/^_|_$/g, "");
|
|
1448
1603
|
// config.headers: { "Header-Name": "value" } — check by header name (case-insensitive)
|
|
1449
1604
|
const configHeaders = entry?.config?.headers;
|
|
1450
|
-
const configHeaderKeys = configHeaders
|
|
1605
|
+
const configHeaderKeys = configHeaders
|
|
1606
|
+
? Object.keys(configHeaders)
|
|
1607
|
+
: [];
|
|
1451
1608
|
const hasConfigHeader = (name) => configHeaderKeys.some((k) => k.toLowerCase() === name.toLowerCase());
|
|
1452
1609
|
// Collect all declared header names from the security scheme
|
|
1453
1610
|
const declaredHeaders = apiKeySec.headers
|
|
@@ -1496,7 +1653,9 @@ export function createAdk(fs, options = {}) {
|
|
|
1496
1653
|
// agent. The registry owns the client_id/secret and returns an authorize
|
|
1497
1654
|
// URL pointing at the registry's OAuth callback domain, so the user
|
|
1498
1655
|
// completes the flow against the registry instead of localhost.
|
|
1499
|
-
const proxy = await resolveProxyForRef(entry, {
|
|
1656
|
+
const proxy = await resolveProxyForRef(entry, {
|
|
1657
|
+
preferLocal: opts?.preferLocal,
|
|
1658
|
+
});
|
|
1500
1659
|
if (proxy) {
|
|
1501
1660
|
const params = { name };
|
|
1502
1661
|
if (opts?.apiKey !== undefined)
|
|
@@ -1517,12 +1676,18 @@ export function createAdk(fs, options = {}) {
|
|
|
1517
1676
|
}
|
|
1518
1677
|
if (security.type === "apiKey") {
|
|
1519
1678
|
const apiKeySec = security;
|
|
1520
|
-
const toStorageKey = (headerName) => headerName
|
|
1679
|
+
const toStorageKey = (headerName) => headerName
|
|
1680
|
+
.toLowerCase()
|
|
1681
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
1682
|
+
.replace(/^_|_$/g, "");
|
|
1521
1683
|
// Check existing config.headers
|
|
1522
1684
|
const existingHeaders = (entry.config ?? {}).headers;
|
|
1523
1685
|
// Collect declared headers: from security.headers or security.name
|
|
1524
1686
|
const declaredHeaders = apiKeySec.headers
|
|
1525
|
-
? Object.entries(apiKeySec.headers).map(([h, meta]) => ({
|
|
1687
|
+
? Object.entries(apiKeySec.headers).map(([h, meta]) => ({
|
|
1688
|
+
headerName: h,
|
|
1689
|
+
description: meta.description,
|
|
1690
|
+
}))
|
|
1526
1691
|
: apiKeySec.name
|
|
1527
1692
|
? [{ headerName: apiKeySec.name }]
|
|
1528
1693
|
: [];
|
|
@@ -1532,12 +1697,13 @@ export function createAdk(fs, options = {}) {
|
|
|
1532
1697
|
for (const { headerName, description } of declaredHeaders) {
|
|
1533
1698
|
const storageKey = toStorageKey(headerName);
|
|
1534
1699
|
// Check: credentials param → existing config.headers → legacy config key → resolve callback
|
|
1535
|
-
const value = opts?.credentials?.[storageKey]
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
??
|
|
1540
|
-
|
|
1700
|
+
const value = opts?.credentials?.[storageKey] ??
|
|
1701
|
+
opts?.credentials?.[headerName] ??
|
|
1702
|
+
(existingHeaders &&
|
|
1703
|
+
Object.entries(existingHeaders).find(([k]) => k.toLowerCase() === headerName.toLowerCase())?.[1]) ??
|
|
1704
|
+
opts?.apiKey ??
|
|
1705
|
+
(await readRefSecret(name, storageKey)) ??
|
|
1706
|
+
(await tryResolve(storageKey));
|
|
1541
1707
|
if (value) {
|
|
1542
1708
|
resolvedHeaders[headerName] = value;
|
|
1543
1709
|
}
|
|
@@ -1557,22 +1723,28 @@ export function createAdk(fs, options = {}) {
|
|
|
1557
1723
|
const encKey = options.encryptionKey;
|
|
1558
1724
|
const headersToStore = {};
|
|
1559
1725
|
for (const [h, v] of Object.entries(resolvedHeaders)) {
|
|
1560
|
-
headersToStore[h] = encKey
|
|
1726
|
+
headersToStore[h] = encKey
|
|
1727
|
+
? `${SECRET_PREFIX}${await encryptSecret(v, encKey)}`
|
|
1728
|
+
: v;
|
|
1561
1729
|
}
|
|
1562
1730
|
await ref.update(name, { config: { headers: headersToStore } });
|
|
1563
1731
|
return { type: "apiKey", complete: true };
|
|
1564
1732
|
}
|
|
1565
1733
|
// Fallback: no headers declared → generic api_key
|
|
1566
|
-
const key = opts?.credentials?.["api_key"] ??
|
|
1734
|
+
const key = opts?.credentials?.["api_key"] ??
|
|
1735
|
+
opts?.apiKey ??
|
|
1736
|
+
(await tryResolve("api_key"));
|
|
1567
1737
|
if (!key) {
|
|
1568
1738
|
return {
|
|
1569
1739
|
type: "apiKey",
|
|
1570
1740
|
complete: false,
|
|
1571
|
-
fields: [
|
|
1741
|
+
fields: [
|
|
1742
|
+
{
|
|
1572
1743
|
name: "api_key",
|
|
1573
1744
|
label: "API Key",
|
|
1574
1745
|
secret: true,
|
|
1575
|
-
}
|
|
1746
|
+
},
|
|
1747
|
+
],
|
|
1576
1748
|
};
|
|
1577
1749
|
}
|
|
1578
1750
|
await storeRefSecret(name, "api_key", key);
|
|
@@ -1582,14 +1754,22 @@ export function createAdk(fs, options = {}) {
|
|
|
1582
1754
|
const httpSec = security;
|
|
1583
1755
|
const isBasic = httpSec.scheme === "basic";
|
|
1584
1756
|
if (isBasic) {
|
|
1585
|
-
const username = opts?.credentials?.["username"] ?? await tryResolve("username");
|
|
1586
|
-
const password = opts?.credentials?.["password"] ?? await tryResolve("password");
|
|
1757
|
+
const username = opts?.credentials?.["username"] ?? (await tryResolve("username"));
|
|
1758
|
+
const password = opts?.credentials?.["password"] ?? (await tryResolve("password"));
|
|
1587
1759
|
if (!username || !password) {
|
|
1588
1760
|
const missingFields = [];
|
|
1589
1761
|
if (!username)
|
|
1590
|
-
missingFields.push({
|
|
1762
|
+
missingFields.push({
|
|
1763
|
+
name: "username",
|
|
1764
|
+
label: "Username",
|
|
1765
|
+
secret: false,
|
|
1766
|
+
});
|
|
1591
1767
|
if (!password)
|
|
1592
|
-
missingFields.push({
|
|
1768
|
+
missingFields.push({
|
|
1769
|
+
name: "password",
|
|
1770
|
+
label: "Password",
|
|
1771
|
+
secret: true,
|
|
1772
|
+
});
|
|
1593
1773
|
return { type: "http", complete: false, fields: missingFields };
|
|
1594
1774
|
}
|
|
1595
1775
|
// Store as base64 encoded basic auth token
|
|
@@ -1598,7 +1778,9 @@ export function createAdk(fs, options = {}) {
|
|
|
1598
1778
|
return { type: "http", complete: true };
|
|
1599
1779
|
}
|
|
1600
1780
|
// Bearer token
|
|
1601
|
-
const token = opts?.credentials?.["token"] ??
|
|
1781
|
+
const token = opts?.credentials?.["token"] ??
|
|
1782
|
+
opts?.apiKey ??
|
|
1783
|
+
(await tryResolve("token"));
|
|
1602
1784
|
if (!token) {
|
|
1603
1785
|
return {
|
|
1604
1786
|
type: "http",
|
|
@@ -1655,8 +1837,9 @@ export function createAdk(fs, options = {}) {
|
|
|
1655
1837
|
const supportedAuthMethods = metadata.token_endpoint_auth_methods_supported ?? ["none"];
|
|
1656
1838
|
const preferredMethod = supportedAuthMethods.includes("none")
|
|
1657
1839
|
? "none"
|
|
1658
|
-
: supportedAuthMethods[0] ?? "client_secret_post";
|
|
1659
|
-
const securityClientName = security
|
|
1840
|
+
: (supportedAuthMethods[0] ?? "client_secret_post");
|
|
1841
|
+
const securityClientName = security
|
|
1842
|
+
.clientName;
|
|
1660
1843
|
const reg = await dynamicClientRegistration(metadata.registration_endpoint, {
|
|
1661
1844
|
clientName: securityClientName ?? options.oauthClientName ?? "adk",
|
|
1662
1845
|
redirectUris: [redirectUri],
|
|
@@ -1674,10 +1857,18 @@ export function createAdk(fs, options = {}) {
|
|
|
1674
1857
|
// Return fields telling the caller what OAuth credentials to provide
|
|
1675
1858
|
const missingFields = [];
|
|
1676
1859
|
if (!clientId) {
|
|
1677
|
-
missingFields.push({
|
|
1860
|
+
missingFields.push({
|
|
1861
|
+
name: "client_id",
|
|
1862
|
+
label: "Client ID",
|
|
1863
|
+
secret: false,
|
|
1864
|
+
});
|
|
1678
1865
|
}
|
|
1679
1866
|
// Always ask for client_secret alongside client_id — most providers need it
|
|
1680
|
-
missingFields.push({
|
|
1867
|
+
missingFields.push({
|
|
1868
|
+
name: "client_secret",
|
|
1869
|
+
label: "Client Secret",
|
|
1870
|
+
secret: true,
|
|
1871
|
+
});
|
|
1681
1872
|
return { type: "oauth2", complete: false, fields: missingFields };
|
|
1682
1873
|
}
|
|
1683
1874
|
// State ties the callback back to this ref. Encode as base64 JSON
|
|
@@ -1699,7 +1890,9 @@ export function createAdk(fs, options = {}) {
|
|
|
1699
1890
|
const scopes = agentScopes.length > 0
|
|
1700
1891
|
? [
|
|
1701
1892
|
...agentScopes,
|
|
1702
|
-
...(metadata.scopes_supported?.includes(
|
|
1893
|
+
...(metadata.scopes_supported?.includes("openid")
|
|
1894
|
+
? ["openid"]
|
|
1895
|
+
: []),
|
|
1703
1896
|
]
|
|
1704
1897
|
: metadata.scopes_supported;
|
|
1705
1898
|
// Read provider-specific authorization params from the agent's security section
|
|
@@ -1748,7 +1941,9 @@ export function createAdk(fs, options = {}) {
|
|
|
1748
1941
|
// owns the credential store, so the user needs to submit via
|
|
1749
1942
|
// whatever UI the registry exposes. Supporting this through the
|
|
1750
1943
|
// proxy would need a remote form endpoint — out of scope here.
|
|
1751
|
-
if (result.fields &&
|
|
1944
|
+
if (result.fields &&
|
|
1945
|
+
result.fields.length > 0 &&
|
|
1946
|
+
result.type !== "oauth2") {
|
|
1752
1947
|
if (proxy) {
|
|
1753
1948
|
throw new Error(`Ref "${name}" is sourced from a proxied registry; submit credentials through ${proxy.agent} instead of a local form.`);
|
|
1754
1949
|
}
|
|
@@ -1908,7 +2103,7 @@ export function createAdk(fs, options = {}) {
|
|
|
1908
2103
|
});
|
|
1909
2104
|
if (!res.ok)
|
|
1910
2105
|
return null;
|
|
1911
|
-
const data = await res.json();
|
|
2106
|
+
const data = (await res.json());
|
|
1912
2107
|
const newAccessToken = data.access_token;
|
|
1913
2108
|
if (!newAccessToken)
|
|
1914
2109
|
return null;
|
|
@@ -1943,7 +2138,9 @@ export function createAdk(fs, options = {}) {
|
|
|
1943
2138
|
try {
|
|
1944
2139
|
stateContext = JSON.parse(atob(params.state));
|
|
1945
2140
|
}
|
|
1946
|
-
catch {
|
|
2141
|
+
catch {
|
|
2142
|
+
/* state wasn't base64 JSON — legacy format */
|
|
2143
|
+
}
|
|
1947
2144
|
return { refName: pending.refName, complete: true, stateContext };
|
|
1948
2145
|
}
|
|
1949
2146
|
return { registry, ref, readConfig, writeConfig, handleCallback };
|