@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.
@@ -488,175 +488,6 @@ describe("ADK ref.call() full auto-refresh flow", () => {
488
488
  });
489
489
  });
490
490
 
491
- // ─── ADK Config Store: registry proxy routing ───────────────────
492
-
493
- describe("ADK registry proxy routing", () => {
494
- let proxyServer: AgentServer;
495
- let proxyServerAdk: ReturnType<typeof createAdk>;
496
- const PROXY_PORT = 19930;
497
-
498
- beforeAll(async () => {
499
- // Real server-side adk that the proxy agent operates on. When the
500
- // local adk forwards ref ops, they land on this backing store via
501
- // the real @config `ref` tool produced by createAdkTools — no mocks.
502
- const proxyServerFs = createMemoryFs();
503
- proxyServerAdk = createAdk(proxyServerFs);
504
- await proxyServerAdk.writeConfig({
505
- refs: [
506
- {
507
- ref: "@gmail",
508
- scheme: "mcp",
509
- url: "https://gmail.example.com/mcp",
510
- },
511
- ],
512
- });
513
-
514
- // Expose that adk via the same tool surface production uses.
515
- const adkTools = createAdkTools({ resolveScope: () => proxyServerAdk });
516
- const configAgent = defineAgent({
517
- path: "@config",
518
- entrypoint: "@config agent for proxy routing tests",
519
- tools: adkTools,
520
- visibility: "public",
521
- });
522
-
523
- const proxyRegistry = createAgentRegistry();
524
- proxyRegistry.register(configAgent);
525
- proxyServer = createAgentServer(proxyRegistry, {
526
- port: PROXY_PORT,
527
- // Advertise proxy mode in the MCP initialize response so the
528
- // "registry.add auto-detects proxy" test can verify discovery.
529
- registry: { version: "1.0", proxy: { mode: "required" } },
530
- });
531
- await proxyServer.start();
532
- });
533
-
534
- afterAll(async () => {
535
- await proxyServer.stop();
536
- });
537
-
538
- /**
539
- * Seed the local consumer config with a ref sourced from the proxy
540
- * registry. We bypass ref.add's reachability check because we're
541
- * specifically testing how proxying routes around local state.
542
- */
543
- async function seedLocalRefFromProxy(fs: FsStore, refName: string) {
544
- const raw = (await fs.readFile("consumer-config.json")) ?? "{}";
545
- const config = JSON.parse(raw);
546
- config.refs = [
547
- {
548
- ref: refName,
549
- scheme: "registry",
550
- sourceRegistry: {
551
- url: `http://localhost:${PROXY_PORT}`,
552
- agentPath: refName,
553
- },
554
- },
555
- ];
556
- await fs.writeFile("consumer-config.json", JSON.stringify(config));
557
- }
558
-
559
- test("ref.authStatus forwards to the real @config on the proxy registry", async () => {
560
- const fs = createMemoryFs();
561
- const adk = createAdk(fs);
562
-
563
- await adk.registry.add({
564
- url: `http://localhost:${PROXY_PORT}`,
565
- name: "cloud",
566
- proxy: { mode: "required" },
567
- });
568
- await seedLocalRefFromProxy(fs, "@gmail");
569
-
570
- // The proxy-side adk owns the @gmail ref (no security declared in
571
- // its config above) so authStatus should report { complete: true }
572
- // with security: null.
573
- const status = await adk.ref.authStatus("@gmail");
574
- expect(status).toBeDefined();
575
- // The remote @config returned something — local adk never saw the ref,
576
- // so this would throw "Ref not found" if proxying wasn't wired.
577
- expect((status as { name?: string }).name ?? "@gmail").toBe("@gmail");
578
- });
579
-
580
- test("ref.auth forwards and returns the remote auth start result", async () => {
581
- const fs = createMemoryFs();
582
- const adk = createAdk(fs);
583
-
584
- await adk.registry.add({
585
- url: `http://localhost:${PROXY_PORT}`,
586
- name: "cloud",
587
- proxy: { mode: "required" },
588
- });
589
- await seedLocalRefFromProxy(fs, "@gmail");
590
-
591
- // @gmail on the proxy side has no security schema, so the real
592
- // @config.ref tool returns { type: 'none', complete: true }. The
593
- // assertion here is that we got *something* back from the remote,
594
- // proving the call made a round trip instead of throwing locally.
595
- const result = await adk.ref.auth("@gmail");
596
- expect(result).toBeDefined();
597
- expect((result as { type?: string }).type).toBeDefined();
598
- });
599
-
600
- test("registry.add auto-detects proxy from the server's handshake", async () => {
601
- const fs = createMemoryFs();
602
- const adk = createAdk(fs);
603
-
604
- // Caller passes NO proxy config. The server advertises
605
- // `capabilities.registry.proxy: { mode: 'required' }` in its MCP
606
- // initialize response (see beforeAll), so registry.add should
607
- // auto-populate the RegistryEntry at probe time.
608
- await adk.registry.add({
609
- url: `http://localhost:${PROXY_PORT}`,
610
- name: "cloud-autodetect",
611
- });
612
-
613
- const list = await adk.registry.list();
614
- const entry = list.find((r) => r.name === "cloud-autodetect");
615
- expect(entry?.proxy?.mode).toBe("required");
616
- });
617
-
618
- test("explicit proxy on registry.add is not overwritten by auto-detection", async () => {
619
- const fs = createMemoryFs();
620
- const adk = createAdk(fs);
621
-
622
- // Server advertises required; caller explicitly sets optional. The
623
- // caller's choice wins — discovery only fills in blanks.
624
- await adk.registry.add({
625
- url: `http://localhost:${PROXY_PORT}`,
626
- name: "cloud-explicit",
627
- proxy: { mode: "optional", agent: "@custom" },
628
- });
629
-
630
- const entry = (await adk.registry.list()).find(
631
- (r) => r.name === "cloud-explicit",
632
- );
633
- expect(entry?.proxy?.mode).toBe("optional");
634
- expect(entry?.proxy?.agent).toBe("@custom");
635
- });
636
-
637
- test("optional proxy honors preferLocal and skips forwarding", async () => {
638
- const fs = createMemoryFs();
639
- const adk = createAdk(fs);
640
-
641
- await adk.registry.add({
642
- url: `http://localhost:${PROXY_PORT}`,
643
- name: "cloud",
644
- proxy: { mode: "optional" },
645
- });
646
- await seedLocalRefFromProxy(fs, "@gmail");
647
-
648
- // With preferLocal:true we should fall through to the local path.
649
- // The local adk has no usable config for @gmail, so this path
650
- // throws or returns an empty status — either way, we prove we
651
- // stayed local by catching and confirming no exception bubbled
652
- // with "authorizeUrl" (which only the proxy path returns).
653
- const result = await adk.ref
654
- .auth("@gmail", { preferLocal: true })
655
- .catch((err: Error) => ({ _error: err.message }));
656
- expect((result as { authorizeUrl?: string }).authorizeUrl).toBeUndefined();
657
- });
658
- });
659
-
660
491
  // ─── Registry auth lifecycle ─────────────────────────────────────
661
492
 
662
493
  describe("ADK registry auth lifecycle", () => {
@@ -1190,22 +1021,6 @@ describe("isRefAuthComplete + cached authFields", () => {
1190
1021
  expect(result).toBeNull();
1191
1022
  });
1192
1023
 
1193
- test("proxy mode short-circuits to true regardless of cache", async () => {
1194
- const { isRefAuthComplete } = await import("./config-store");
1195
- const result = isRefAuthComplete(
1196
- {
1197
- ref: "slash",
1198
- name: "slash",
1199
- scheme: "registry",
1200
- // proxy mode set by ref.add when registry inspection includes it.
1201
- // biome-ignore lint/suspicious/noExplicitAny: mode isn't on the public type
1202
- mode: "proxy",
1203
- } as any,
1204
- undefined,
1205
- );
1206
- expect(result).toBe(true);
1207
- });
1208
-
1209
1024
  test("required field present → true", async () => {
1210
1025
  const { isRefAuthComplete } = await import("./config-store");
1211
1026
  const result = isRefAuthComplete(
@@ -1307,5 +1122,61 @@ describe("isRefAuthComplete + cached authFields", () => {
1307
1122
  );
1308
1123
  expect(result).toBe(true);
1309
1124
  });
1125
+
1126
+ test("resolvableFields satisfies required, non-automated fields absent from config", async () => {
1127
+ // Scenario: registry-hosted OAuth where the platform injects
1128
+ // client_id / client_secret at runtime via resolveCredentials.
1129
+ // The registry sees them as user-provided (required + non-automated)
1130
+ // but the consumer environment satisfies them externally.
1131
+ const { isRefAuthComplete } = await import("./config-store");
1132
+ const result = isRefAuthComplete(
1133
+ {
1134
+ ref: "google-gmail",
1135
+ name: "google-gmail",
1136
+ scheme: "registry",
1137
+ config: {
1138
+ // client_id / client_secret missing — resolved from env vars.
1139
+ access_token: "tok",
1140
+ },
1141
+ },
1142
+ {
1143
+ ref: "google-gmail",
1144
+ fetchedAt: new Date().toISOString(),
1145
+ authFields: {
1146
+ client_id: { required: true, automated: false },
1147
+ client_secret: { required: true, automated: false },
1148
+ access_token: { required: true, automated: true },
1149
+ },
1150
+ },
1151
+ { resolvableFields: ["client_id", "client_secret"] },
1152
+ );
1153
+ expect(result).toBe(true);
1154
+ });
1155
+
1156
+ test("resolvableFields does not bypass missing fields it doesn't list", async () => {
1157
+ const { isRefAuthComplete } = await import("./config-store");
1158
+ const result = isRefAuthComplete(
1159
+ {
1160
+ ref: "@oauth",
1161
+ name: "@oauth",
1162
+ scheme: "https",
1163
+ url: "http://localhost",
1164
+ config: {
1165
+ client_id: "abc",
1166
+ // client_secret missing AND not listed as resolvable.
1167
+ },
1168
+ },
1169
+ {
1170
+ ref: "@oauth",
1171
+ fetchedAt: new Date().toISOString(),
1172
+ authFields: {
1173
+ client_id: { required: true, automated: false },
1174
+ client_secret: { required: true, automated: false },
1175
+ },
1176
+ },
1177
+ { resolvableFields: ["client_id"] },
1178
+ );
1179
+ expect(result).toBe(false);
1180
+ });
1310
1181
  });
1311
1182