@slashfi/agents-sdk 0.77.2 → 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/src/adk-tools.ts CHANGED
@@ -17,252 +17,10 @@
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";
23
20
  import type { Adk } from "./config-store.js";
24
21
  import type { RefEntry, RegistryEntry } from "./define-config.js";
25
22
  import { defineTool } from "./define.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
- }
23
+ import type { ToolContext, ToolDefinition } from "./types.js";
266
24
 
267
25
  export interface AdkToolsHooks<TCtx extends ToolContext = ToolContext> {
268
26
  /**
@@ -310,14 +68,97 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
310
68
  name: "ref",
311
69
  description:
312
70
  "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),
71
+ inputSchema: {
72
+ type: "object" as const,
73
+ properties: {
74
+ operation: {
75
+ type: "string",
76
+ enum: [
77
+ "add",
78
+ "remove",
79
+ "list",
80
+ "update",
81
+ "inspect",
82
+ "call",
83
+ "auth",
84
+ "auth-status",
85
+ "refresh-token",
86
+ "resources",
87
+ "read",
88
+ ],
89
+ },
90
+ scope: scopeSchema,
91
+ ref: {
92
+ type: "string",
93
+ description:
94
+ "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`.",
95
+ },
96
+ name: {
97
+ type: "string",
98
+ description:
99
+ "Local identifier for this ref, used by all operations to look up the entry. On `add`, defaults to `ref` when omitted.",
100
+ },
101
+ scheme: {
102
+ type: "string",
103
+ description:
104
+ "Connection scheme: 'mcp' (direct MCP server), 'https' (REST proxy), or 'registry' (discovered via a registry). Auto-inferred from `url` or `sourceRegistry` when omitted.",
105
+ },
106
+ url: {
107
+ type: "string",
108
+ description:
109
+ "Direct URL to the agent (e.g. https://mcp.notion.com/mcp). Required for 'mcp' and 'https' schemes.",
110
+ },
111
+ sourceRegistry: {
112
+ type: "object",
113
+ properties: {
114
+ url: { type: "string" },
115
+ agentPath: { type: "string" },
116
+ },
117
+ description:
118
+ "When scheme is 'registry', the registry + agent path to resolve through.",
119
+ },
120
+ config: {
121
+ type: "object",
122
+ description:
123
+ "Per-instance config passed to the agent (headers, credentials, etc.). Supports `{{secret-uri}}` templates.",
124
+ },
125
+ tool: {
126
+ type: "string",
127
+ description:
128
+ "For `call` operation: the tool name on the ref to invoke.",
129
+ },
130
+ params: {
131
+ type: "object",
132
+ description: "For `call` operation: arguments to pass to the tool.",
133
+ },
134
+ full: {
135
+ type: "boolean",
136
+ description:
137
+ "For `inspect` operation: include full agent definition.",
138
+ },
139
+ uris: {
140
+ type: "array",
141
+ items: { type: "string" },
142
+ description: "For `read` operation: the resource URIs to read.",
143
+ },
144
+ apiKey: {
145
+ type: "string",
146
+ description: "For `auth` operation: pre-provisioned API key.",
147
+ },
148
+ credentials: {
149
+ type: "object",
150
+ description:
151
+ "For `auth` operation: key-value map of credential fields (keys match field names from the auth challenge).",
152
+ },
153
+ },
154
+ required: ["operation"],
155
+ },
314
156
  execute: async (input: Record<string, unknown>, ctx) => {
315
- const parsedInput = parseRefToolInput(input);
316
157
  const adk = await resolveScope(
317
- parsedInput.scope as string | undefined,
158
+ input.scope as string | undefined,
318
159
  ctx as TCtx,
319
160
  );
320
- const op = parsedInput.operation as string;
161
+ const op = input.operation as string;
321
162
 
322
163
  switch (op) {
323
164
  case "add": {
@@ -325,24 +166,18 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
325
166
  // other defaults to it. The stored entry always has an explicit
326
167
  // `name`, so downstream auth/callback state can distinguish the
327
168
  // canonical ref from the local connection handle.
328
- const refValue = (parsedInput.ref ?? parsedInput.name) as
329
- | string
330
- | undefined;
169
+ const refValue = (input.ref ?? input.name) as string | undefined;
331
170
  if (!refValue) {
332
171
  throw new Error(
333
172
  "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.",
334
173
  );
335
174
  }
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;
175
+ const nameValue = (input.name ?? refValue) as string;
176
+ const entry: Record<string, unknown> = { ref: refValue, name: nameValue };
177
+ if (input.scheme) entry.scheme = input.scheme;
178
+ if (input.url) entry.url = input.url;
179
+ if (input.sourceRegistry) entry.sourceRegistry = input.sourceRegistry;
180
+ if (input.config) entry.config = input.config;
346
181
  const { security } = await adk.ref.add(entry as unknown as RefEntry);
347
182
  return {
348
183
  added: true,
@@ -352,25 +187,25 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
352
187
  };
353
188
  }
354
189
  case "remove":
355
- return { removed: await adk.ref.remove(parsedInput.name as string) };
190
+ return { removed: await adk.ref.remove(input.name as string) };
356
191
  case "list":
357
192
  return { refs: await adk.ref.list() };
358
193
  case "update":
359
194
  return {
360
195
  updated: await adk.ref.update(
361
- parsedInput.name as string,
362
- parsedInput as unknown as Partial<RefEntry>,
196
+ input.name as string,
197
+ input as unknown as Partial<RefEntry>,
363
198
  ),
364
199
  };
365
200
  case "inspect":
366
- return await adk.ref.inspect(parsedInput.name as string, {
367
- full: parsedInput.full as boolean,
201
+ return await adk.ref.inspect(input.name as string, {
202
+ full: input.full as boolean,
368
203
  });
369
204
  case "call":
370
205
  return await adk.ref.call(
371
- parsedInput.name as string,
372
- parsedInput.tool as string,
373
- parsedInput.params as Record<string, unknown>,
206
+ input.name as string,
207
+ input.tool as string,
208
+ input.params as Record<string, unknown>,
374
209
  );
375
210
  case "auth": {
376
211
  const authOpts: {
@@ -378,31 +213,27 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
378
213
  credentials?: Record<string, string>;
379
214
  stateContext?: Record<string, unknown>;
380
215
  } = {};
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
- >;
216
+ if (input.apiKey) authOpts.apiKey = input.apiKey as string;
217
+ if (input.credentials)
218
+ authOpts.credentials = input.credentials as Record<string, string>;
388
219
  if (opts.hooks?.getAuthStateContext) {
389
220
  authOpts.stateContext = await opts.hooks.getAuthStateContext(
390
- parsedInput,
221
+ input,
391
222
  ctx as TCtx,
392
223
  );
393
224
  }
394
- return await adk.ref.auth(parsedInput.name as string, authOpts);
225
+ return await adk.ref.auth(input.name as string, authOpts);
395
226
  }
396
227
  case "auth-status":
397
- return await adk.ref.authStatus(parsedInput.name as string);
228
+ return await adk.ref.authStatus(input.name as string);
398
229
  case "refresh-token":
399
- return await adk.ref.refreshToken(parsedInput.name as string);
230
+ return await adk.ref.refreshToken(input.name as string);
400
231
  case "resources":
401
- return await adk.ref.resources(parsedInput.name as string);
232
+ return await adk.ref.resources(input.name as string);
402
233
  case "read":
403
234
  return await adk.ref.read(
404
- parsedInput.name as string,
405
- parsedInput.uris as string[],
235
+ input.name as string,
236
+ input.uris as string[],
406
237
  );
407
238
  default:
408
239
  throw new Error(`Unknown ref operation: ${op}`);