@slashfi/agents-sdk 0.90.1 → 0.90.4

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/src/adk.ts CHANGED
@@ -74,19 +74,37 @@ function hasFlag(flag: string): boolean {
74
74
  return args.includes(flag);
75
75
  }
76
76
 
77
+ function getCredentialArgs(startIndex: number, excludeFlags = new Set<string>()): Record<string, string> {
78
+ const credentials: Record<string, string> = {};
79
+ for (let i = startIndex; i < args.length; i++) {
80
+ const arg = args[i];
81
+ if (!arg?.startsWith("--")) continue;
82
+ const key = arg.slice(2);
83
+ if (excludeFlags.has(key)) {
84
+ i++;
85
+ continue;
86
+ }
87
+ const value = args[i + 1];
88
+ if (value === undefined || value.startsWith("--")) continue;
89
+ credentials[key] = value;
90
+ i++;
91
+ }
92
+ return credentials;
93
+ }
94
+
77
95
  function wantsHelp(): boolean {
78
96
  return args.includes("--help") || args.includes("-h");
79
97
  }
80
98
 
81
99
  const HELP_SECTIONS: Record<string, string> = {
82
100
  registry: `Registry operations:
83
- adk registry add <url> --name <name> [--auth-type bearer|api-key|none]
101
+ adk registry add <url> --name <name> [--auth-type bearer|basic|api-key|none]
84
102
  adk registry remove <name>
85
103
  adk registry list
86
104
  adk registry browse <name> [--query <q>]
87
105
  adk registry inspect <name>
88
106
  adk registry test [name]
89
- adk registry auth <name> [--token <t>] [--api-key <k>] [--header <h>]`,
107
+ adk registry auth <name> [--token <t>] [--username <u> --password <p>] [--api-key <k>] [--header <h>]`,
90
108
  ref: `Ref operations:
91
109
  adk ref add <name> Install from default (public) registry
92
110
  adk ref add <name> --registry <reg> Install from a specific registry
@@ -123,18 +141,22 @@ Usage:
123
141
  adk search <query> [options] BM25 search over materialized refs/tools
124
142
  adk registry <op> [options] Manage registry connections
125
143
  adk ref <op> [options] Manage agent refs
144
+ adk list List configured refs
145
+ adk call <name> <tool> [params] Call a ref tool
146
+ adk auth <name> [--<field> <val>] Authenticate a ref
147
+ adk auth-status <name> Show ref auth status
126
148
  adk config-path Print config directory path
127
149
  adk error [id] View recent errors or a specific error
128
150
  adk version | --version | -v Print the installed adk SDK version
129
151
 
130
152
  Registry operations:
131
- adk registry add <url> --name <name> [--auth-type bearer|api-key|none]
153
+ adk registry add <url> --name <name> [--auth-type bearer|basic|api-key|none]
132
154
  adk registry remove <name>
133
155
  adk registry list
134
156
  adk registry browse <name> [--query <q>]
135
157
  adk registry inspect <name>
136
158
  adk registry test [name]
137
- adk registry auth <name> [--token <t>] [--api-key <k>] [--header <h>]
159
+ adk registry auth <name> [--token <t>] [--username <u> --password <p>] [--api-key <k>] [--header <h>]
138
160
 
139
161
  Ref operations:
140
162
  adk ref add <ref> [--name <name>] [--registry <name>] [--url <url>] [--scheme mcp|https|registry]
@@ -205,9 +227,9 @@ async function runRegistry() {
205
227
  console.error("Usage: adk registry add <url> --name <name>");
206
228
  process.exit(1);
207
229
  }
208
- const authType = getArg("--auth-type") as "bearer" | "api-key" | "none" | undefined;
230
+ const authType = getArg("--auth-type") as "bearer" | "basic" | "api-key" | "none" | undefined;
209
231
  const auth = authType && authType !== "none"
210
- ? { type: authType as "bearer" | "api-key" }
232
+ ? { type: authType as "bearer" | "basic" | "api-key" }
211
233
  : undefined;
212
234
  const displayName = name ?? new URL(url).hostname;
213
235
  const addResult = await adk.registry.add({
@@ -290,16 +312,20 @@ async function runRegistry() {
290
312
  }
291
313
  case "auth": {
292
314
  const name = args[2];
293
- if (!name) { console.error("Usage: adk registry auth <name> [--token <t>] [--api-key <k>] [--header <h>]"); process.exit(1); }
315
+ if (!name) { console.error("Usage: adk registry auth <name> [--token <t>] [--username <u> --password <p>] [--api-key <k>] [--header <h>]"); process.exit(1); }
294
316
  const token = getArg("--token");
317
+ const username = getArg("--username");
318
+ const password = getArg("--password");
295
319
  const apiKey = getArg("--api-key");
296
320
  const header = getArg("--header");
297
321
 
298
- // Explicit credential mode — user supplied a token/key directly.
299
- if (token || apiKey) {
322
+ // Explicit credential mode — user supplied a token/basic/key directly.
323
+ if (token || username || apiKey) {
300
324
  const credential = token
301
325
  ? { token }
302
- : { apiKey: apiKey!, ...(header && { header }) };
326
+ : username
327
+ ? { username, ...(password !== undefined && { password }) }
328
+ : { apiKey: apiKey!, ...(header && { header }) };
303
329
  const updated = await adk.registry.auth(name, credential);
304
330
  if (!updated) {
305
331
  console.error(`Registry not found: ${name}`);
@@ -500,13 +526,19 @@ async function runRef() {
500
526
  }
501
527
  case "auth": {
502
528
  const name = args[2];
503
- if (!name) { console.error("Usage: adk ref auth <name> [--api-key <key>]"); process.exit(1); }
529
+ if (!name) { console.error("Usage: adk ref auth <name> [--api-key <key>] [--<field> <value> ...]"); process.exit(1); }
504
530
  const apiKey = getArg("--api-key");
531
+ const credentials = getCredentialArgs(3, new Set(["api-key"]));
505
532
 
506
- if (apiKey) {
507
- const result = await adk.ref.auth(name, { apiKey });
533
+ if (apiKey || Object.keys(credentials).length > 0) {
534
+ const result = await adk.ref.auth(name, {
535
+ ...(apiKey && { apiKey }),
536
+ ...(Object.keys(credentials).length > 0 && { credentials }),
537
+ });
508
538
  if (result.complete) {
509
539
  console.log(`\x1b[32m\u2713\x1b[0m Auth complete for ${name} (${result.type})`);
540
+ } else if (result.fields?.length) {
541
+ console.log(JSON.stringify(result, null, 2));
510
542
  }
511
543
  break;
512
544
  }
@@ -662,6 +694,22 @@ switch (command) {
662
694
  }
663
695
  break;
664
696
  }
697
+ case "list":
698
+ args.splice(0, 1, "ref", "list");
699
+ await runRef();
700
+ break;
701
+ case "call":
702
+ args.splice(0, 1, "ref", "call");
703
+ await runRef();
704
+ break;
705
+ case "auth":
706
+ args.splice(0, 1, "ref", "auth");
707
+ await runRef();
708
+ break;
709
+ case "auth-status":
710
+ args.splice(0, 1, "ref", "auth-status");
711
+ await runRef();
712
+ break;
665
713
  case "registry":
666
714
  await runRegistry();
667
715
  break;
@@ -583,6 +583,104 @@ describe("ADK ref.call() full auto-refresh flow", () => {
583
583
  expect(cache.refs.math.authFields).toEqual({});
584
584
  });
585
585
 
586
+
587
+ test("ref http basic auth-status/auth/call/cache uses token with username/password parts", async () => {
588
+ let receivedAccessToken: string | undefined;
589
+ const basicTool = defineTool({
590
+ name: "get_profile",
591
+ description: "Requires HTTP Basic token",
592
+ inputSchema: { type: "object" as const, properties: {} },
593
+ execute: async (input: any) => {
594
+ receivedAccessToken = input?.accessToken;
595
+ return { ok: input?.accessToken === Buffer.from("ashby-key:", "utf8").toString("base64") };
596
+ },
597
+ });
598
+ const basicAgent = defineAgent({
599
+ path: "basic-api",
600
+ entrypoint: "Basic API agent",
601
+ tools: [basicTool],
602
+ visibility: "public",
603
+ config: {
604
+ security: { type: "http", scheme: "basic" },
605
+ },
606
+ });
607
+
608
+ const registry = createAgentRegistry();
609
+ registry.register(basicAgent);
610
+ const port = 19961;
611
+ const server = createAgentServer(registry, { port });
612
+ await server.start();
613
+ try {
614
+ const fs = createMemoryFs();
615
+ const adk = createAdk(fs, {
616
+ encryptionKey: "test-key-32-chars-long-enough!!",
617
+ });
618
+
619
+ await adk.registry.add({ name: "basic-reg", url: `http://localhost:${port}` });
620
+ await adk.ref.add({
621
+ ref: "basic-api",
622
+ name: "basic-api",
623
+ sourceRegistry: {
624
+ url: `http://localhost:${port}`,
625
+ agentPath: "basic-api",
626
+ },
627
+ });
628
+
629
+ const statusBefore = await adk.ref.authStatus("basic-api");
630
+ expect(statusBefore.complete).toBe(false);
631
+ expect(statusBefore.fields.token).toEqual({
632
+ required: true,
633
+ automated: false,
634
+ present: false,
635
+ resolvable: false,
636
+ format: "basic",
637
+ parts: [
638
+ { name: "username", label: "Username", secret: false },
639
+ { name: "password", label: "Password", secret: true, optional: true },
640
+ ],
641
+ });
642
+
643
+ let cacheRaw = await fs.readFile("registry-cache.json");
644
+ expect(cacheRaw).not.toBeNull();
645
+ let cache = JSON.parse(cacheRaw!) as { refs: Record<string, { authFields?: Record<string, unknown> }> };
646
+ expect(cache.refs["basic-api"].authFields).toEqual({
647
+ token: {
648
+ required: true,
649
+ automated: false,
650
+ format: "basic",
651
+ parts: [
652
+ { name: "username", label: "Username", secret: false },
653
+ { name: "password", label: "Password", secret: true, optional: true },
654
+ ],
655
+ },
656
+ });
657
+
658
+ const authResult = await adk.ref.auth("basic-api", {
659
+ credentials: { username: "ashby-key", password: "" },
660
+ });
661
+ expect(authResult.complete).toBe(true);
662
+
663
+ const configAfterAuth = await adk.readConfig();
664
+ const refEntry = configAfterAuth.refs?.find((r) => r.name === "basic-api");
665
+ expect(refEntry?.config?.token).toMatch(/^secret:/);
666
+
667
+ const statusAfter = await adk.ref.authStatus("basic-api");
668
+ expect(statusAfter.complete).toBe(true);
669
+ expect(statusAfter.fields.token.present).toBe(true);
670
+
671
+ cacheRaw = await fs.readFile("registry-cache.json");
672
+ cache = JSON.parse(cacheRaw!) as { refs: Record<string, { authFields?: Record<string, unknown> }> };
673
+ const { isRefAuthComplete } = await import("./config-store");
674
+ expect(isRefAuthComplete(refEntry!, cache.refs["basic-api"] as any)).toBe(true);
675
+
676
+ const callResult = await adk.ref.call("basic-api", "get_profile");
677
+ expect((callResult as any)?.result?.ok).toBe(true);
678
+ expect(receivedAccessToken).toBe(Buffer.from("ashby-key:", "utf8").toString("base64"));
679
+ } finally {
680
+ await server.stop();
681
+ }
682
+ });
683
+
586
684
  test("ref.authStatus maps form security to required access_token authFields", async () => {
587
685
  // Form security (e.g. databases) asks the user for structured
588
686
  // connection fields, but the ADK call path stores the encoded form
@@ -744,6 +842,258 @@ describe("ADK ref.call() full auto-refresh flow", () => {
744
842
  expect(status.fields?.access_token?.automated).toBe(false);
745
843
  expect(status.fields?.access_token?.present).toBe(false);
746
844
  });
845
+
846
+ test("ref.authStatus honors registry-declared authFields over oauth2 heuristics", async () => {
847
+ const platformRegPort = 19924;
848
+ const platformTool = defineTool({
849
+ name: "get_data",
850
+ description: "Platform minted bearer",
851
+ inputSchema: { type: "object" as const, properties: {} },
852
+ execute: async () => ({ ok: true }),
853
+ });
854
+ const platformAgent = defineAgent({
855
+ path: "platform-api",
856
+ entrypoint: "Platform bearer agent",
857
+ tools: [platformTool],
858
+ visibility: "public",
859
+ config: {
860
+ security: {
861
+ type: "oauth2",
862
+ flows: {
863
+ authorizationCode: {
864
+ authorizationUrl: "http://localhost/authorize",
865
+ tokenUrl: "http://localhost/token",
866
+ },
867
+ },
868
+ authFields: {
869
+ access_token: { required: true, automated: true },
870
+ },
871
+ },
872
+ },
873
+ });
874
+ const platformRegistry = createAgentRegistry();
875
+ platformRegistry.register(platformAgent);
876
+ const platformServer = createAgentServer(platformRegistry, {
877
+ port: platformRegPort,
878
+ });
879
+ await platformServer.start();
880
+
881
+ try {
882
+ const fs = createMemoryFs();
883
+ const adk = createAdk(fs, {
884
+ resolveCredentials: async ({ field }) =>
885
+ field === "access_token" ? "minted" : null,
886
+ });
887
+
888
+ await adk.registry.add({
889
+ name: "platform-reg",
890
+ url: `http://localhost:${platformRegPort}`,
891
+ });
892
+ await adk.ref.add({
893
+ ref: "platform-api",
894
+ name: "platform-api",
895
+ sourceRegistry: {
896
+ url: `http://localhost:${platformRegPort}`,
897
+ agentPath: "platform-api",
898
+ },
899
+ config: {},
900
+ });
901
+
902
+ const status = await adk.ref.authStatus("platform-api");
903
+ expect(status.fields?.access_token?.automated).toBe(true);
904
+ expect(status.fields?.access_token?.resolvable).toBe(true);
905
+ expect(status.complete).toBe(true);
906
+
907
+ const cacheRaw = await fs.readFile("registry-cache.json");
908
+ const cache = JSON.parse(cacheRaw!) as {
909
+ refs: Record<string, { authFields?: Record<string, unknown> }>;
910
+ };
911
+ expect(cache.refs["platform-api"].authFields).toEqual({
912
+ access_token: { required: true, automated: true },
913
+ });
914
+ } finally {
915
+ await platformServer.stop();
916
+ }
917
+ });
918
+ });
919
+
920
+ describe("ADK ref.call() resolveCredentials fallback", () => {
921
+ let registryServer: AgentServer;
922
+ const REG_PORT = 19922;
923
+ let receivedToken: string | undefined;
924
+
925
+ beforeAll(async () => {
926
+ const apiTool = defineTool({
927
+ name: "get_data",
928
+ description: "Echo accessToken",
929
+ inputSchema: { type: "object" as const, properties: {} },
930
+ execute: async (input: any) => {
931
+ receivedToken = input?.accessToken;
932
+ if (!receivedToken) {
933
+ return {
934
+ content: [{ type: "text", text: '{"error":"401 Unauthorized"}' }],
935
+ _httpStatus: 401,
936
+ };
937
+ }
938
+ return { ok: true, token: receivedToken };
939
+ },
940
+ });
941
+
942
+ const agent = defineAgent({
943
+ path: "resolved-api",
944
+ entrypoint: "Resolved creds agent",
945
+ tools: [apiTool],
946
+ visibility: "public",
947
+ config: {
948
+ security: {
949
+ type: "oauth2",
950
+ flows: {
951
+ authorizationCode: {
952
+ authorizationUrl: "http://localhost/authorize",
953
+ tokenUrl: "http://localhost/token",
954
+ },
955
+ },
956
+ },
957
+ },
958
+ });
959
+
960
+ const registry = createAgentRegistry();
961
+ registry.register(agent);
962
+ registryServer = createAgentServer(registry, { port: REG_PORT });
963
+ await registryServer.start();
964
+ });
965
+
966
+ afterAll(async () => {
967
+ await registryServer.stop();
968
+ });
969
+
970
+ test("uses resolveCredentials when access_token is not stored", async () => {
971
+ receivedToken = undefined;
972
+ const fs = createMemoryFs();
973
+ const adk = createAdk(fs, {
974
+ resolveCredentials: async ({ field }) => {
975
+ if (field === "access_token") return "minted-call-token";
976
+ return null;
977
+ },
978
+ });
979
+
980
+ await adk.registry.add({
981
+ name: "resolved-reg",
982
+ url: `http://localhost:${REG_PORT}`,
983
+ });
984
+ await adk.ref.add({
985
+ ref: "resolved-api",
986
+ sourceRegistry: {
987
+ url: `http://localhost:${REG_PORT}`,
988
+ agentPath: "resolved-api",
989
+ },
990
+ config: {},
991
+ });
992
+
993
+ const result = await adk.ref.call("resolved-api", "get_data");
994
+ expect(receivedToken).toBe("minted-call-token");
995
+ expect((result as any)?.result?.ok).toBe(true);
996
+ });
997
+
998
+ test("stored access_token still wins over resolveCredentials", async () => {
999
+ receivedToken = undefined;
1000
+ const fs = createMemoryFs();
1001
+ const adk = createAdk(fs, {
1002
+ resolveCredentials: async () => "should-not-be-used",
1003
+ });
1004
+
1005
+ await adk.registry.add({
1006
+ name: "resolved-reg",
1007
+ url: `http://localhost:${REG_PORT}`,
1008
+ });
1009
+ await adk.ref.add({
1010
+ ref: "resolved-api",
1011
+ name: "resolved-api-stored",
1012
+ sourceRegistry: {
1013
+ url: `http://localhost:${REG_PORT}`,
1014
+ agentPath: "resolved-api",
1015
+ },
1016
+ config: { access_token: "stored-token" },
1017
+ });
1018
+
1019
+ await adk.ref.call("resolved-api-stored", "get_data");
1020
+ expect(receivedToken).toBe("stored-token");
1021
+ });
1022
+
1023
+ test("uses resolveCredentials for cached apiKey header fields", async () => {
1024
+ let receivedHeaders: Record<string, string> | undefined;
1025
+ const headerRegPort = 19923;
1026
+ const headerServer = createAgentServer(
1027
+ (() => {
1028
+ const apiTool = defineTool({
1029
+ name: "get_data",
1030
+ description: "Echo _headers",
1031
+ inputSchema: { type: "object" as const, properties: {} },
1032
+ execute: async (input: any) => {
1033
+ receivedHeaders = input?._headers;
1034
+ return { ok: true, headers: receivedHeaders };
1035
+ },
1036
+ });
1037
+ const agent = defineAgent({
1038
+ path: "header-api",
1039
+ entrypoint: "Header creds agent",
1040
+ tools: [apiTool],
1041
+ visibility: "public",
1042
+ });
1043
+ const registry = createAgentRegistry();
1044
+ registry.register(agent);
1045
+ return registry;
1046
+ })(),
1047
+ { port: headerRegPort },
1048
+ );
1049
+ await headerServer.start();
1050
+
1051
+ try {
1052
+ receivedHeaders = undefined;
1053
+ const fs = createMemoryFs();
1054
+ const adk = createAdk(fs, {
1055
+ resolveCredentials: async ({ field }) => {
1056
+ if (field === "x_api_key") return "resolved-header-key";
1057
+ return null;
1058
+ },
1059
+ });
1060
+
1061
+ await adk.registry.add({
1062
+ name: "header-reg",
1063
+ url: `http://localhost:${headerRegPort}`,
1064
+ });
1065
+ await adk.ref.add({
1066
+ ref: "header-api",
1067
+ name: "header-api",
1068
+ sourceRegistry: {
1069
+ url: `http://localhost:${headerRegPort}`,
1070
+ agentPath: "header-api",
1071
+ },
1072
+ config: {},
1073
+ });
1074
+
1075
+ await fs.writeFile(
1076
+ "registry-cache.json",
1077
+ JSON.stringify({
1078
+ refs: {
1079
+ "header-api": {
1080
+ ref: "header-api",
1081
+ fetchedAt: new Date().toISOString(),
1082
+ authFields: {
1083
+ x_api_key: { required: true, automated: false },
1084
+ },
1085
+ },
1086
+ },
1087
+ }),
1088
+ );
1089
+
1090
+ const result = await adk.ref.call("header-api", "get_data");
1091
+ expect(receivedHeaders?.["X-API-KEY"]).toBe("resolved-header-key");
1092
+ expect((result as any)?.result?.ok).toBe(true);
1093
+ } finally {
1094
+ await headerServer.stop();
1095
+ }
1096
+ });
747
1097
  });
748
1098
 
749
1099
  describe("ADK ref.call() auto-refresh on direct MCP 401", () => {