@slashfi/agents-sdk 0.80.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adk.js +3 -28
- package/dist/adk.js.map +1 -1
- package/dist/cjs/config-store.js +20 -212
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/define-config.js.map +1 -1
- package/dist/cjs/materialize.js +3 -4
- package/dist/cjs/materialize.js.map +1 -1
- package/dist/cjs/registry-consumer.js +0 -8
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/server.js +0 -8
- package/dist/cjs/server.js.map +1 -1
- package/dist/config-store.d.ts +24 -19
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +20 -212
- package/dist/config-store.js.map +1 -1
- package/dist/define-config.d.ts +0 -29
- package/dist/define-config.d.ts.map +1 -1
- package/dist/define-config.js.map +1 -1
- package/dist/materialize.d.ts.map +1 -1
- package/dist/materialize.js +3 -4
- package/dist/materialize.js.map +1 -1
- package/dist/registry-consumer.d.ts +0 -10
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js +0 -8
- package/dist/registry-consumer.js.map +1 -1
- package/dist/server.d.ts +0 -11
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +0 -8
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/adk.ts +3 -31
- package/src/config-store.test.ts +56 -185
- package/src/config-store.ts +38 -259
- package/src/define-config.ts +0 -31
- package/src/materialize.ts +3 -4
- package/src/registry-consumer.ts +0 -27
- package/src/server.ts +0 -19
package/src/config-store.ts
CHANGED
|
@@ -103,12 +103,6 @@ export interface RegistryCacheEntry {
|
|
|
103
103
|
* entry's `config` — no network round-trip needed. Absent when the
|
|
104
104
|
* scheme couldn't be fetched (e.g. registry was offline at add
|
|
105
105
|
* time); fall back to whatever heuristic the caller chooses.
|
|
106
|
-
*
|
|
107
|
-
* Note on proxy refs: when the entry is in `proxy` mode the
|
|
108
|
-
* security scheme is exposed by the *proxy*, and the answer to
|
|
109
|
-
* "is this callable?" lives server-side — `authFields` is omitted
|
|
110
|
-
* locally and hosts should treat proxy refs as authoritative
|
|
111
|
-
* regardless of entry-side fields.
|
|
112
106
|
*/
|
|
113
107
|
authFields?: Record<string, RegistryCacheAuthField>;
|
|
114
108
|
/** ISO timestamp of the most recent registry round-trip that wrote this. */
|
|
@@ -124,6 +118,25 @@ export interface RegistryCache {
|
|
|
124
118
|
refs: Record<string, RegistryCacheEntry>;
|
|
125
119
|
}
|
|
126
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Options for `isRefAuthComplete`.
|
|
123
|
+
*/
|
|
124
|
+
export interface RefAuthCompleteOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Field names the consumer can resolve at call time without them
|
|
127
|
+
* being present in `entry.config` — typically OAuth client_id /
|
|
128
|
+
* client_secret resolved from environment variables or platform
|
|
129
|
+
* config by the host's `resolveCredentials` callback.
|
|
130
|
+
*
|
|
131
|
+
* Required-non-automated fields listed here count as satisfied even
|
|
132
|
+
* when missing from `entry.config`. The default behaviour (no opt
|
|
133
|
+
* passed) requires every such field to live in config, which is
|
|
134
|
+
* correct for self-hosted SDK consumers but wrong for platforms
|
|
135
|
+
* that inject OAuth client credentials at runtime.
|
|
136
|
+
*/
|
|
137
|
+
resolvableFields?: ReadonlyArray<string>;
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
/**
|
|
128
141
|
* "Is this ref ready to call?" answered locally using the cached
|
|
129
142
|
* security-scheme requirements. Mirrors the `complete` boolean
|
|
@@ -132,16 +145,15 @@ export interface RegistryCache {
|
|
|
132
145
|
* we evaluate satisfaction against the entry's current `config`.
|
|
133
146
|
*
|
|
134
147
|
* Behavior:
|
|
135
|
-
* - `mode: 'proxy'` refs → always true. Auth lives server-side; the
|
|
136
|
-
* proxy is the source of truth, no entry-side fields involved.
|
|
137
148
|
* - Cache miss (no `authFields` for this ref yet) → returns `null`,
|
|
138
149
|
* signaling "I don't know — caller should fall back to its own
|
|
139
150
|
* heuristic or call `auth-status` to populate the cache".
|
|
140
151
|
* - Cache hit → for every required, non-automated field, checks
|
|
141
|
-
* presence in `entry.config
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
152
|
+
* presence in `entry.config` OR (if `opts.resolvableFields`
|
|
153
|
+
* includes the field name) treats it as satisfied externally.
|
|
154
|
+
* Mirrors the `present || resolvable` check in `auth-status`.
|
|
155
|
+
* `automated` fields (e.g. dynamic OAuth client_id minted by the
|
|
156
|
+
* registry) always count as satisfied.
|
|
145
157
|
*
|
|
146
158
|
* Returning `null` for cache miss is intentional. A boolean would
|
|
147
159
|
* force callers to choose a default that's wrong half the time;
|
|
@@ -150,16 +162,22 @@ export interface RegistryCache {
|
|
|
150
162
|
export function isRefAuthComplete(
|
|
151
163
|
entry: RefEntry,
|
|
152
164
|
cacheEntry: RegistryCacheEntry | undefined,
|
|
165
|
+
opts?: RefAuthCompleteOptions,
|
|
153
166
|
): boolean | null {
|
|
154
167
|
if (typeof entry === "string") return false;
|
|
155
|
-
if ((entry as { mode?: unknown }).mode === "proxy") return true;
|
|
156
168
|
const authFields = cacheEntry?.authFields;
|
|
157
169
|
if (!authFields) return null;
|
|
158
170
|
const config = entry.config ?? {};
|
|
171
|
+
const resolvable =
|
|
172
|
+
opts?.resolvableFields && opts.resolvableFields.length > 0
|
|
173
|
+
? new Set(opts.resolvableFields)
|
|
174
|
+
: null;
|
|
159
175
|
for (const [field, info] of Object.entries(authFields)) {
|
|
160
176
|
if (!info.required) continue;
|
|
161
177
|
if (info.automated) continue;
|
|
162
|
-
if (
|
|
178
|
+
if (field in config) continue;
|
|
179
|
+
if (resolvable && resolvable.has(field)) continue;
|
|
180
|
+
return false;
|
|
163
181
|
}
|
|
164
182
|
return true;
|
|
165
183
|
}
|
|
@@ -405,12 +423,6 @@ export interface AdkRefApi {
|
|
|
405
423
|
stateContext?: Record<string, unknown>;
|
|
406
424
|
/** Additional scopes to request (e.g., optional scopes declared by the agent) */
|
|
407
425
|
scopes?: string[];
|
|
408
|
-
/**
|
|
409
|
-
* Opt out of proxy routing when the ref's source registry has
|
|
410
|
-
* `proxy: { mode: 'optional' }`. Ignored for `mode: 'required'`.
|
|
411
|
-
* Defaults to `false` — if a registry offers a proxy we use it.
|
|
412
|
-
*/
|
|
413
|
-
preferLocal?: boolean;
|
|
414
426
|
},
|
|
415
427
|
): Promise<AuthStartResult>;
|
|
416
428
|
/**
|
|
@@ -1161,96 +1173,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1161
1173
|
return fallback;
|
|
1162
1174
|
}
|
|
1163
1175
|
|
|
1164
|
-
// ==========================================
|
|
1165
|
-
// Proxy Routing
|
|
1166
|
-
// ==========================================
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Find the configured RegistryEntry for a ref, consulting `sourceRegistry`
|
|
1170
|
-
* first and falling back to the first registry in config. Returns `null` when
|
|
1171
|
-
* the ref is sourced from a raw URL (no registry), in which case proxy routing
|
|
1172
|
-
* does not apply.
|
|
1173
|
-
*/
|
|
1174
|
-
async function findRegistryEntryForRef(
|
|
1175
|
-
entry: RefEntry,
|
|
1176
|
-
): Promise<RegistryEntry | null> {
|
|
1177
|
-
const sourceUrl = entry.sourceRegistry?.url;
|
|
1178
|
-
if (!sourceUrl) return null;
|
|
1179
|
-
const config = await readConfig();
|
|
1180
|
-
const match = (config.registries ?? []).find((r) => {
|
|
1181
|
-
if (typeof r === "string") return r === sourceUrl;
|
|
1182
|
-
return r.url === sourceUrl;
|
|
1183
|
-
});
|
|
1184
|
-
if (!match || typeof match === "string") return null;
|
|
1185
|
-
return match;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
/**
|
|
1189
|
-
* Returns the proxy settings for a ref when its source registry has
|
|
1190
|
-
* `proxy` configured. `null` means "run locally".
|
|
1191
|
-
*
|
|
1192
|
-
* Callers pass `{ preferLocal: true }` to opt out of `mode: 'optional'`
|
|
1193
|
-
* proxying when they already hold credentials locally. `mode: 'required'`
|
|
1194
|
-
* cannot be bypassed — the registry owns auth server-side and there is
|
|
1195
|
-
* nothing useful the local SDK can do.
|
|
1196
|
-
*/
|
|
1197
|
-
async function resolveProxyForRef(
|
|
1198
|
-
entry: RefEntry,
|
|
1199
|
-
opts?: { preferLocal?: boolean },
|
|
1200
|
-
): Promise<{ reg: RegistryEntry; agent: string } | null> {
|
|
1201
|
-
const reg = await findRegistryEntryForRef(entry);
|
|
1202
|
-
if (!reg?.proxy) return null;
|
|
1203
|
-
if (reg.proxy.mode === "optional" && opts?.preferLocal) return null;
|
|
1204
|
-
return { reg, agent: reg.proxy.agent ?? "@config" };
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
/**
|
|
1208
|
-
* Forward an `@config ref` operation to the proxy agent on a remote registry.
|
|
1209
|
-
*
|
|
1210
|
-
* The remote side speaks the standard adk-tools surface, so the call shape is
|
|
1211
|
-
* identical to what the local `ref` API would do — the only difference is
|
|
1212
|
-
* that tokens and secrets live server-side. `callRegistry` returns the
|
|
1213
|
-
* standard CallAgentResponse envelope: `{ success: true, result }` on
|
|
1214
|
-
* success or `{ success: false, error }` on failure. We unwrap once and
|
|
1215
|
-
* throw on error so callers get a result that matches the local signature.
|
|
1216
|
-
*/
|
|
1217
|
-
async function forwardRefOpToProxy<T>(
|
|
1218
|
-
reg: RegistryEntry,
|
|
1219
|
-
agent: string,
|
|
1220
|
-
operation: string,
|
|
1221
|
-
params: Record<string, unknown>,
|
|
1222
|
-
): Promise<T> {
|
|
1223
|
-
const consumer = await buildConsumerForRef({
|
|
1224
|
-
ref: "",
|
|
1225
|
-
name: "",
|
|
1226
|
-
sourceRegistry: { url: reg.url, agentPath: agent },
|
|
1227
|
-
});
|
|
1228
|
-
const resolved = consumer.registries().find((r) => r.url === reg.url);
|
|
1229
|
-
if (!resolved)
|
|
1230
|
-
throw new Error(
|
|
1231
|
-
`Registry ${reg.url} not resolvable for proxy forwarding`,
|
|
1232
|
-
);
|
|
1233
|
-
|
|
1234
|
-
const response = await consumer.callRegistry(resolved, {
|
|
1235
|
-
action: "execute_tool",
|
|
1236
|
-
path: agent,
|
|
1237
|
-
tool: "ref",
|
|
1238
|
-
params: { operation, ...params },
|
|
1239
|
-
});
|
|
1240
|
-
|
|
1241
|
-
if (!response.success) {
|
|
1242
|
-
const errResponse = response as {
|
|
1243
|
-
success: false;
|
|
1244
|
-
error?: string;
|
|
1245
|
-
code?: string;
|
|
1246
|
-
};
|
|
1247
|
-
const msg =
|
|
1248
|
-
errResponse.error ?? `Proxy ${agent}.ref(${operation}) failed`;
|
|
1249
|
-
throw new Error(msg);
|
|
1250
|
-
}
|
|
1251
|
-
return (response as { success: true; result: unknown }).result as T;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
1176
|
// ==========================================
|
|
1255
1177
|
// Registry API
|
|
1256
1178
|
// ==========================================
|
|
@@ -1265,39 +1187,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1265
1187
|
return `${SECRET_PREFIX}${await encryptSecret(value, options.encryptionKey)}`;
|
|
1266
1188
|
}
|
|
1267
1189
|
|
|
1268
|
-
/**
|
|
1269
|
-
* Re-probe a registry with the current stored credentials to see whether it
|
|
1270
|
-
* advertises `capabilities.registry.proxy` in its MCP `initialize` response,
|
|
1271
|
-
* and persist the proxy config when it does. Safe to call after a successful
|
|
1272
|
-
* `auth()` / `authLocal()` — on the add path we skip the proxy probe when
|
|
1273
|
-
* auth is required, so this is the second chance to back-fill it.
|
|
1274
|
-
*
|
|
1275
|
-
* Respects explicit user config: if `proxy` is already set, we leave it
|
|
1276
|
-
* alone. Any discovery failure is swallowed — proxy is an optimization,
|
|
1277
|
-
* not a correctness requirement.
|
|
1278
|
-
*/
|
|
1279
|
-
async function discoverProxyAfterAuth(nameOrUrl: string): Promise<void> {
|
|
1280
|
-
const config = await readConfig();
|
|
1281
|
-
const target = findRegistry(config.registries ?? [], nameOrUrl);
|
|
1282
|
-
if (!target || typeof target === "string") return;
|
|
1283
|
-
if (target.proxy) return;
|
|
1284
|
-
|
|
1285
|
-
try {
|
|
1286
|
-
const consumer = await buildConsumer(nameOrUrl);
|
|
1287
|
-
const discovered = await consumer.discover(target.url);
|
|
1288
|
-
if (!discovered.proxy?.mode) return;
|
|
1289
|
-
await updateRegistryEntry(nameOrUrl, (existing) => {
|
|
1290
|
-
if (existing.proxy) return;
|
|
1291
|
-
existing.proxy = {
|
|
1292
|
-
mode: discovered.proxy!.mode,
|
|
1293
|
-
...(discovered.proxy!.agent && { agent: discovered.proxy!.agent }),
|
|
1294
|
-
};
|
|
1295
|
-
});
|
|
1296
|
-
} catch {
|
|
1297
|
-
// Proxy probe is best-effort — auth itself already succeeded.
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
1190
|
/**
|
|
1302
1191
|
* Atomic read-modify-write on a registry entry by name or URL. Used by
|
|
1303
1192
|
* `authLocal` to persist both `auth` and `oauth` together, which `auth()`
|
|
@@ -1449,14 +1338,10 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1449
1338
|
(r) => registryDisplayName(r) !== alias,
|
|
1450
1339
|
);
|
|
1451
1340
|
|
|
1452
|
-
// Probe the registry before saving.
|
|
1453
|
-
//
|
|
1454
|
-
//
|
|
1455
|
-
//
|
|
1456
|
-
// 2. Proxy capability — the MCP `initialize` response may advertise
|
|
1457
|
-
// `capabilities.registry.proxy`, which auto-populates `proxy`.
|
|
1458
|
-
// Users who set `proxy` or `auth` explicitly on the entry always win:
|
|
1459
|
-
// discovery only fills in blanks.
|
|
1341
|
+
// Probe the registry before saving. If it returns 401 with a
|
|
1342
|
+
// WWW-Authenticate / RFC 9728 resource metadata pointer, persist
|
|
1343
|
+
// that on `authRequirement` so subsequent ops can refuse early
|
|
1344
|
+
// with a friendly message.
|
|
1460
1345
|
let final: RegistryEntry = entry;
|
|
1461
1346
|
let authRequirement: RegistryAuthRequirement | undefined;
|
|
1462
1347
|
|
|
@@ -1475,33 +1360,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1475
1360
|
}
|
|
1476
1361
|
}
|
|
1477
1362
|
|
|
1478
|
-
if (!entry.proxy && !authRequirement) {
|
|
1479
|
-
try {
|
|
1480
|
-
const probeConsumer = await createRegistryConsumer(
|
|
1481
|
-
{ registries: [entry], refs: [] },
|
|
1482
|
-
{ token: options.token, fetch: options.fetch },
|
|
1483
|
-
);
|
|
1484
|
-
const resolved = probeConsumer.registries()[0];
|
|
1485
|
-
if (resolved) {
|
|
1486
|
-
const discovered = await probeConsumer.discover(resolved.url);
|
|
1487
|
-
if (discovered.proxy?.mode) {
|
|
1488
|
-
final = {
|
|
1489
|
-
...final,
|
|
1490
|
-
proxy: {
|
|
1491
|
-
mode: discovered.proxy.mode,
|
|
1492
|
-
...(discovered.proxy.agent && {
|
|
1493
|
-
agent: discovered.proxy.agent,
|
|
1494
|
-
}),
|
|
1495
|
-
},
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
} catch {
|
|
1500
|
-
// Discovery is best-effort — offline, unreachable, or non-adk
|
|
1501
|
-
// registries simply skip proxy auto-configuration.
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
1363
|
registries.push(final);
|
|
1506
1364
|
await writeConfig({ ...config, registries });
|
|
1507
1365
|
return authRequirement ? { authRequirement } : {};
|
|
@@ -1552,7 +1410,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1552
1410
|
if (updates.auth) existing.auth = updates.auth;
|
|
1553
1411
|
if (updates.headers)
|
|
1554
1412
|
existing.headers = { ...existing.headers, ...updates.headers };
|
|
1555
|
-
if (updates.proxy !== undefined) existing.proxy = updates.proxy;
|
|
1556
1413
|
return existing;
|
|
1557
1414
|
});
|
|
1558
1415
|
if (!found) return false;
|
|
@@ -1669,7 +1526,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1669
1526
|
}
|
|
1670
1527
|
delete existing.authRequirement;
|
|
1671
1528
|
});
|
|
1672
|
-
if (updated) await discoverProxyAfterAuth(nameOrUrl);
|
|
1673
1529
|
return updated;
|
|
1674
1530
|
},
|
|
1675
1531
|
|
|
@@ -1850,7 +1706,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
1850
1706
|
};
|
|
1851
1707
|
delete existing.authRequirement;
|
|
1852
1708
|
});
|
|
1853
|
-
await discoverProxyAfterAuth(displayName);
|
|
1854
1709
|
resOut.writeHead(200, { "Content-Type": "text/html" });
|
|
1855
1710
|
resOut.end(renderAuthSuccess(displayName));
|
|
1856
1711
|
server.close();
|
|
@@ -2297,18 +2152,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
2297
2152
|
const entry = findRef(config.refs ?? [], name);
|
|
2298
2153
|
if (!entry) throw new Error(`Ref "${name}" not found`);
|
|
2299
2154
|
|
|
2300
|
-
// Registry-proxied refs: ask the remote @config for state (secrets live
|
|
2301
|
-
// server-side so local inspection would always return "missing").
|
|
2302
|
-
const proxy = await resolveProxyForRef(entry);
|
|
2303
|
-
if (proxy) {
|
|
2304
|
-
return forwardRefOpToProxy<RefAuthStatus>(
|
|
2305
|
-
proxy.reg,
|
|
2306
|
-
proxy.agent,
|
|
2307
|
-
"auth-status",
|
|
2308
|
-
{ name },
|
|
2309
|
-
);
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
2155
|
let security: SecuritySchemeSummary | null = null;
|
|
2313
2156
|
try {
|
|
2314
2157
|
const consumer = await buildConsumerForRef(entry);
|
|
@@ -2465,38 +2308,12 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
2465
2308
|
stateContext?: Record<string, unknown>;
|
|
2466
2309
|
/** Additional scopes to request (e.g., optional scopes declared by the agent) */
|
|
2467
2310
|
scopes?: string[];
|
|
2468
|
-
/**
|
|
2469
|
-
* Opt out of proxy routing when the ref's source registry has
|
|
2470
|
-
* `proxy: { mode: 'optional' }`. Ignored for `mode: 'required'`.
|
|
2471
|
-
*/
|
|
2472
|
-
preferLocal?: boolean;
|
|
2473
2311
|
},
|
|
2474
2312
|
): Promise<AuthStartResult> {
|
|
2475
2313
|
const config = await readConfig();
|
|
2476
2314
|
const entry = findRef(config.refs ?? [], name);
|
|
2477
2315
|
if (!entry) throw new Error(`Ref "${name}" not found`);
|
|
2478
2316
|
|
|
2479
|
-
// Registry-proxied auth: forward the start-of-flow to the remote @config
|
|
2480
|
-
// agent. The registry owns the client_id/secret and returns an authorize
|
|
2481
|
-
// URL pointing at the registry's OAuth callback domain, so the user
|
|
2482
|
-
// completes the flow against the registry instead of localhost.
|
|
2483
|
-
const proxy = await resolveProxyForRef(entry, {
|
|
2484
|
-
preferLocal: opts?.preferLocal,
|
|
2485
|
-
});
|
|
2486
|
-
if (proxy) {
|
|
2487
|
-
const params: Record<string, unknown> = { name };
|
|
2488
|
-
if (opts?.apiKey !== undefined) params.apiKey = opts.apiKey;
|
|
2489
|
-
if (opts?.credentials) params.credentials = opts.credentials;
|
|
2490
|
-
if (opts?.scopes) params.scopes = opts.scopes;
|
|
2491
|
-
if (opts?.stateContext) params.stateContext = opts.stateContext;
|
|
2492
|
-
return forwardRefOpToProxy<AuthStartResult>(
|
|
2493
|
-
proxy.reg,
|
|
2494
|
-
proxy.agent,
|
|
2495
|
-
"auth",
|
|
2496
|
-
params,
|
|
2497
|
-
);
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
2317
|
const status = await ref.authStatus(name);
|
|
2501
2318
|
const security = status.security;
|
|
2502
2319
|
const tryResolve = makeTryResolve({ name, entry, security });
|
|
@@ -2825,38 +2642,19 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
2825
2642
|
timeoutMs?: number;
|
|
2826
2643
|
},
|
|
2827
2644
|
): Promise<{ complete: boolean }> {
|
|
2828
|
-
// `ref.auth` is already proxy-aware — for proxied refs it returns
|
|
2829
|
-
// the authorizeUrl that the registry minted against its own
|
|
2830
|
-
// callback domain. Everything below is identical for local and
|
|
2831
|
-
// proxied refs except the last step (polling for the callback),
|
|
2832
|
-
// which only makes sense when we own the redirect URI.
|
|
2833
2645
|
const result = await ref.auth(name);
|
|
2834
2646
|
if (result.complete) return { complete: true };
|
|
2835
2647
|
|
|
2836
|
-
const config = await readConfig();
|
|
2837
|
-
const entry = findRef(config.refs ?? [], name);
|
|
2838
|
-
const proxy = entry ? await resolveProxyForRef(entry) : null;
|
|
2839
|
-
|
|
2840
2648
|
const port = options.oauthCallbackPort ?? 8919;
|
|
2841
2649
|
const timeout = opts?.timeoutMs ?? 300_000;
|
|
2842
2650
|
const { createServer } = await import("node:http");
|
|
2843
2651
|
|
|
2844
2652
|
// API key / HTTP auth — local credential form.
|
|
2845
|
-
//
|
|
2846
|
-
// We refuse to serve the form for a proxied ref: the registry
|
|
2847
|
-
// owns the credential store, so the user needs to submit via
|
|
2848
|
-
// whatever UI the registry exposes. Supporting this through the
|
|
2849
|
-
// proxy would need a remote form endpoint — out of scope here.
|
|
2850
2653
|
if (
|
|
2851
2654
|
result.fields &&
|
|
2852
2655
|
result.fields.length > 0 &&
|
|
2853
2656
|
result.type !== "oauth2"
|
|
2854
2657
|
) {
|
|
2855
|
-
if (proxy) {
|
|
2856
|
-
throw new Error(
|
|
2857
|
-
`Ref "${name}" is sourced from a proxied registry; submit credentials through ${proxy.agent} instead of a local form.`,
|
|
2858
|
-
);
|
|
2859
|
-
}
|
|
2860
2658
|
return new Promise<{ complete: boolean }>((resolve, reject) => {
|
|
2861
2659
|
const server = createServer(async (req, res) => {
|
|
2862
2660
|
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
@@ -2935,13 +2733,8 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
2935
2733
|
opts.onAuthorizeUrl(result.authorizeUrl);
|
|
2936
2734
|
}
|
|
2937
2735
|
|
|
2938
|
-
//
|
|
2939
|
-
//
|
|
2940
|
-
// schedule once the user finishes the remote consent screen.
|
|
2941
|
-
if (proxy) return { complete: false };
|
|
2942
|
-
|
|
2943
|
-
// Local refs: spin up the callback server on oauthCallbackPort and
|
|
2944
|
-
// block until the OAuth provider redirects back.
|
|
2736
|
+
// Spin up the callback server on oauthCallbackPort and block
|
|
2737
|
+
// until the OAuth provider redirects back.
|
|
2945
2738
|
return new Promise<{ complete: boolean }>((resolve, reject) => {
|
|
2946
2739
|
const server = createServer(async (req, res) => {
|
|
2947
2740
|
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
@@ -2986,20 +2779,6 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
2986
2779
|
},
|
|
2987
2780
|
|
|
2988
2781
|
async refreshToken(name: string): Promise<{ accessToken: string } | null> {
|
|
2989
|
-
// Registry-proxied refs: the remote @config holds the refresh_token.
|
|
2990
|
-
const entryForProxy = await ref.get(name);
|
|
2991
|
-
if (entryForProxy) {
|
|
2992
|
-
const proxy = await resolveProxyForRef(entryForProxy);
|
|
2993
|
-
if (proxy) {
|
|
2994
|
-
return forwardRefOpToProxy<{ accessToken: string } | null>(
|
|
2995
|
-
proxy.reg,
|
|
2996
|
-
proxy.agent,
|
|
2997
|
-
"refresh-token",
|
|
2998
|
-
{ name },
|
|
2999
|
-
);
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
2782
|
// Read stored refresh_token
|
|
3004
2783
|
const refreshToken = await readRefSecret(name, "refresh_token");
|
|
3005
2784
|
if (!refreshToken) return null;
|
package/src/define-config.ts
CHANGED
|
@@ -34,30 +34,6 @@ export type RegistryAuth =
|
|
|
34
34
|
| { type: "api-key"; key?: string; header?: string }
|
|
35
35
|
| { type: "jwt"; issuer?: string };
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Proxy configuration for a registry.
|
|
39
|
-
*
|
|
40
|
-
* When set, ref operations (`auth`, `auth-status`, `call`, `inspect`,
|
|
41
|
-
* `resources`, `read`, `refresh-token`) for refs sourced from this
|
|
42
|
-
* registry are forwarded to a server-side agent that implements the
|
|
43
|
-
* adk-tools surface. Use this for cloud-hosted registries that own
|
|
44
|
-
* OAuth client credentials and/or user tokens on behalf of consumers
|
|
45
|
-
* (e.g. `api.twin.slash.com/mcp`).
|
|
46
|
-
*
|
|
47
|
-
* - `mode: 'required'` — all ref ops MUST route through the proxy agent.
|
|
48
|
-
* Local handshake (`ref.authLocal`) is refused for refs from this
|
|
49
|
-
* registry because the local environment has no way to build an
|
|
50
|
-
* authorize URL without the server's client credentials.
|
|
51
|
-
* - `mode: 'optional'` — proxy is the default; callers may opt out via
|
|
52
|
-
* `{ preferLocal: true }` on a per-op basis when they already hold
|
|
53
|
-
* local credentials.
|
|
54
|
-
*/
|
|
55
|
-
export interface RegistryProxy {
|
|
56
|
-
mode: "required" | "optional";
|
|
57
|
-
/** Agent path to forward to. Defaults to `@config`. */
|
|
58
|
-
agent?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
37
|
/**
|
|
62
38
|
* OAuth state captured after `adk registry auth` completes a dynamic-client
|
|
63
39
|
* registration + authorization-code flow against a registry. Stored alongside
|
|
@@ -121,13 +97,6 @@ export interface RegistryEntry {
|
|
|
121
97
|
/** Connection status — set by validation/test, used to filter active entries */
|
|
122
98
|
status?: "active" | "inactive" | "error";
|
|
123
99
|
|
|
124
|
-
/**
|
|
125
|
-
* If set, ref ops for refs sourced from this registry are forwarded
|
|
126
|
-
* to a server-side adk-tools agent (default `@config`) instead of
|
|
127
|
-
* running locally. See {@link RegistryProxy}.
|
|
128
|
-
*/
|
|
129
|
-
proxy?: RegistryProxy;
|
|
130
|
-
|
|
131
100
|
/**
|
|
132
101
|
* Populated by `adk registry add` when the probe returned 401. Cleared
|
|
133
102
|
* by `adk registry auth`. Registry ops refuse to run while this is set
|
package/src/materialize.ts
CHANGED
|
@@ -294,10 +294,9 @@ export async function materializeRef(
|
|
|
294
294
|
// to actually fetch the body. Then the per-resource field is `content`,
|
|
295
295
|
// not `text` (per `CallAgentReadResourcesResponse`).
|
|
296
296
|
//
|
|
297
|
-
//
|
|
298
|
-
// `{success,
|
|
299
|
-
//
|
|
300
|
-
// the inner registry response). Unwrap both shapes the same way.
|
|
297
|
+
// The unwrap below tolerates both `{success, resources}` (direct) and
|
|
298
|
+
// `{success, result: {resources}}` (registries that wrap responses
|
|
299
|
+
// through their MCP `tools/call` envelope) without caring which.
|
|
301
300
|
try {
|
|
302
301
|
type ResourceListEntry = {
|
|
303
302
|
uri?: string;
|
package/src/registry-consumer.ts
CHANGED
|
@@ -209,16 +209,6 @@ export interface RegistryConfiguration {
|
|
|
209
209
|
jwks_uri?: string;
|
|
210
210
|
token_endpoint?: string;
|
|
211
211
|
supported_grant_types?: string[];
|
|
212
|
-
/**
|
|
213
|
-
* When the registry advertises proxy support in its `initialize` response,
|
|
214
|
-
* consumers can auto-populate `RegistryEntry.proxy` at add time so ref ops
|
|
215
|
-
* forward to the server-side adk-tools agent automatically.
|
|
216
|
-
*/
|
|
217
|
-
proxy?: {
|
|
218
|
-
mode: "required" | "optional";
|
|
219
|
-
/** Agent path to forward to. Defaults to '@config'. */
|
|
220
|
-
agent?: string;
|
|
221
|
-
};
|
|
222
212
|
}
|
|
223
213
|
|
|
224
214
|
/** Fields common to every agent reference a registry can return. */
|
|
@@ -498,34 +488,17 @@ async function discoverRegistryViaMcp(
|
|
|
498
488
|
clientInfo: { name: "agents-sdk-consumer", version: "1.0.0" },
|
|
499
489
|
})) as {
|
|
500
490
|
serverInfo?: { name?: string; version?: string };
|
|
501
|
-
capabilities?: {
|
|
502
|
-
registry?: {
|
|
503
|
-
proxy?: {
|
|
504
|
-
mode?: "required" | "optional";
|
|
505
|
-
agent?: string;
|
|
506
|
-
};
|
|
507
|
-
};
|
|
508
|
-
};
|
|
509
491
|
};
|
|
510
492
|
|
|
511
493
|
await rpc("notifications/initialized").catch(() => {});
|
|
512
494
|
|
|
513
495
|
const issuer = issuerFromMcpUrlAndServerInfo(serverUrl, initResult?.serverInfo);
|
|
514
496
|
|
|
515
|
-
const advertisedProxy = initResult?.capabilities?.registry?.proxy;
|
|
516
|
-
const proxy = advertisedProxy?.mode
|
|
517
|
-
? {
|
|
518
|
-
mode: advertisedProxy.mode,
|
|
519
|
-
...(advertisedProxy.agent && { agent: advertisedProxy.agent }),
|
|
520
|
-
}
|
|
521
|
-
: undefined;
|
|
522
|
-
|
|
523
497
|
return {
|
|
524
498
|
issuer,
|
|
525
499
|
jwks_uri: `${issuer}/.well-known/jwks.json`,
|
|
526
500
|
token_endpoint: `${issuer}/oauth/token`,
|
|
527
501
|
supported_grant_types: ["client_credentials", "jwt_exchange"],
|
|
528
|
-
...(proxy && { proxy }),
|
|
529
502
|
};
|
|
530
503
|
}
|
|
531
504
|
|
package/src/server.ts
CHANGED
|
@@ -206,17 +206,6 @@ export interface AgentServerOptions {
|
|
|
206
206
|
features?: string[];
|
|
207
207
|
/** OAuth callback URL for shared OAuth flows */
|
|
208
208
|
oauthCallbackUrl?: string;
|
|
209
|
-
/**
|
|
210
|
-
* Announce that ref operations for agents sourced from this registry
|
|
211
|
-
* should be forwarded to a server-side adk-tools agent instead of
|
|
212
|
-
* running locally. Consumers pick this up during `registry.add` and
|
|
213
|
-
* auto-populate `RegistryEntry.proxy` — no user flag needed.
|
|
214
|
-
*/
|
|
215
|
-
proxy?: {
|
|
216
|
-
mode: "required" | "optional";
|
|
217
|
-
/** Agent path to forward to. Defaults to '@config'. */
|
|
218
|
-
agent?: string;
|
|
219
|
-
};
|
|
220
209
|
};
|
|
221
210
|
/**
|
|
222
211
|
* Structured logger for server-side errors (tool-call failures, JWT
|
|
@@ -602,14 +591,6 @@ export function createAgentServer(
|
|
|
602
591
|
...(options.registry.oauthCallbackUrl && {
|
|
603
592
|
oauthCallbackUrl: options.registry.oauthCallbackUrl,
|
|
604
593
|
}),
|
|
605
|
-
...(options.registry.proxy && {
|
|
606
|
-
proxy: {
|
|
607
|
-
mode: options.registry.proxy.mode,
|
|
608
|
-
...(options.registry.proxy.agent && {
|
|
609
|
-
agent: options.registry.proxy.agent,
|
|
610
|
-
}),
|
|
611
|
-
},
|
|
612
|
-
}),
|
|
613
594
|
},
|
|
614
595
|
}),
|
|
615
596
|
},
|