@slashfi/agents-sdk 0.77.0 → 0.77.1
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 +2 -2
- package/dist/adk-tools.d.ts.map +1 -1
- package/dist/adk-tools.js +9 -18
- package/dist/adk-tools.js.map +1 -1
- package/dist/adk.js +7 -9
- package/dist/adk.js.map +1 -1
- package/dist/agent-definitions/config.d.ts.map +1 -1
- package/dist/agent-definitions/config.js +12 -14
- package/dist/agent-definitions/config.js.map +1 -1
- package/dist/cjs/adk-tools.js +9 -18
- package/dist/cjs/adk-tools.js.map +1 -1
- package/dist/cjs/agent-definitions/config.js +12 -14
- package/dist/cjs/agent-definitions/config.js.map +1 -1
- package/dist/cjs/config-store.js +34 -17
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/define-config.js +5 -7
- package/dist/cjs/define-config.js.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/materialize.js +1 -1
- package/dist/cjs/materialize.js.map +1 -1
- package/dist/cjs/registry-consumer.js +1 -1
- package/dist/config-store.d.ts +3 -3
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +34 -17
- package/dist/config-store.js.map +1 -1
- package/dist/define-config.d.ts +11 -18
- package/dist/define-config.d.ts.map +1 -1
- package/dist/define-config.js +5 -7
- package/dist/define-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/materialize.js +1 -1
- package/dist/materialize.js.map +1 -1
- package/dist/registry-consumer.d.ts +1 -1
- package/dist/registry-consumer.js +1 -1
- package/dist/validate.d.ts +8 -8
- package/package.json +1 -1
- package/src/adk-tools.ts +11 -18
- package/src/adk.ts +7 -8
- package/src/agent-definitions/config.ts +15 -16
- package/src/config-store.ts +43 -19
- package/src/consumer.test.ts +7 -7
- package/src/define-config.ts +11 -20
- package/src/index.ts +1 -0
- package/src/materialize.ts +1 -1
- package/src/ref-naming.test.ts +115 -90
- package/src/registry-consumer.ts +1 -1
package/src/define-config.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* ],
|
|
17
17
|
* refs: [
|
|
18
18
|
* 'notion',
|
|
19
|
-
* { ref: 'postgres',
|
|
20
|
-
* { ref: 'postgres',
|
|
19
|
+
* { ref: 'postgres', name: 'prod-db', config: { url: 'https://twin.slash.com/secrets/crdb-url' } },
|
|
20
|
+
* { ref: 'postgres', name: 'staging', config: { url: 'https://twin.slash.com/secrets/staging-url' } },
|
|
21
21
|
* ],
|
|
22
22
|
* });
|
|
23
23
|
* ```
|
|
@@ -158,14 +158,10 @@ export type RefEntry = {
|
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
160
|
* Local identifier for this ref. Used by all operations
|
|
161
|
-
* (call/remove/auth/update/…) to look up the entry.
|
|
162
|
-
*
|
|
163
|
-
* common case "one local instance per agent" requires only
|
|
164
|
-
* `{ ref: 'notion', ... }`. Set `name` to a different value only
|
|
165
|
-
* when you need multiple local instances of the same remote
|
|
166
|
-
* agent (e.g. `{ ref: 'notion', name: 'work-notion' }`).
|
|
161
|
+
* (call/remove/auth/update/…) to look up the entry. Add paths
|
|
162
|
+
* default this to `ref` when omitted.
|
|
167
163
|
*/
|
|
168
|
-
name
|
|
164
|
+
name: string;
|
|
169
165
|
|
|
170
166
|
/** Connection scheme */
|
|
171
167
|
scheme?: 'mcp' | 'https' | 'registry';
|
|
@@ -173,12 +169,6 @@ export type RefEntry = {
|
|
|
173
169
|
/** Direct URL to the agent (e.g. https://mcp.notion.com/mcp) */
|
|
174
170
|
url?: string;
|
|
175
171
|
|
|
176
|
-
/**
|
|
177
|
-
* @deprecated Use `name` instead. `as` is preserved for reading
|
|
178
|
-
* old consumer-config.json files; new writes emit `name`.
|
|
179
|
-
*/
|
|
180
|
-
as?: string;
|
|
181
|
-
|
|
182
172
|
/** Per-instance config (headers, secrets, etc. — values support {{secret-uri}} templates) */
|
|
183
173
|
config?: RefConfig;
|
|
184
174
|
|
|
@@ -189,6 +179,9 @@ export type RefEntry = {
|
|
|
189
179
|
status?: 'active' | 'inactive' | 'error';
|
|
190
180
|
};
|
|
191
181
|
|
|
182
|
+
/** Input accepted by add paths. `name` defaults to `ref` when omitted. */
|
|
183
|
+
export type RefAddInput = Omit<RefEntry, "name"> & { name?: string };
|
|
184
|
+
|
|
192
185
|
// ============================================
|
|
193
186
|
// Consumer Config
|
|
194
187
|
// ============================================
|
|
@@ -259,15 +252,13 @@ export interface ResolvedConfig {
|
|
|
259
252
|
/**
|
|
260
253
|
* Normalize a ref entry to its full form.
|
|
261
254
|
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* consistent with the `ref.add({ ref, name })` contract while still
|
|
265
|
-
* reading old `{ ref, as }` entries from pre-0.74 consumer-config.json.
|
|
255
|
+
* Add paths default `name` to `ref` before writing. `normalizeRef` keeps the
|
|
256
|
+
* same invariant for in-memory/test configs that omit `name`.
|
|
266
257
|
*/
|
|
267
258
|
export function normalizeRef(entry: RefEntry): ResolvedRef {
|
|
268
259
|
return {
|
|
269
260
|
...entry,
|
|
270
|
-
name: entry.name ?? entry.
|
|
261
|
+
name: entry.name ?? entry.ref,
|
|
271
262
|
config: entry.config ?? {},
|
|
272
263
|
};
|
|
273
264
|
}
|
package/src/index.ts
CHANGED
package/src/materialize.ts
CHANGED
|
@@ -326,7 +326,7 @@ export async function syncAllRefs(
|
|
|
326
326
|
for (const refEntry of refs) {
|
|
327
327
|
const name = typeof refEntry === "string"
|
|
328
328
|
? refEntry
|
|
329
|
-
: (refEntry as any).
|
|
329
|
+
: (refEntry as any).name ?? (refEntry as any).ref;
|
|
330
330
|
if (!name) continue;
|
|
331
331
|
if (opts?.filter && name !== opts.filter) continue;
|
|
332
332
|
names.push(name);
|
package/src/ref-naming.test.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the `ref` naming contract introduced in 0.74:
|
|
3
3
|
*
|
|
4
|
-
* - `
|
|
5
|
-
*
|
|
6
|
-
* - `
|
|
7
|
-
*
|
|
8
|
-
* in either shape.
|
|
9
|
-
* - `createRefTool` (the adk-tools.ts MCP tool) drops `as` from its
|
|
10
|
-
* schema and defaults `ref` to `name` on add, so "Add a ref called X"
|
|
11
|
-
* via an LLM that picks either field lands on the same stored
|
|
12
|
-
* `{ ref: 'X' }` entry.
|
|
4
|
+
* - `name` is the local identifier for every stored ref entry.
|
|
5
|
+
* - Add paths default `name` to `ref` when omitted.
|
|
6
|
+
* - `as` is not part of the stored or public ref shape.
|
|
7
|
+
* - Adding a duplicate `name` is a loud error, not a replace.
|
|
13
8
|
*/
|
|
14
9
|
|
|
15
10
|
import { describe, expect, test } from "bun:test";
|
|
@@ -42,7 +37,7 @@ async function readJson<T = unknown>(fs: FsStore, path: string): Promise<T> {
|
|
|
42
37
|
// ─── RefEntry: name field on write ───────────────────────────────────
|
|
43
38
|
|
|
44
39
|
describe("ref.add — identifier field", () => {
|
|
45
|
-
test("single-instance case stores
|
|
40
|
+
test("single-instance case stores explicit `name` equal to `ref`", async () => {
|
|
46
41
|
const fs = createMemoryFs();
|
|
47
42
|
const adk = createAdk(fs);
|
|
48
43
|
|
|
@@ -58,8 +53,7 @@ describe("ref.add — identifier field", () => {
|
|
|
58
53
|
);
|
|
59
54
|
expect(parsed.refs).toHaveLength(1);
|
|
60
55
|
expect(parsed.refs[0].ref).toBe("test-ref");
|
|
61
|
-
expect(parsed.refs[0].name).
|
|
62
|
-
expect(parsed.refs[0].as).toBeUndefined();
|
|
56
|
+
expect(parsed.refs[0].name).toBe("test-ref");
|
|
63
57
|
});
|
|
64
58
|
|
|
65
59
|
test("aliasing case stores both `ref` and `name`", async () => {
|
|
@@ -79,14 +73,40 @@ describe("ref.add — identifier field", () => {
|
|
|
79
73
|
);
|
|
80
74
|
expect(parsed.refs[0].ref).toBe("notion");
|
|
81
75
|
expect(parsed.refs[0].name).toBe("work-notion");
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("adding a duplicate name rejects instead of replacing", async () => {
|
|
79
|
+
const fs = createMemoryFs();
|
|
80
|
+
const adk = createAdk(fs);
|
|
81
|
+
|
|
82
|
+
await adk.ref.add({
|
|
83
|
+
ref: "notion",
|
|
84
|
+
name: "work",
|
|
85
|
+
scheme: "mcp",
|
|
86
|
+
url: "http://localhost:12345",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await expect(
|
|
90
|
+
adk.ref.add({
|
|
91
|
+
ref: "linear",
|
|
92
|
+
name: "work",
|
|
93
|
+
scheme: "mcp",
|
|
94
|
+
url: "http://localhost:12346",
|
|
95
|
+
}),
|
|
96
|
+
).rejects.toThrow(/already exists/);
|
|
97
|
+
|
|
98
|
+
const parsed = await readJson<{ refs: Array<Record<string, unknown>> }>(
|
|
99
|
+
fs,
|
|
100
|
+
"consumer-config.json",
|
|
101
|
+
);
|
|
102
|
+
expect(parsed.refs).toHaveLength(1);
|
|
103
|
+
expect(parsed.refs[0].ref).toBe("notion");
|
|
84
104
|
});
|
|
85
105
|
});
|
|
86
106
|
|
|
87
107
|
// ─── Lookup compatibility: new `name` field works end-to-end ─────────
|
|
88
108
|
|
|
89
|
-
describe("ref lookup — name/
|
|
109
|
+
describe("ref lookup — name/ref resolution", () => {
|
|
90
110
|
test("entries written with `name` are findable by `name`", async () => {
|
|
91
111
|
const fs = createMemoryFs();
|
|
92
112
|
const adk = createAdk(fs);
|
|
@@ -119,9 +139,7 @@ describe("ref lookup — name/as/ref resolution", () => {
|
|
|
119
139
|
expect(refs[0]?.name).toBe("work-notion");
|
|
120
140
|
});
|
|
121
141
|
|
|
122
|
-
test("
|
|
123
|
-
// Simulate a consumer-config.json produced by a pre-0.74 client that
|
|
124
|
-
// still writes the `as` field. The read path must still resolve it.
|
|
142
|
+
test("entries without `name` normalize to `ref`", async () => {
|
|
125
143
|
const fs = createMemoryFs();
|
|
126
144
|
await fs.writeFile(
|
|
127
145
|
"consumer-config.json",
|
|
@@ -129,7 +147,6 @@ describe("ref lookup — name/as/ref resolution", () => {
|
|
|
129
147
|
refs: [
|
|
130
148
|
{
|
|
131
149
|
ref: "notion",
|
|
132
|
-
as: "work-notion",
|
|
133
150
|
scheme: "mcp",
|
|
134
151
|
url: "http://localhost:12345",
|
|
135
152
|
},
|
|
@@ -138,56 +155,26 @@ describe("ref lookup — name/as/ref resolution", () => {
|
|
|
138
155
|
);
|
|
139
156
|
const adk = createAdk(fs);
|
|
140
157
|
|
|
141
|
-
const
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
144
|
-
expect(entry?.as).toBe("work-notion");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("when both `name` and `as` are present, `name` wins", async () => {
|
|
148
|
-
const fs = createMemoryFs();
|
|
149
|
-
await fs.writeFile(
|
|
150
|
-
"consumer-config.json",
|
|
151
|
-
JSON.stringify({
|
|
152
|
-
refs: [
|
|
153
|
-
{
|
|
154
|
-
ref: "notion",
|
|
155
|
-
name: "new-identifier",
|
|
156
|
-
as: "legacy-identifier",
|
|
157
|
-
scheme: "mcp",
|
|
158
|
-
url: "http://localhost:12345",
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
}),
|
|
162
|
-
);
|
|
163
|
-
const adk = createAdk(fs);
|
|
164
|
-
|
|
165
|
-
const byNew = await adk.ref.get("new-identifier");
|
|
166
|
-
expect(byNew).not.toBeNull();
|
|
167
|
-
expect(byNew?.ref).toBe("notion");
|
|
158
|
+
const refs = await adk.ref.list();
|
|
159
|
+
expect(refs[0]?.name).toBe("notion");
|
|
160
|
+
expect(await adk.ref.get("notion")).not.toBeNull();
|
|
168
161
|
});
|
|
169
162
|
});
|
|
170
163
|
|
|
171
|
-
// ─── ref.update:
|
|
164
|
+
// ─── ref.update: name is the only rename field ───────────────────────
|
|
172
165
|
|
|
173
|
-
describe("ref.update — name
|
|
174
|
-
test("passing `name` in updates sets name
|
|
166
|
+
describe("ref.update — name handling", () => {
|
|
167
|
+
test("passing `name` in updates sets name", async () => {
|
|
175
168
|
const fs = createMemoryFs();
|
|
176
|
-
await fs.writeFile(
|
|
177
|
-
"consumer-config.json",
|
|
178
|
-
JSON.stringify({
|
|
179
|
-
refs: [
|
|
180
|
-
{
|
|
181
|
-
ref: "notion",
|
|
182
|
-
as: "old-alias",
|
|
183
|
-
scheme: "mcp",
|
|
184
|
-
url: "http://localhost:12345",
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
}),
|
|
188
|
-
);
|
|
189
169
|
const adk = createAdk(fs);
|
|
190
170
|
|
|
171
|
+
await adk.ref.add({
|
|
172
|
+
ref: "notion",
|
|
173
|
+
name: "old-alias",
|
|
174
|
+
scheme: "mcp",
|
|
175
|
+
url: "http://localhost:12345",
|
|
176
|
+
});
|
|
177
|
+
|
|
191
178
|
const ok = await adk.ref.update("old-alias", { name: "new-alias" });
|
|
192
179
|
expect(ok).toBe(true);
|
|
193
180
|
|
|
@@ -196,35 +183,29 @@ describe("ref.update — name/as handling", () => {
|
|
|
196
183
|
"consumer-config.json",
|
|
197
184
|
);
|
|
198
185
|
expect(parsed.refs[0].name).toBe("new-alias");
|
|
199
|
-
expect(parsed.refs[0].as).toBeUndefined();
|
|
200
186
|
expect(parsed.refs[0].ref).toBe("notion");
|
|
201
187
|
});
|
|
202
188
|
|
|
203
|
-
test("
|
|
189
|
+
test("renaming to an existing name rejects", async () => {
|
|
204
190
|
const fs = createMemoryFs();
|
|
205
|
-
await fs.writeFile(
|
|
206
|
-
"consumer-config.json",
|
|
207
|
-
JSON.stringify({
|
|
208
|
-
refs: [
|
|
209
|
-
{
|
|
210
|
-
ref: "notion",
|
|
211
|
-
as: "first",
|
|
212
|
-
scheme: "mcp",
|
|
213
|
-
url: "http://localhost:12345",
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
}),
|
|
217
|
-
);
|
|
218
191
|
const adk = createAdk(fs);
|
|
219
192
|
|
|
220
|
-
|
|
221
|
-
|
|
193
|
+
await adk.ref.add({
|
|
194
|
+
ref: "notion",
|
|
195
|
+
name: "first",
|
|
196
|
+
scheme: "mcp",
|
|
197
|
+
url: "http://localhost:12345",
|
|
198
|
+
});
|
|
199
|
+
await adk.ref.add({
|
|
200
|
+
ref: "linear",
|
|
201
|
+
name: "second",
|
|
202
|
+
scheme: "mcp",
|
|
203
|
+
url: "http://localhost:12346",
|
|
204
|
+
});
|
|
222
205
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"consumer-config.json",
|
|
206
|
+
await expect(adk.ref.update("first", { name: "second" })).rejects.toThrow(
|
|
207
|
+
/already exists/,
|
|
226
208
|
);
|
|
227
|
-
expect(parsed.refs[0].as).toBe("second");
|
|
228
209
|
});
|
|
229
210
|
});
|
|
230
211
|
|
|
@@ -232,10 +213,8 @@ describe("ref.update — name/as handling", () => {
|
|
|
232
213
|
//
|
|
233
214
|
// When an LLM is prompted with "Add a ref called X", its tool-call
|
|
234
215
|
// arguments can land on either `{ ref: 'X', … }` or `{ name: 'X', … }`
|
|
235
|
-
// depending on sampling.
|
|
236
|
-
//
|
|
237
|
-
// `add` handler now defaults `ref ??= name` so both paths converge on
|
|
238
|
-
// the same stored entry.
|
|
216
|
+
// depending on sampling. The `add` handler defaults the missing field so
|
|
217
|
+
// both paths converge on the same stored `{ ref: 'X', name: 'X' }` entry.
|
|
239
218
|
|
|
240
219
|
describe("ref tool — add operation defaults ref to name", () => {
|
|
241
220
|
function makeRefTool(adk: ReturnType<typeof createAdk>) {
|
|
@@ -267,8 +246,7 @@ describe("ref tool — add operation defaults ref to name", () => {
|
|
|
267
246
|
);
|
|
268
247
|
expect(parsed.refs).toHaveLength(1);
|
|
269
248
|
expect(parsed.refs[0].ref).toBe("test-identity-ref");
|
|
270
|
-
|
|
271
|
-
expect(parsed.refs[0].name).toBeUndefined();
|
|
249
|
+
expect(parsed.refs[0].name).toBe("test-identity-ref");
|
|
272
250
|
|
|
273
251
|
const entry = await adk.ref.get("test-identity-ref");
|
|
274
252
|
expect(entry).not.toBeNull();
|
|
@@ -295,7 +273,7 @@ describe("ref tool — add operation defaults ref to name", () => {
|
|
|
295
273
|
"consumer-config.json",
|
|
296
274
|
);
|
|
297
275
|
expect(parsed.refs[0].ref).toBe("test-identity-ref");
|
|
298
|
-
expect(parsed.refs[0].name).
|
|
276
|
+
expect(parsed.refs[0].name).toBe("test-identity-ref");
|
|
299
277
|
});
|
|
300
278
|
|
|
301
279
|
test("LLM sends `ref` + different `name` → stored as canonical + alias", async () => {
|
|
@@ -349,3 +327,50 @@ describe("ref tool — add operation defaults ref to name", () => {
|
|
|
349
327
|
expect(raw).toBeNull();
|
|
350
328
|
});
|
|
351
329
|
});
|
|
330
|
+
|
|
331
|
+
describe("ref tool — auth state hook", () => {
|
|
332
|
+
test("passes tool input to getAuthStateContext", async () => {
|
|
333
|
+
const authCalls: Array<{
|
|
334
|
+
name: string;
|
|
335
|
+
opts: { stateContext?: Record<string, unknown> };
|
|
336
|
+
}> = [];
|
|
337
|
+
const adk = {
|
|
338
|
+
ref: {
|
|
339
|
+
auth: async (
|
|
340
|
+
name: string,
|
|
341
|
+
opts: { stateContext?: Record<string, unknown> },
|
|
342
|
+
) => {
|
|
343
|
+
authCalls.push({ name, opts });
|
|
344
|
+
return { complete: true };
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
} as unknown as ReturnType<typeof createAdk>;
|
|
348
|
+
const tools = createAdkTools({
|
|
349
|
+
resolveScope: () => adk,
|
|
350
|
+
hooks: {
|
|
351
|
+
getAuthStateContext: async (input) => ({
|
|
352
|
+
name: input.name,
|
|
353
|
+
ref: input.ref,
|
|
354
|
+
}),
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
const refTool = tools.find((t) => t.name === "ref");
|
|
358
|
+
if (!refTool) throw new Error("ref tool not found");
|
|
359
|
+
|
|
360
|
+
await refTool.execute(
|
|
361
|
+
{
|
|
362
|
+
operation: "auth",
|
|
363
|
+
ref: "google-gmail",
|
|
364
|
+
name: "work2",
|
|
365
|
+
},
|
|
366
|
+
{} as ToolContext,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(authCalls).toHaveLength(1);
|
|
370
|
+
expect(authCalls[0]?.name).toBe("work2");
|
|
371
|
+
expect(authCalls[0]?.opts.stateContext).toEqual({
|
|
372
|
+
ref: "google-gmail",
|
|
373
|
+
name: "work2",
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
});
|
package/src/registry-consumer.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* const config = defineConfig({
|
|
14
14
|
* registries: ['https://registry.slash.com'],
|
|
15
|
-
* refs: ['notion', { ref: 'postgres',
|
|
15
|
+
* refs: ['notion', { ref: 'postgres', name: 'prod-db', config: { url: '...' } }],
|
|
16
16
|
* });
|
|
17
17
|
*
|
|
18
18
|
* const consumer = await createRegistryConsumer(config);
|