@slashfi/agents-sdk 0.77.1 → 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.
- package/dist/adk-tools.d.ts.map +1 -1
- package/dist/adk-tools.js +253 -104
- package/dist/adk-tools.js.map +1 -1
- package/dist/cjs/adk-tools.js +253 -104
- package/dist/cjs/adk-tools.js.map +1 -1
- package/dist/cjs/registry.js +33 -2
- package/dist/cjs/registry.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +33 -2
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/adk-tools.ts +282 -113
- package/src/ref-naming.test.ts +49 -1
- package/src/registry.ts +40 -2
- 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
|
/**
|
|
@@ -68,97 +310,14 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
|
|
|
68
310
|
name: "ref",
|
|
69
311
|
description:
|
|
70
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`.",
|
|
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
|
-
},
|
|
313
|
+
inputSchema: withScopeSchema(refToolInputJsonSchema, scopeSchema),
|
|
156
314
|
execute: async (input: Record<string, unknown>, ctx) => {
|
|
315
|
+
const parsedInput = parseRefToolInput(input);
|
|
157
316
|
const adk = await resolveScope(
|
|
158
|
-
|
|
317
|
+
parsedInput.scope as string | undefined,
|
|
159
318
|
ctx as TCtx,
|
|
160
319
|
);
|
|
161
|
-
const op =
|
|
320
|
+
const op = parsedInput.operation as string;
|
|
162
321
|
|
|
163
322
|
switch (op) {
|
|
164
323
|
case "add": {
|
|
@@ -166,18 +325,24 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
|
|
|
166
325
|
// other defaults to it. The stored entry always has an explicit
|
|
167
326
|
// `name`, so downstream auth/callback state can distinguish the
|
|
168
327
|
// canonical ref from the local connection handle.
|
|
169
|
-
const refValue = (
|
|
328
|
+
const refValue = (parsedInput.ref ?? parsedInput.name) as
|
|
329
|
+
| string
|
|
330
|
+
| undefined;
|
|
170
331
|
if (!refValue) {
|
|
171
332
|
throw new Error(
|
|
172
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.",
|
|
173
334
|
);
|
|
174
335
|
}
|
|
175
|
-
const nameValue = (
|
|
176
|
-
const entry: Record<string, unknown> = {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
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;
|
|
181
346
|
const { security } = await adk.ref.add(entry as unknown as RefEntry);
|
|
182
347
|
return {
|
|
183
348
|
added: true,
|
|
@@ -187,25 +352,25 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
|
|
|
187
352
|
};
|
|
188
353
|
}
|
|
189
354
|
case "remove":
|
|
190
|
-
return { removed: await adk.ref.remove(
|
|
355
|
+
return { removed: await adk.ref.remove(parsedInput.name as string) };
|
|
191
356
|
case "list":
|
|
192
357
|
return { refs: await adk.ref.list() };
|
|
193
358
|
case "update":
|
|
194
359
|
return {
|
|
195
360
|
updated: await adk.ref.update(
|
|
196
|
-
|
|
197
|
-
|
|
361
|
+
parsedInput.name as string,
|
|
362
|
+
parsedInput as unknown as Partial<RefEntry>,
|
|
198
363
|
),
|
|
199
364
|
};
|
|
200
365
|
case "inspect":
|
|
201
|
-
return await adk.ref.inspect(
|
|
202
|
-
full:
|
|
366
|
+
return await adk.ref.inspect(parsedInput.name as string, {
|
|
367
|
+
full: parsedInput.full as boolean,
|
|
203
368
|
});
|
|
204
369
|
case "call":
|
|
205
370
|
return await adk.ref.call(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
371
|
+
parsedInput.name as string,
|
|
372
|
+
parsedInput.tool as string,
|
|
373
|
+
parsedInput.params as Record<string, unknown>,
|
|
209
374
|
);
|
|
210
375
|
case "auth": {
|
|
211
376
|
const authOpts: {
|
|
@@ -213,27 +378,31 @@ export function createAdkTools<TCtx extends ToolContext = ToolContext>(
|
|
|
213
378
|
credentials?: Record<string, string>;
|
|
214
379
|
stateContext?: Record<string, unknown>;
|
|
215
380
|
} = {};
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
>;
|
|
219
388
|
if (opts.hooks?.getAuthStateContext) {
|
|
220
389
|
authOpts.stateContext = await opts.hooks.getAuthStateContext(
|
|
221
|
-
|
|
390
|
+
parsedInput,
|
|
222
391
|
ctx as TCtx,
|
|
223
392
|
);
|
|
224
393
|
}
|
|
225
|
-
return await adk.ref.auth(
|
|
394
|
+
return await adk.ref.auth(parsedInput.name as string, authOpts);
|
|
226
395
|
}
|
|
227
396
|
case "auth-status":
|
|
228
|
-
return await adk.ref.authStatus(
|
|
397
|
+
return await adk.ref.authStatus(parsedInput.name as string);
|
|
229
398
|
case "refresh-token":
|
|
230
|
-
return await adk.ref.refreshToken(
|
|
399
|
+
return await adk.ref.refreshToken(parsedInput.name as string);
|
|
231
400
|
case "resources":
|
|
232
|
-
return await adk.ref.resources(
|
|
401
|
+
return await adk.ref.resources(parsedInput.name as string);
|
|
233
402
|
case "read":
|
|
234
403
|
return await adk.ref.read(
|
|
235
|
-
|
|
236
|
-
|
|
404
|
+
parsedInput.name as string,
|
|
405
|
+
parsedInput.uris as string[],
|
|
237
406
|
);
|
|
238
407
|
default:
|
|
239
408
|
throw new Error(`Unknown ref operation: ${op}`);
|
package/src/ref-naming.test.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { describe, expect, test } from "bun:test";
|
|
11
11
|
import { createAdkTools } from "./adk-tools";
|
|
12
12
|
import type { FsStore } from "./agent-definitions/config";
|
|
13
|
-
import { createAdk } from "./index";
|
|
13
|
+
import { createAdk, createAgentRegistry, defineAgent } from "./index";
|
|
14
14
|
import type { ToolContext } from "./types";
|
|
15
15
|
|
|
16
16
|
function createMemoryFs(): FsStore {
|
|
@@ -326,6 +326,54 @@ describe("ref tool — add operation defaults ref to name", () => {
|
|
|
326
326
|
const raw = await fs.readFile("consumer-config.json");
|
|
327
327
|
expect(raw).toBeNull();
|
|
328
328
|
});
|
|
329
|
+
|
|
330
|
+
test("invalid add input returns schema details through registry call", async () => {
|
|
331
|
+
const fs = createMemoryFs();
|
|
332
|
+
const adk = createAdk(fs);
|
|
333
|
+
const refTool = makeRefTool(adk);
|
|
334
|
+
const registry = createAgentRegistry();
|
|
335
|
+
registry.register(
|
|
336
|
+
defineAgent({
|
|
337
|
+
path: "@config",
|
|
338
|
+
entrypoint: "Config agent",
|
|
339
|
+
tools: [refTool],
|
|
340
|
+
visibility: "public",
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const response = await registry.call({
|
|
345
|
+
action: "execute_tool",
|
|
346
|
+
path: "@config",
|
|
347
|
+
tool: "ref",
|
|
348
|
+
params: {
|
|
349
|
+
operation: "add",
|
|
350
|
+
ref: "google-calendar",
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(response.success).toBe(false);
|
|
355
|
+
if (response.success) throw new Error("expected invalid input error");
|
|
356
|
+
expect(response.code).toBe("TOOL_INPUT_INVALID");
|
|
357
|
+
expect(response.error).toContain("Invalid ref.add input");
|
|
358
|
+
expect(response.details?.issues).toEqual(
|
|
359
|
+
expect.arrayContaining([
|
|
360
|
+
expect.objectContaining({
|
|
361
|
+
path: "sourceRegistry",
|
|
362
|
+
}),
|
|
363
|
+
]),
|
|
364
|
+
);
|
|
365
|
+
expect(response.details?.schema).toMatchObject({
|
|
366
|
+
anyOf: expect.any(Array),
|
|
367
|
+
});
|
|
368
|
+
expect(response.details?.operationSchema).toMatchObject({
|
|
369
|
+
type: "object",
|
|
370
|
+
});
|
|
371
|
+
expect(response.hint).toContain("details.schema");
|
|
372
|
+
expect(response.details).not.toHaveProperty("examples");
|
|
373
|
+
expect(JSON.stringify(response.details?.operationSchema)).toContain(
|
|
374
|
+
"sourceRegistry",
|
|
375
|
+
);
|
|
376
|
+
});
|
|
329
377
|
});
|
|
330
378
|
|
|
331
379
|
describe("ref tool — auth state hook", () => {
|
package/src/registry.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { AdkError } from "./adk-error.js";
|
|
8
9
|
import type { AgentEvent, BaseEvent, CallAgentToolCallEvent, CustomEventMap, EventCallback, EventType, ListAgentsResult, ListAgentsToolCallEvent } from "./events.js";
|
|
9
10
|
import { createEventBus } from "./events.js";
|
|
10
11
|
import type { Logger } from "./logger.js";
|
|
@@ -39,6 +40,33 @@ const DEFAULT_SUPPORTED_ACTIONS: AgentAction[] = [
|
|
|
39
40
|
"read_resources",
|
|
40
41
|
];
|
|
41
42
|
|
|
43
|
+
function adkErrorFields(
|
|
44
|
+
err: unknown,
|
|
45
|
+
): { code: string; hint: string; details: Record<string, unknown> } | null {
|
|
46
|
+
if (err instanceof AdkError) {
|
|
47
|
+
return { code: err.code, hint: err.hint, details: err.details };
|
|
48
|
+
}
|
|
49
|
+
if (!err || typeof err !== "object") return null;
|
|
50
|
+
const candidate = err as {
|
|
51
|
+
name?: unknown;
|
|
52
|
+
code?: unknown;
|
|
53
|
+
hint?: unknown;
|
|
54
|
+
details?: unknown;
|
|
55
|
+
};
|
|
56
|
+
return candidate.name === "AdkError" &&
|
|
57
|
+
typeof candidate.code === "string" &&
|
|
58
|
+
typeof candidate.hint === "string" &&
|
|
59
|
+
!!candidate.details &&
|
|
60
|
+
typeof candidate.details === "object" &&
|
|
61
|
+
!Array.isArray(candidate.details)
|
|
62
|
+
? {
|
|
63
|
+
code: candidate.code,
|
|
64
|
+
hint: candidate.hint,
|
|
65
|
+
details: candidate.details as Record<string, unknown>,
|
|
66
|
+
}
|
|
67
|
+
: null;
|
|
68
|
+
}
|
|
69
|
+
|
|
42
70
|
/**
|
|
43
71
|
* Estimate the token count for a tool schema when serialized to JSON.
|
|
44
72
|
* Uses a rough heuristic: ~4 characters per token (conservative estimate
|
|
@@ -760,10 +788,15 @@ export function createAgentRegistry(
|
|
|
760
788
|
})
|
|
761
789
|
.catch(() => {}); // don't let emit error mask tool error
|
|
762
790
|
|
|
791
|
+
const adkErr = adkErrorFields(err);
|
|
763
792
|
return {
|
|
764
793
|
success: false,
|
|
765
794
|
error: err instanceof Error ? err.message : String(err),
|
|
766
|
-
code: "TOOL_EXECUTION_ERROR",
|
|
795
|
+
code: adkErr?.code ?? "TOOL_EXECUTION_ERROR",
|
|
796
|
+
...(adkErr && {
|
|
797
|
+
hint: adkErr.hint,
|
|
798
|
+
details: adkErr.details,
|
|
799
|
+
}),
|
|
767
800
|
} as CallAgentErrorResponse;
|
|
768
801
|
}
|
|
769
802
|
|
|
@@ -784,11 +817,16 @@ export function createAgentRegistry(
|
|
|
784
817
|
} as CallAgentExecuteToolResponse;
|
|
785
818
|
} catch (outerErr) {
|
|
786
819
|
// Catch-all for unexpected errors (e.g., emit failures)
|
|
820
|
+
const adkErr = adkErrorFields(outerErr);
|
|
787
821
|
return {
|
|
788
822
|
success: false,
|
|
789
823
|
error:
|
|
790
824
|
outerErr instanceof Error ? outerErr.message : String(outerErr),
|
|
791
|
-
code: "TOOL_EXECUTION_ERROR",
|
|
825
|
+
code: adkErr?.code ?? "TOOL_EXECUTION_ERROR",
|
|
826
|
+
...(adkErr && {
|
|
827
|
+
hint: adkErr.hint,
|
|
828
|
+
details: adkErr.details,
|
|
829
|
+
}),
|
|
792
830
|
} as CallAgentErrorResponse;
|
|
793
831
|
}
|
|
794
832
|
}
|
package/src/types.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Defines the fundamental types for agent definitions, tools, and contexts.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { EventCallback, EventType } from "./events.js";
|
|
8
7
|
import type { AgentAction, CallerType } from "./call-agent-schema.js";
|
|
8
|
+
import type { EventCallback, EventType } from "./events.js";
|
|
9
9
|
|
|
10
10
|
/** Internal listener entry stored on agents/tools */
|
|
11
11
|
export interface ListenerEntry {
|
|
@@ -22,7 +22,15 @@ export interface ListenerEntry {
|
|
|
22
22
|
* JSON Schema definition for tool input parameters.
|
|
23
23
|
*/
|
|
24
24
|
export type JsonSchema = {
|
|
25
|
-
type:
|
|
25
|
+
type:
|
|
26
|
+
| "object"
|
|
27
|
+
| "array"
|
|
28
|
+
| "string"
|
|
29
|
+
| "number"
|
|
30
|
+
| "integer"
|
|
31
|
+
| "boolean"
|
|
32
|
+
| "null"
|
|
33
|
+
| string[];
|
|
26
34
|
properties?: Record<string, JsonSchema>;
|
|
27
35
|
items?: JsonSchema;
|
|
28
36
|
required?: string[];
|
|
@@ -293,7 +301,7 @@ export type SecurityScheme =
|
|
|
293
301
|
* directory-level overview (e.g., in list_agents responses).
|
|
294
302
|
*/
|
|
295
303
|
export interface SecuritySchemeSummary {
|
|
296
|
-
type: SecurityScheme[
|
|
304
|
+
type: SecurityScheme["type"];
|
|
297
305
|
[key: string]: unknown;
|
|
298
306
|
}
|
|
299
307
|
|
|
@@ -710,7 +718,7 @@ export interface AgentDefinition<TContext extends ToolContext = ToolContext> {
|
|
|
710
718
|
* - 'direct': registry hosts and serves this agent's tools (default)
|
|
711
719
|
* - 'redirect': registry catalogs this agent but clients connect to `upstream` directly
|
|
712
720
|
*/
|
|
713
|
-
mode?:
|
|
721
|
+
mode?: "direct" | "redirect";
|
|
714
722
|
|
|
715
723
|
/**
|
|
716
724
|
* Upstream URL for redirect-mode agents.
|
|
@@ -861,6 +869,8 @@ export interface CallAgentErrorResponse {
|
|
|
861
869
|
success: false;
|
|
862
870
|
error: string;
|
|
863
871
|
code?: string;
|
|
872
|
+
hint?: string;
|
|
873
|
+
details?: Record<string, unknown>;
|
|
864
874
|
}
|
|
865
875
|
|
|
866
876
|
/** Union of all response types */
|
|
@@ -942,5 +952,10 @@ export type ServerSource =
|
|
|
942
952
|
| string
|
|
943
953
|
| { command: string; args?: string[]; env?: Record<string, string> }
|
|
944
954
|
| { url: string; headers?: Record<string, string> }
|
|
945
|
-
| {
|
|
946
|
-
|
|
955
|
+
| {
|
|
956
|
+
spawn: string;
|
|
957
|
+
args?: string[];
|
|
958
|
+
env?: Record<string, string>;
|
|
959
|
+
port?: number;
|
|
960
|
+
endpoint?: string;
|
|
961
|
+
};
|