@slashfi/agents-sdk 0.79.0 → 0.81.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/dist/adk.js +90 -1
- package/dist/adk.js.map +1 -1
- package/dist/cjs/config-store.js +14 -7
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/materialize.js +37 -10
- package/dist/cjs/materialize.js.map +1 -1
- package/dist/cjs/search.js +406 -0
- package/dist/cjs/search.js.map +1 -0
- package/dist/config-store.d.ts +24 -5
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +14 -7
- package/dist/config-store.js.map +1 -1
- package/dist/materialize.d.ts.map +1 -1
- package/dist/materialize.js +37 -10
- package/dist/materialize.js.map +1 -1
- package/dist/search.d.ts +185 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +396 -0
- package/dist/search.js.map +1 -0
- package/package.json +1 -1
- package/src/adk.ts +96 -1
- package/src/config-store.test.ts +56 -0
- package/src/config-store.ts +32 -5
- package/src/materialize.ts +48 -10
- package/src/search.test.ts +406 -0
- package/src/search.ts +541 -0
package/src/adk.ts
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { readFileSync } from "node:fs";
|
|
26
26
|
import { homedir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
27
28
|
import { createAdk } from "./config-store.js";
|
|
28
29
|
import { createLocalFsStore, getLocalEncryptionKey } from "./local-fs.js";
|
|
29
30
|
import type { Adk } from "./config-store.js";
|
|
@@ -31,6 +32,12 @@ import { AdkError, getError, getRecentErrors } from "./adk-error.js";
|
|
|
31
32
|
import { runInit, parseTarget } from "./init.js";
|
|
32
33
|
import { materializeRef, syncAllRefs } from "./materialize.js";
|
|
33
34
|
import { adkCheck } from "./adk-check.js";
|
|
35
|
+
import {
|
|
36
|
+
refsRootExists,
|
|
37
|
+
renderResults,
|
|
38
|
+
searchRefs,
|
|
39
|
+
writeSearchIndex,
|
|
40
|
+
} from "./search.js";
|
|
34
41
|
|
|
35
42
|
const args = process.argv.slice(2);
|
|
36
43
|
const command = args[0];
|
|
@@ -39,6 +46,24 @@ const command = args[0];
|
|
|
39
46
|
// Helpers
|
|
40
47
|
// ============================================
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Read the SDK's published version from the sibling package.json.
|
|
51
|
+
* Resolved at runtime so a single source-of-truth lives in the manifest.
|
|
52
|
+
* Safe for both `bun src/adk.ts` (dev) and the npm-installed bin (which
|
|
53
|
+
* still runs through bun via the shebang).
|
|
54
|
+
*/
|
|
55
|
+
function getCliVersion(): string {
|
|
56
|
+
try {
|
|
57
|
+
const pkgPath = join(import.meta.dir, "..", "package.json");
|
|
58
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
|
|
59
|
+
version?: string;
|
|
60
|
+
};
|
|
61
|
+
return pkg.version ?? "unknown";
|
|
62
|
+
} catch {
|
|
63
|
+
return "unknown";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
function getArg(flag: string): string | undefined {
|
|
43
68
|
const idx = args.indexOf(flag);
|
|
44
69
|
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
|
@@ -95,10 +120,12 @@ adk — Agent Development Kit
|
|
|
95
120
|
Usage:
|
|
96
121
|
adk init [--target <agent>:<path>] Setup + install skills for coding agents
|
|
97
122
|
adk sync [--ref <name>] Materialize tool docs for all refs in config
|
|
123
|
+
adk search <query> [options] BM25 search over materialized refs/tools
|
|
98
124
|
adk registry <op> [options] Manage registry connections
|
|
99
125
|
adk ref <op> [options] Manage agent refs
|
|
100
126
|
adk config-path Print config directory path
|
|
101
127
|
adk error [id] View recent errors or a specific error
|
|
128
|
+
adk version | --version | -v Print the installed adk SDK version
|
|
102
129
|
|
|
103
130
|
Registry operations:
|
|
104
131
|
adk registry add <url> --name <name> [--auth-type bearer|api-key|none] [--proxy [--proxy-agent @config]]
|
|
@@ -135,6 +162,15 @@ Environment:
|
|
|
135
162
|
ADK_TOKEN Bearer token for authenticated registries
|
|
136
163
|
ADK_ENCRYPTION_KEY Override encryption key (default: auto from ~/.adk/.encryption-key)
|
|
137
164
|
|
|
165
|
+
Search options:
|
|
166
|
+
adk search <query> [--json] [--limit N] [--ref <name>] [--tools-only] [--refs-only]
|
|
167
|
+
BM25 over ~/.adk/refs/* — index includes
|
|
168
|
+
ref names, descriptions, tool names,
|
|
169
|
+
tool docs, parameter names, and skill
|
|
170
|
+
resources. Reads ~/.adk/.search-index.json
|
|
171
|
+
when present (rebuilt by \`adk sync\`),
|
|
172
|
+
otherwise walks refs/* on the fly.
|
|
173
|
+
|
|
138
174
|
Examples:
|
|
139
175
|
adk init --target claude --target cursor --target codex
|
|
140
176
|
adk registry add https://registry.slash.com --name public
|
|
@@ -142,6 +178,8 @@ Examples:
|
|
|
142
178
|
adk ref add notion --registry public
|
|
143
179
|
adk ref inspect notion --full
|
|
144
180
|
adk ref call notion notion-search '{"query":"hello"}'
|
|
181
|
+
adk search "schedule reminder" --json
|
|
182
|
+
adk search "email unread inbox" --tools-only --limit 5
|
|
145
183
|
`);
|
|
146
184
|
}
|
|
147
185
|
|
|
@@ -575,6 +613,58 @@ switch (command) {
|
|
|
575
613
|
for (const f of failed) console.log(` ${f.name}: ${f.error}`);
|
|
576
614
|
}
|
|
577
615
|
console.log(`\nDocs written to: ${configDir}/refs/`);
|
|
616
|
+
// Persist the BM25 search index so `adk search` can skip the
|
|
617
|
+
// recursive ref walk on every query. Best-effort — failure here
|
|
618
|
+
// shouldn't fail `adk sync` since the search path falls back to a
|
|
619
|
+
// fresh walk.
|
|
620
|
+
try {
|
|
621
|
+
const { path, documentCount } = writeSearchIndex(configDir);
|
|
622
|
+
console.log(`Search index: ${path} (${documentCount} docs)`);
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.log(
|
|
625
|
+
`\x1b[33m!\x1b[0m Failed to write search index: ${err instanceof Error ? err.message : String(err)}`,
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
case "search": {
|
|
631
|
+
const configDir = process.env.ADK_CONFIG_DIR ?? join(homedir(), ".adk");
|
|
632
|
+
const refsRoot = join(configDir, "refs");
|
|
633
|
+
// Positional query — first non-flag argv after the `search` command.
|
|
634
|
+
const query = args
|
|
635
|
+
.filter((a) => !a.startsWith("--"))
|
|
636
|
+
.slice(1)
|
|
637
|
+
.join(" ")
|
|
638
|
+
.trim();
|
|
639
|
+
if (!query) {
|
|
640
|
+
console.log("Usage: adk search \"<query>\" [--json] [--limit N] [--ref name] [--tools-only] [--refs-only]");
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
if (!refsRootExists(refsRoot)) {
|
|
644
|
+
const msg = `No materialized refs found at ${refsRoot}. Run \`adk sync\` first.`;
|
|
645
|
+
if (hasFlag("--json")) {
|
|
646
|
+
console.log(JSON.stringify({ error: msg, results: [] }));
|
|
647
|
+
} else {
|
|
648
|
+
console.log(msg);
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
const limitArg = getArg("--limit");
|
|
653
|
+
const limit = limitArg ? Number.parseInt(limitArg, 10) : undefined;
|
|
654
|
+
const ref = getArg("--ref");
|
|
655
|
+
const toolsOnly = hasFlag("--tools-only");
|
|
656
|
+
const refsOnly = hasFlag("--refs-only");
|
|
657
|
+
const results = searchRefs(refsRoot, query, {
|
|
658
|
+
...(limit !== undefined && Number.isFinite(limit) && { limit }),
|
|
659
|
+
...(ref && { ref }),
|
|
660
|
+
...(toolsOnly && { toolsOnly: true }),
|
|
661
|
+
...(refsOnly && { refsOnly: true }),
|
|
662
|
+
});
|
|
663
|
+
if (hasFlag("--json")) {
|
|
664
|
+
console.log(JSON.stringify(results, null, 2));
|
|
665
|
+
} else {
|
|
666
|
+
console.log(renderResults(results));
|
|
667
|
+
}
|
|
578
668
|
break;
|
|
579
669
|
}
|
|
580
670
|
case "config-path": {
|
|
@@ -619,6 +709,11 @@ switch (command) {
|
|
|
619
709
|
const result = await adkCheck({ file, code, run: isRun, noCheck });
|
|
620
710
|
process.exit(result.exitCode);
|
|
621
711
|
}
|
|
712
|
+
case "--version":
|
|
713
|
+
case "-v":
|
|
714
|
+
case "version":
|
|
715
|
+
console.log(getCliVersion());
|
|
716
|
+
break;
|
|
622
717
|
case "--help":
|
|
623
718
|
case "-h":
|
|
624
719
|
case undefined:
|
package/src/config-store.test.ts
CHANGED
|
@@ -1307,5 +1307,61 @@ describe("isRefAuthComplete + cached authFields", () => {
|
|
|
1307
1307
|
);
|
|
1308
1308
|
expect(result).toBe(true);
|
|
1309
1309
|
});
|
|
1310
|
+
|
|
1311
|
+
test("resolvableFields satisfies required, non-automated fields absent from config", async () => {
|
|
1312
|
+
// Scenario: registry-hosted OAuth where the platform injects
|
|
1313
|
+
// client_id / client_secret at runtime via resolveCredentials.
|
|
1314
|
+
// The registry sees them as user-provided (required + non-automated)
|
|
1315
|
+
// but the consumer environment satisfies them externally.
|
|
1316
|
+
const { isRefAuthComplete } = await import("./config-store");
|
|
1317
|
+
const result = isRefAuthComplete(
|
|
1318
|
+
{
|
|
1319
|
+
ref: "google-gmail",
|
|
1320
|
+
name: "google-gmail",
|
|
1321
|
+
scheme: "registry",
|
|
1322
|
+
config: {
|
|
1323
|
+
// client_id / client_secret missing — resolved from env vars.
|
|
1324
|
+
access_token: "tok",
|
|
1325
|
+
},
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
ref: "google-gmail",
|
|
1329
|
+
fetchedAt: new Date().toISOString(),
|
|
1330
|
+
authFields: {
|
|
1331
|
+
client_id: { required: true, automated: false },
|
|
1332
|
+
client_secret: { required: true, automated: false },
|
|
1333
|
+
access_token: { required: true, automated: true },
|
|
1334
|
+
},
|
|
1335
|
+
},
|
|
1336
|
+
{ resolvableFields: ["client_id", "client_secret"] },
|
|
1337
|
+
);
|
|
1338
|
+
expect(result).toBe(true);
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
test("resolvableFields does not bypass missing fields it doesn't list", async () => {
|
|
1342
|
+
const { isRefAuthComplete } = await import("./config-store");
|
|
1343
|
+
const result = isRefAuthComplete(
|
|
1344
|
+
{
|
|
1345
|
+
ref: "@oauth",
|
|
1346
|
+
name: "@oauth",
|
|
1347
|
+
scheme: "https",
|
|
1348
|
+
url: "http://localhost",
|
|
1349
|
+
config: {
|
|
1350
|
+
client_id: "abc",
|
|
1351
|
+
// client_secret missing AND not listed as resolvable.
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
ref: "@oauth",
|
|
1356
|
+
fetchedAt: new Date().toISOString(),
|
|
1357
|
+
authFields: {
|
|
1358
|
+
client_id: { required: true, automated: false },
|
|
1359
|
+
client_secret: { required: true, automated: false },
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
{ resolvableFields: ["client_id"] },
|
|
1363
|
+
);
|
|
1364
|
+
expect(result).toBe(false);
|
|
1365
|
+
});
|
|
1310
1366
|
});
|
|
1311
1367
|
|
package/src/config-store.ts
CHANGED
|
@@ -124,6 +124,25 @@ export interface RegistryCache {
|
|
|
124
124
|
refs: Record<string, RegistryCacheEntry>;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Options for `isRefAuthComplete`.
|
|
129
|
+
*/
|
|
130
|
+
export interface RefAuthCompleteOptions {
|
|
131
|
+
/**
|
|
132
|
+
* Field names the consumer can resolve at call time without them
|
|
133
|
+
* being present in `entry.config` — typically OAuth client_id /
|
|
134
|
+
* client_secret resolved from environment variables or platform
|
|
135
|
+
* config by the host's `resolveCredentials` callback.
|
|
136
|
+
*
|
|
137
|
+
* Required-non-automated fields listed here count as satisfied even
|
|
138
|
+
* when missing from `entry.config`. The default behaviour (no opt
|
|
139
|
+
* passed) requires every such field to live in config, which is
|
|
140
|
+
* correct for self-hosted SDK consumers but wrong for platforms
|
|
141
|
+
* that inject OAuth client credentials at runtime.
|
|
142
|
+
*/
|
|
143
|
+
resolvableFields?: ReadonlyArray<string>;
|
|
144
|
+
}
|
|
145
|
+
|
|
127
146
|
/**
|
|
128
147
|
* "Is this ref ready to call?" answered locally using the cached
|
|
129
148
|
* security-scheme requirements. Mirrors the `complete` boolean
|
|
@@ -138,10 +157,11 @@ export interface RegistryCache {
|
|
|
138
157
|
* signaling "I don't know — caller should fall back to its own
|
|
139
158
|
* heuristic or call `auth-status` to populate the cache".
|
|
140
159
|
* - Cache hit → for every required, non-automated field, checks
|
|
141
|
-
* presence in `entry.config
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
160
|
+
* presence in `entry.config` OR (if `opts.resolvableFields`
|
|
161
|
+
* includes the field name) treats it as satisfied externally.
|
|
162
|
+
* Mirrors the `present || resolvable` check in `auth-status`.
|
|
163
|
+
* `automated` fields (e.g. dynamic OAuth client_id minted by the
|
|
164
|
+
* registry) always count as satisfied.
|
|
145
165
|
*
|
|
146
166
|
* Returning `null` for cache miss is intentional. A boolean would
|
|
147
167
|
* force callers to choose a default that's wrong half the time;
|
|
@@ -150,16 +170,23 @@ export interface RegistryCache {
|
|
|
150
170
|
export function isRefAuthComplete(
|
|
151
171
|
entry: RefEntry,
|
|
152
172
|
cacheEntry: RegistryCacheEntry | undefined,
|
|
173
|
+
opts?: RefAuthCompleteOptions,
|
|
153
174
|
): boolean | null {
|
|
154
175
|
if (typeof entry === "string") return false;
|
|
155
176
|
if ((entry as { mode?: unknown }).mode === "proxy") return true;
|
|
156
177
|
const authFields = cacheEntry?.authFields;
|
|
157
178
|
if (!authFields) return null;
|
|
158
179
|
const config = entry.config ?? {};
|
|
180
|
+
const resolvable =
|
|
181
|
+
opts?.resolvableFields && opts.resolvableFields.length > 0
|
|
182
|
+
? new Set(opts.resolvableFields)
|
|
183
|
+
: null;
|
|
159
184
|
for (const [field, info] of Object.entries(authFields)) {
|
|
160
185
|
if (!info.required) continue;
|
|
161
186
|
if (info.automated) continue;
|
|
162
|
-
if (
|
|
187
|
+
if (field in config) continue;
|
|
188
|
+
if (resolvable && resolvable.has(field)) continue;
|
|
189
|
+
return false;
|
|
163
190
|
}
|
|
164
191
|
return true;
|
|
165
192
|
}
|
package/src/materialize.ts
CHANGED
|
@@ -288,20 +288,58 @@ export async function materializeRef(
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
// 2. Fetch and write resources (skills)
|
|
291
|
+
//
|
|
292
|
+
// `list_resources` returns URIs only — content is omitted to keep the
|
|
293
|
+
// listing payload small. We have to follow up with `read_resources(uris)`
|
|
294
|
+
// to actually fetch the body. Then the per-resource field is `content`,
|
|
295
|
+
// not `text` (per `CallAgentReadResourcesResponse`).
|
|
296
|
+
//
|
|
297
|
+
// Response shape varies depending on the call path: direct calls return
|
|
298
|
+
// `{success, agentPath, resources}` while proxied calls return
|
|
299
|
+
// `{success, result: {success, agentPath, resources}}` (the proxy wraps
|
|
300
|
+
// the inner registry response). Unwrap both shapes the same way.
|
|
291
301
|
try {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
type ResourceListEntry = {
|
|
303
|
+
uri?: string;
|
|
304
|
+
name?: string;
|
|
305
|
+
mimeType?: string;
|
|
306
|
+
};
|
|
307
|
+
type ResourceReadEntry = ResourceListEntry & {
|
|
308
|
+
content?: string;
|
|
309
|
+
error?: string;
|
|
310
|
+
};
|
|
311
|
+
const unwrapResources = <T>(raw: unknown): T[] => {
|
|
312
|
+
const r = raw as Record<string, unknown> | null | undefined;
|
|
313
|
+
if (!r) return [];
|
|
314
|
+
if (Array.isArray(r.resources)) return r.resources as T[];
|
|
315
|
+
const inner = r.result as Record<string, unknown> | undefined;
|
|
316
|
+
if (inner && Array.isArray(inner.resources))
|
|
317
|
+
return inner.resources as T[];
|
|
318
|
+
return [];
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const listed = unwrapResources<ResourceListEntry>(
|
|
322
|
+
await adk.ref.resources(refName),
|
|
323
|
+
);
|
|
324
|
+
const uris = listed
|
|
325
|
+
.map((r) => r.uri)
|
|
326
|
+
.filter((u): u is string => typeof u === "string" && u.length > 0);
|
|
327
|
+
|
|
328
|
+
if (uris.length > 0) {
|
|
329
|
+
const fetched = unwrapResources<ResourceReadEntry>(
|
|
330
|
+
await adk.ref.read(refName, uris),
|
|
331
|
+
);
|
|
332
|
+
for (const resource of fetched) {
|
|
333
|
+
if (!resource.uri) continue;
|
|
334
|
+
if (typeof resource.content !== "string") continue;
|
|
335
|
+
const filename = resource.uri.split("/").pop() || "resource.md";
|
|
336
|
+
ensureWrite(join(skillsDir, filename), resource.content);
|
|
337
|
+
skillCount++;
|
|
301
338
|
}
|
|
302
339
|
}
|
|
303
340
|
} catch {
|
|
304
|
-
// resources fetch failed — might not
|
|
341
|
+
// resources fetch failed — registry might not support resources, or
|
|
342
|
+
// ref isn't authenticated yet. Best-effort only.
|
|
305
343
|
}
|
|
306
344
|
|
|
307
345
|
return { toolCount, skillCount, typesGenerated, docsGenerated };
|