@slashfi/agents-sdk 0.77.0 → 0.77.2

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.
Files changed (59) hide show
  1. package/dist/adk-tools.d.ts +2 -2
  2. package/dist/adk-tools.d.ts.map +1 -1
  3. package/dist/adk-tools.js +258 -118
  4. package/dist/adk-tools.js.map +1 -1
  5. package/dist/adk.js +7 -9
  6. package/dist/adk.js.map +1 -1
  7. package/dist/agent-definitions/config.d.ts.map +1 -1
  8. package/dist/agent-definitions/config.js +12 -14
  9. package/dist/agent-definitions/config.js.map +1 -1
  10. package/dist/cjs/adk-tools.js +258 -118
  11. package/dist/cjs/adk-tools.js.map +1 -1
  12. package/dist/cjs/agent-definitions/config.js +12 -14
  13. package/dist/cjs/agent-definitions/config.js.map +1 -1
  14. package/dist/cjs/config-store.js +34 -17
  15. package/dist/cjs/config-store.js.map +1 -1
  16. package/dist/cjs/define-config.js +5 -7
  17. package/dist/cjs/define-config.js.map +1 -1
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/materialize.js +1 -1
  20. package/dist/cjs/materialize.js.map +1 -1
  21. package/dist/cjs/registry-consumer.js +1 -1
  22. package/dist/cjs/registry.js +33 -2
  23. package/dist/cjs/registry.js.map +1 -1
  24. package/dist/cjs/types.js.map +1 -1
  25. package/dist/config-store.d.ts +3 -3
  26. package/dist/config-store.d.ts.map +1 -1
  27. package/dist/config-store.js +34 -17
  28. package/dist/config-store.js.map +1 -1
  29. package/dist/define-config.d.ts +11 -18
  30. package/dist/define-config.d.ts.map +1 -1
  31. package/dist/define-config.js +5 -7
  32. package/dist/define-config.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/materialize.js +1 -1
  37. package/dist/materialize.js.map +1 -1
  38. package/dist/registry-consumer.d.ts +1 -1
  39. package/dist/registry-consumer.js +1 -1
  40. package/dist/registry.d.ts.map +1 -1
  41. package/dist/registry.js +33 -2
  42. package/dist/registry.js.map +1 -1
  43. package/dist/types.d.ts +5 -3
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/types.js.map +1 -1
  46. package/dist/validate.d.ts +8 -8
  47. package/package.json +1 -1
  48. package/src/adk-tools.ts +289 -127
  49. package/src/adk.ts +7 -8
  50. package/src/agent-definitions/config.ts +15 -16
  51. package/src/config-store.ts +43 -19
  52. package/src/consumer.test.ts +7 -7
  53. package/src/define-config.ts +11 -20
  54. package/src/index.ts +1 -0
  55. package/src/materialize.ts +1 -1
  56. package/src/ref-naming.test.ts +164 -91
  57. package/src/registry-consumer.ts +1 -1
  58. package/src/registry.ts +40 -2
  59. package/src/types.ts +21 -6
package/src/adk-tools.ts CHANGED
@@ -17,10 +17,252 @@
17
17
  * ```
18
18
  */
19
19
 
20
+ import { z } from "zod";
21
+ import { AdkError } from "./adk-error.js";
22
+ import { zodToOpenAiJsonSchema } from "./call-agent-schema.js";
20
23
  import type { Adk } from "./config-store.js";
21
24
  import type { RefEntry, RegistryEntry } from "./define-config.js";
22
25
  import { defineTool } from "./define.js";
23
- import type { ToolContext, ToolDefinition } from "./types.js";
26
+ import type { JsonSchema, ToolContext, ToolDefinition } from "./types.js";
27
+
28
+ const objectRecordSchema = z.record(z.unknown());
29
+ const sourceRegistrySchema = z
30
+ .object({
31
+ url: z.string().min(1).describe("Registry MCP URL."),
32
+ agentPath: z.string().optional().describe("Agent path on that registry."),
33
+ })
34
+ .passthrough();
35
+ const refScopeSchema = z
36
+ .string()
37
+ .optional()
38
+ .describe("Config scope to operate on.");
39
+ const refNameSchema = z.string().min(1).describe("Local connection name.");
40
+
41
+ const refAddOperationSchema = z
42
+ .object({
43
+ operation: z.literal("add"),
44
+ scope: refScopeSchema,
45
+ ref: z
46
+ .string()
47
+ .min(1)
48
+ .optional()
49
+ .describe(
50
+ "Canonical agent path, e.g. 'google-calendar'. Defaults to name when omitted.",
51
+ ),
52
+ name: refNameSchema
53
+ .optional()
54
+ .describe("Local connection name. Defaults to ref when omitted."),
55
+ scheme: z
56
+ .enum(["registry", "mcp", "https"])
57
+ .optional()
58
+ .describe(
59
+ "Connection type. Usually inferred from sourceRegistry or url.",
60
+ ),
61
+ url: z
62
+ .string()
63
+ .min(1)
64
+ .optional()
65
+ .describe("Direct MCP/HTTPS URL. Required for direct mcp/https refs."),
66
+ sourceRegistry: sourceRegistrySchema
67
+ .optional()
68
+ .describe(
69
+ "Registry that serves this agent. Required for registry-backed refs.",
70
+ ),
71
+ config: objectRecordSchema
72
+ .optional()
73
+ .describe("Optional per-instance config."),
74
+ })
75
+ .passthrough()
76
+ .superRefine((input, ctx) => {
77
+ if (!input.ref && !input.name) {
78
+ ctx.addIssue({
79
+ code: z.ZodIssueCode.custom,
80
+ path: ["ref"],
81
+ message: "Either ref or name is required.",
82
+ });
83
+ }
84
+ if (input.scheme === "registry" && !input.sourceRegistry?.url) {
85
+ ctx.addIssue({
86
+ code: z.ZodIssueCode.custom,
87
+ path: ["sourceRegistry", "url"],
88
+ message: "scheme=registry requires sourceRegistry.url.",
89
+ });
90
+ }
91
+ if ((input.scheme === "mcp" || input.scheme === "https") && !input.url) {
92
+ ctx.addIssue({
93
+ code: z.ZodIssueCode.custom,
94
+ path: ["url"],
95
+ message: `scheme=${input.scheme} requires url.`,
96
+ });
97
+ }
98
+ if (!input.url && !input.sourceRegistry?.url) {
99
+ ctx.addIssue({
100
+ code: z.ZodIssueCode.custom,
101
+ path: ["sourceRegistry"],
102
+ message:
103
+ "Connection target is required: provide sourceRegistry.url for a registry ref, or url for a direct mcp/https ref.",
104
+ });
105
+ }
106
+ });
107
+
108
+ const refOperationSchemas = {
109
+ add: refAddOperationSchema,
110
+ remove: z
111
+ .object({
112
+ operation: z.literal("remove"),
113
+ scope: refScopeSchema,
114
+ name: refNameSchema,
115
+ })
116
+ .passthrough(),
117
+ list: z
118
+ .object({ operation: z.literal("list"), scope: refScopeSchema })
119
+ .passthrough(),
120
+ update: z
121
+ .object({
122
+ operation: z.literal("update"),
123
+ scope: refScopeSchema,
124
+ name: refNameSchema,
125
+ ref: z.string().optional(),
126
+ scheme: z.enum(["registry", "mcp", "https"]).optional(),
127
+ url: z.string().optional(),
128
+ sourceRegistry: sourceRegistrySchema.optional(),
129
+ config: objectRecordSchema.optional(),
130
+ })
131
+ .passthrough(),
132
+ inspect: z
133
+ .object({
134
+ operation: z.literal("inspect"),
135
+ scope: refScopeSchema,
136
+ name: refNameSchema,
137
+ full: z.boolean().optional(),
138
+ })
139
+ .passthrough(),
140
+ call: z
141
+ .object({
142
+ operation: z.literal("call"),
143
+ scope: refScopeSchema,
144
+ name: refNameSchema,
145
+ tool: z.string().min(1),
146
+ params: objectRecordSchema.optional(),
147
+ })
148
+ .passthrough(),
149
+ auth: z
150
+ .object({
151
+ operation: z.literal("auth"),
152
+ scope: refScopeSchema,
153
+ name: refNameSchema,
154
+ ref: z.string().optional(),
155
+ apiKey: z.string().optional(),
156
+ credentials: z.record(z.string()).optional(),
157
+ sourceRegistry: sourceRegistrySchema.optional(),
158
+ })
159
+ .passthrough(),
160
+ "auth-status": z
161
+ .object({
162
+ operation: z.literal("auth-status"),
163
+ scope: refScopeSchema,
164
+ name: refNameSchema,
165
+ })
166
+ .passthrough(),
167
+ "refresh-token": z
168
+ .object({
169
+ operation: z.literal("refresh-token"),
170
+ scope: refScopeSchema,
171
+ name: refNameSchema,
172
+ })
173
+ .passthrough(),
174
+ resources: z
175
+ .object({
176
+ operation: z.literal("resources"),
177
+ scope: refScopeSchema,
178
+ name: refNameSchema,
179
+ })
180
+ .passthrough(),
181
+ read: z
182
+ .object({
183
+ operation: z.literal("read"),
184
+ scope: refScopeSchema,
185
+ name: refNameSchema,
186
+ uris: z.array(z.string()),
187
+ })
188
+ .passthrough(),
189
+ } as const;
190
+
191
+ const refToolInputSchema = z.union([
192
+ refOperationSchemas.add,
193
+ refOperationSchemas.remove,
194
+ refOperationSchemas.list,
195
+ refOperationSchemas.update,
196
+ refOperationSchemas.inspect,
197
+ refOperationSchemas.call,
198
+ refOperationSchemas.auth,
199
+ refOperationSchemas["auth-status"],
200
+ refOperationSchemas["refresh-token"],
201
+ refOperationSchemas.resources,
202
+ refOperationSchemas.read,
203
+ ]);
204
+ const refToolInputJsonSchema = zodToOpenAiJsonSchema(
205
+ refToolInputSchema,
206
+ ) as JsonSchema;
207
+
208
+ function parseRefToolInput(
209
+ input: Record<string, unknown>,
210
+ ): Record<string, unknown> {
211
+ const op = typeof input.operation === "string" ? input.operation : undefined;
212
+ const schema =
213
+ op && op in refOperationSchemas
214
+ ? refOperationSchemas[op as keyof typeof refOperationSchemas]
215
+ : refToolInputSchema;
216
+ const result = schema.safeParse(input);
217
+ if (result.success) return result.data as Record<string, unknown>;
218
+
219
+ const operation = op ? `ref.${op}` : "ref";
220
+ throw new AdkError({
221
+ code: "TOOL_INPUT_INVALID",
222
+ message: `Invalid ${operation} input`,
223
+ hint: "The expected input schema is serialized in details.schema; operation-specific schema is in details.operationSchema.",
224
+ details: {
225
+ operation,
226
+ issues: result.error.issues.map((issue) => ({
227
+ path: issue.path.join("."),
228
+ message: issue.message,
229
+ })),
230
+ received: input,
231
+ schema: refToolInputJsonSchema,
232
+ ...(op &&
233
+ op in refOperationSchemas && {
234
+ operationSchema: zodToOpenAiJsonSchema(
235
+ refOperationSchemas[op as keyof typeof refOperationSchemas],
236
+ ),
237
+ }),
238
+ },
239
+ });
240
+ }
241
+
242
+ function withScopeSchema(
243
+ schema: JsonSchema,
244
+ scopeSchema: JsonSchema,
245
+ ): JsonSchema {
246
+ const clone = JSON.parse(JSON.stringify(schema)) as JsonSchema;
247
+ const visit = (value: unknown) => {
248
+ if (!value || typeof value !== "object") return;
249
+ if (Array.isArray(value)) {
250
+ for (const item of value) visit(item);
251
+ return;
252
+ }
253
+
254
+ const record = value as Record<string, unknown>;
255
+ const properties = record.properties as Record<string, unknown> | undefined;
256
+ if (properties?.scope) {
257
+ properties.scope = scopeSchema;
258
+ }
259
+ for (const child of Object.values(record)) {
260
+ visit(child);
261
+ }
262
+ };
263
+ visit(clone);
264
+ return clone;
265
+ }
24
266
 
25
267
  export interface AdkToolsHooks<TCtx extends ToolContext = ToolContext> {
26
268
  /**
@@ -30,10 +272,11 @@ export interface AdkToolsHooks<TCtx extends ToolContext = ToolContext> {
30
272
  *
31
273
  * @example
32
274
  * ```ts
33
- * getAuthStateContext: async (ctx) => ({ tid: ctx.tenantId, uid: ctx.userId })
275
+ * getAuthStateContext: async (input, ctx) => ({ tid: ctx.tenantId, uid: ctx.userId, name: input.name })
34
276
  * ```
35
277
  */
36
278
  getAuthStateContext?: (
279
+ input: Record<string, unknown>,
37
280
  ctx: TCtx,
38
281
  ) => Record<string, unknown> | Promise<Record<string, unknown>>;
39
282
  }
@@ -66,154 +309,68 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
66
309
  const refTool = defineTool({
67
310
  name: "ref",
68
311
  description:
69
- "Manage agent refs. Operations: add, remove, list, update, inspect, call, auth, auth-status, refresh-token, resources, read. For `add`, supply `ref` (canonical agent path, e.g. 'notion') and optionally `name` for a local alias; either one uniquely identifies the ref. For every other operation, pass `name` (the local identifier you used on add defaults to `ref` when you didn't set it explicitly).",
70
- inputSchema: {
71
- type: "object" as const,
72
- properties: {
73
- operation: {
74
- type: "string",
75
- enum: [
76
- "add",
77
- "remove",
78
- "list",
79
- "update",
80
- "inspect",
81
- "call",
82
- "auth",
83
- "auth-status",
84
- "refresh-token",
85
- "resources",
86
- "read",
87
- ],
88
- },
89
- scope: scopeSchema,
90
- ref: {
91
- type: "string",
92
- description:
93
- "Canonical agent path on the remote registry (e.g. 'notion', 'linear', 'github'). Used by `add` to identify which agent definition to connect to. Other operations use `name` instead. If you call `add` with only `name` and no `ref`, `ref` defaults to `name`.",
94
- },
95
- name: {
96
- type: "string",
97
- description:
98
- "Local identifier for this ref, used by all operations except `add` to look up the entry. On `add`, `name` is optional — it only needs to differ from `ref` when you want multiple local instances of the same agent (e.g. `{ ref: 'notion', name: 'work-notion' }`). Omit it and the ref is identified by its canonical path.",
99
- },
100
- scheme: {
101
- type: "string",
102
- description:
103
- "Connection scheme: 'mcp' (direct MCP server), 'https' (REST proxy), or 'registry' (discovered via a registry). Auto-inferred from `url` or `sourceRegistry` when omitted.",
104
- },
105
- url: {
106
- type: "string",
107
- description:
108
- "Direct URL to the agent (e.g. https://mcp.notion.com/mcp). Required for 'mcp' and 'https' schemes.",
109
- },
110
- sourceRegistry: {
111
- type: "object",
112
- properties: {
113
- url: { type: "string" },
114
- agentPath: { type: "string" },
115
- },
116
- description:
117
- "When scheme is 'registry', the registry + agent path to resolve through.",
118
- },
119
- config: {
120
- type: "object",
121
- description:
122
- "Per-instance config passed to the agent (headers, credentials, etc.). Supports `{{secret-uri}}` templates.",
123
- },
124
- tool: {
125
- type: "string",
126
- description:
127
- "For `call` operation: the tool name on the ref to invoke.",
128
- },
129
- params: {
130
- type: "object",
131
- description: "For `call` operation: arguments to pass to the tool.",
132
- },
133
- full: {
134
- type: "boolean",
135
- description:
136
- "For `inspect` operation: include full agent definition.",
137
- },
138
- uris: {
139
- type: "array",
140
- items: { type: "string" },
141
- description: "For `read` operation: the resource URIs to read.",
142
- },
143
- apiKey: {
144
- type: "string",
145
- description: "For `auth` operation: pre-provisioned API key.",
146
- },
147
- credentials: {
148
- type: "object",
149
- description:
150
- "For `auth` operation: key-value map of credential fields (keys match field names from the auth challenge).",
151
- },
152
- },
153
- required: ["operation"],
154
- },
312
+ "Manage agent refs. Operations: add, remove, list, update, inspect, call, auth, auth-status, refresh-token, resources, read. For `add`, supply `ref` (canonical agent path, e.g. 'notion') and `name` (local identifier). If `name` is omitted on add, it defaults to `ref`. For every other operation, pass `name`.",
313
+ inputSchema: withScopeSchema(refToolInputJsonSchema, scopeSchema),
155
314
  execute: async (input: Record<string, unknown>, ctx) => {
315
+ const parsedInput = parseRefToolInput(input);
156
316
  const adk = await resolveScope(
157
- input.scope as string | undefined,
317
+ parsedInput.scope as string | undefined,
158
318
  ctx as TCtx,
159
319
  );
160
- const op = input.operation as string;
320
+ const op = parsedInput.operation as string;
161
321
 
162
322
  switch (op) {
163
323
  case "add": {
164
324
  // Accept `ref` or `name` (or both). If only one is given, the
165
- // other defaults to it. This matches the "Add a ref called X"
166
- // natural-language phrasing LLMs that pick `name` get the
167
- // same behavior as ones that pick `ref`, eliminating
168
- // non-determinism on the identifier field. Throws when both
169
- // are missing, so misuse is loud instead of silently storing
170
- // `{ ref: undefined }`.
171
- const refValue = (input.ref ?? input.name) as string | undefined;
325
+ // other defaults to it. The stored entry always has an explicit
326
+ // `name`, so downstream auth/callback state can distinguish the
327
+ // canonical ref from the local connection handle.
328
+ const refValue = (parsedInput.ref ?? parsedInput.name) as
329
+ | string
330
+ | undefined;
172
331
  if (!refValue) {
173
332
  throw new Error(
174
333
  "ref.add: must supply either 'ref' (canonical agent path) or 'name' (local identifier); both may be the same string for the common single-instance case.",
175
334
  );
176
335
  }
177
- const entry: Record<string, unknown> = { ref: refValue };
178
- if (input.scheme) entry.scheme = input.scheme;
179
- if (input.url) entry.url = input.url;
180
- // Only store `name` when it's meaningfully different from
181
- // `ref` (the multi-instance aliasing case). Avoids a
182
- // redundant `{ name: 'notion', ref: 'notion' }` stored shape.
183
- const nameValue = input.name as string | undefined;
184
- if (nameValue && nameValue !== refValue) {
185
- entry.name = nameValue;
186
- }
187
- if (input.sourceRegistry) entry.sourceRegistry = input.sourceRegistry;
188
- if (input.config) entry.config = input.config;
336
+ const nameValue = (parsedInput.name ?? refValue) as string;
337
+ const entry: Record<string, unknown> = {
338
+ ref: refValue,
339
+ name: nameValue,
340
+ };
341
+ if (parsedInput.scheme) entry.scheme = parsedInput.scheme;
342
+ if (parsedInput.url) entry.url = parsedInput.url;
343
+ if (parsedInput.sourceRegistry)
344
+ entry.sourceRegistry = parsedInput.sourceRegistry;
345
+ if (parsedInput.config) entry.config = parsedInput.config;
189
346
  const { security } = await adk.ref.add(entry as unknown as RefEntry);
190
347
  return {
191
348
  added: true,
192
349
  ref: refValue,
193
- name: (entry.name ?? refValue) as string,
350
+ name: nameValue,
194
351
  security,
195
352
  };
196
353
  }
197
354
  case "remove":
198
- return { removed: await adk.ref.remove(input.name as string) };
355
+ return { removed: await adk.ref.remove(parsedInput.name as string) };
199
356
  case "list":
200
357
  return { refs: await adk.ref.list() };
201
358
  case "update":
202
359
  return {
203
360
  updated: await adk.ref.update(
204
- input.name as string,
205
- input as unknown as Partial<RefEntry>,
361
+ parsedInput.name as string,
362
+ parsedInput as unknown as Partial<RefEntry>,
206
363
  ),
207
364
  };
208
365
  case "inspect":
209
- return await adk.ref.inspect(input.name as string, {
210
- full: input.full as boolean,
366
+ return await adk.ref.inspect(parsedInput.name as string, {
367
+ full: parsedInput.full as boolean,
211
368
  });
212
369
  case "call":
213
370
  return await adk.ref.call(
214
- input.name as string,
215
- input.tool as string,
216
- input.params as Record<string, unknown>,
371
+ parsedInput.name as string,
372
+ parsedInput.tool as string,
373
+ parsedInput.params as Record<string, unknown>,
217
374
  );
218
375
  case "auth": {
219
376
  const authOpts: {
@@ -221,26 +378,31 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
221
378
  credentials?: Record<string, string>;
222
379
  stateContext?: Record<string, unknown>;
223
380
  } = {};
224
- if (input.apiKey) authOpts.apiKey = input.apiKey as string;
225
- if (input.credentials)
226
- authOpts.credentials = input.credentials as Record<string, string>;
381
+ if (parsedInput.apiKey)
382
+ authOpts.apiKey = parsedInput.apiKey as string;
383
+ if (parsedInput.credentials)
384
+ authOpts.credentials = parsedInput.credentials as Record<
385
+ string,
386
+ string
387
+ >;
227
388
  if (opts.hooks?.getAuthStateContext) {
228
389
  authOpts.stateContext = await opts.hooks.getAuthStateContext(
390
+ parsedInput,
229
391
  ctx as TCtx,
230
392
  );
231
393
  }
232
- return await adk.ref.auth(input.name as string, authOpts);
394
+ return await adk.ref.auth(parsedInput.name as string, authOpts);
233
395
  }
234
396
  case "auth-status":
235
- return await adk.ref.authStatus(input.name as string);
397
+ return await adk.ref.authStatus(parsedInput.name as string);
236
398
  case "refresh-token":
237
- return await adk.ref.refreshToken(input.name as string);
399
+ return await adk.ref.refreshToken(parsedInput.name as string);
238
400
  case "resources":
239
- return await adk.ref.resources(input.name as string);
401
+ return await adk.ref.resources(parsedInput.name as string);
240
402
  case "read":
241
403
  return await adk.ref.read(
242
- input.name as string,
243
- input.uris as string[],
404
+ parsedInput.name as string,
405
+ parsedInput.uris as string[],
244
406
  );
245
407
  default:
246
408
  throw new Error(`Unknown ref operation: ${op}`);
package/src/adk.ts CHANGED
@@ -110,7 +110,7 @@ Registry operations:
110
110
  adk registry auth <name> [--token <t>] [--api-key <k>] [--header <h>]
111
111
 
112
112
  Ref operations:
113
- adk ref add <ref> [--registry <name>] [--as <alias>] [--url <url>] [--scheme mcp|https|registry]
113
+ adk ref add <ref> [--name <name>] [--registry <name>] [--url <url>] [--scheme mcp|https|registry]
114
114
  adk ref remove <name>
115
115
  adk ref list
116
116
  adk ref inspect <name> [--full]
@@ -351,15 +351,14 @@ async function runRef() {
351
351
  switch (op) {
352
352
  case "add": {
353
353
  const refArg = args[2];
354
- if (!refArg) { console.error("Usage: adk ref add <ref> [--registry <name>] [--as <alias>]"); process.exit(1); }
355
- const entry: Record<string, unknown> = { ref: refArg };
356
- const alias = getArg("--as");
354
+ if (!refArg) { console.error("Usage: adk ref add <ref> [--name <name>] [--registry <name>]"); process.exit(1); }
355
+ const name = getArg("--name") ?? refArg;
356
+ const entry: Record<string, unknown> = { ref: refArg, name };
357
357
  const url = getArg("--url");
358
358
  const registryName = getArg("--registry");
359
359
  // Auto-detect: if no --registry and no --url, try default registry
360
360
  const effectiveRegistry = registryName ?? (url ? undefined : "public");
361
361
  const scheme = getArg("--scheme") ?? (effectiveRegistry ? "registry" : undefined);
362
- if (alias) entry.as = alias;
363
362
  if (url) entry.url = url;
364
363
  if (scheme) entry.scheme = scheme;
365
364
  if (effectiveRegistry) {
@@ -370,15 +369,15 @@ async function runRef() {
370
369
  }
371
370
  try {
372
371
  const { security } = await adk.ref.add(entry as import("./define-config.js").RefEntry);
373
- console.log(`Added ref: ${alias ?? refArg}`);
372
+ console.log(`Added ref: ${name}`);
374
373
  if (security && security.type !== "none") {
375
374
  console.log(`\n Auth required: ${security.type}`);
376
- console.log(` Run: adk ref auth ${alias ?? refArg}`);
375
+ console.log(` Run: adk ref auth ${name}`);
377
376
  }
378
377
 
379
378
  // Materialize local docs
380
379
  const configDir = process.env.ADK_CONFIG_DIR ?? join(homedir(), ".adk");
381
- const refDisplayName = alias ?? refArg;
380
+ const refDisplayName = name;
382
381
  try {
383
382
  const result = await materializeRef(adk, refDisplayName, configDir);
384
383
  if (result.toolCount > 0) {
@@ -108,9 +108,9 @@ export function createConfigAgent(
108
108
  type: "string",
109
109
  description: 'Agent ref name (e.g. "notion", "linear")',
110
110
  },
111
- as: {
111
+ name: {
112
112
  type: "string",
113
- description: "Local alias (for multi-instance refs)",
113
+ description: "Local ref name. Defaults to ref when omitted.",
114
114
  },
115
115
  url: {
116
116
  type: "string",
@@ -132,7 +132,7 @@ export function createConfigAgent(
132
132
  execute: async (
133
133
  input: {
134
134
  ref: string;
135
- as?: string;
135
+ name?: string;
136
136
  url?: string;
137
137
  config?: Record<string, string>;
138
138
  registry?: string;
@@ -141,28 +141,27 @@ export function createConfigAgent(
141
141
  ) => {
142
142
  const fs = getStore(ctx);
143
143
  const currentConfig = await readConfig(fs);
144
+ const name = input.name ?? input.ref;
144
145
 
145
- const entry: RefEntry = {
146
+ const entry = {
146
147
  ref: input.ref,
147
- ...(input.as && { as: input.as }),
148
+ name,
148
149
  ...(input.url && { url: input.url }),
149
150
  ...(input.config && { config: input.config }),
150
151
  ...(input.registry && { registry: input.registry }),
151
- };
152
+ } as RefEntry;
152
153
 
153
- // Upsert: find existing ref by name/alias, replace or append
154
- const name = input.as ?? input.ref;
155
154
  const refs = currentConfig.refs ?? [];
156
- const existingIdx = refs.findIndex((r) => {
155
+ const altName = name.startsWith("@") ? name.slice(1) : `@${name}`;
156
+ const duplicate = refs.some((r) => {
157
157
  const normalized = normalizeRef(r);
158
- return normalized.name === name;
158
+ return normalized.name === name || normalized.name === altName;
159
159
  });
160
160
 
161
- if (existingIdx >= 0) {
162
- refs[existingIdx] = entry;
163
- } else {
164
- refs.push(entry);
161
+ if (duplicate) {
162
+ throw new Error(`Cannot add ref "${input.ref}" as "${name}": a ref with that name already exists`);
165
163
  }
164
+ refs.push(entry);
166
165
 
167
166
  currentConfig.refs = refs;
168
167
  await writeConfig(fs, currentConfig);
@@ -178,13 +177,13 @@ export function createConfigAgent(
178
177
  // ---- remove_ref ----
179
178
  const removeRefTool = defineTool({
180
179
  name: "remove_ref",
181
- description: "Remove an agent ref from the consumer config by name or alias.",
180
+ description: "Remove an agent ref from the consumer config by name.",
182
181
  inputSchema: {
183
182
  type: "object" as const,
184
183
  properties: {
185
184
  name: {
186
185
  type: "string",
187
- description: "Ref name or alias to remove",
186
+ description: "Ref name to remove",
188
187
  },
189
188
  },
190
189
  required: ["name"],