akm-cli 0.0.21 → 0.0.22
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/README.md +8 -5
- package/dist/asset-spec.js +91 -10
- package/dist/cli.js +195 -55
- package/dist/common.js +15 -2
- package/dist/config-cli.js +65 -6
- package/dist/config.js +206 -22
- package/dist/create-provider-registry.js +18 -0
- package/dist/db.js +156 -53
- package/dist/embedder.js +36 -18
- package/dist/errors.js +6 -0
- package/dist/file-context.js +18 -19
- package/dist/frontmatter.js +19 -3
- package/dist/indexer.js +126 -89
- package/dist/{stash-registry.js → installed-kits.js} +16 -24
- package/dist/kit-include.js +108 -0
- package/dist/local-search.js +429 -0
- package/dist/lockfile.js +47 -5
- package/dist/matchers.js +6 -0
- package/dist/metadata.js +20 -10
- package/dist/paths.js +4 -0
- package/dist/providers/skills-sh.js +3 -2
- package/dist/providers/static-index.js +4 -9
- package/dist/registry-build-index.js +356 -0
- package/dist/registry-factory.js +19 -0
- package/dist/registry-install.js +114 -109
- package/dist/registry-resolve.js +44 -9
- package/dist/registry-search.js +14 -9
- package/dist/renderers.js +23 -7
- package/dist/ripgrep-install.js +9 -4
- package/dist/self-update.js +31 -4
- package/dist/stash-add.js +75 -6
- package/dist/stash-clone.js +1 -1
- package/dist/stash-provider-factory.js +52 -0
- package/dist/stash-provider.js +1 -0
- package/dist/stash-providers/filesystem.js +42 -0
- package/dist/stash-providers/index.js +9 -0
- package/dist/stash-providers/openviking.js +337 -0
- package/dist/stash-resolve.js +4 -4
- package/dist/stash-search.js +70 -401
- package/dist/stash-show.js +24 -5
- package/dist/stash-source.js +19 -11
- package/dist/walker.js +15 -10
- package/dist/warn.js +7 -0
- package/package.json +1 -1
- package/dist/provider-registry.js +0 -8
package/dist/config-cli.js
CHANGED
|
@@ -25,6 +25,10 @@ export function parseConfigValue(key, value) {
|
|
|
25
25
|
return { llm: parseLlmConnectionValue(value) };
|
|
26
26
|
case "registries":
|
|
27
27
|
return { registries: parseRegistriesValue(value) };
|
|
28
|
+
case "remoteStashSources":
|
|
29
|
+
return { remoteStashSources: parseStashesValue(value) };
|
|
30
|
+
case "stashes":
|
|
31
|
+
return { stashes: parseStashesValue(value) };
|
|
28
32
|
case "output.format":
|
|
29
33
|
return { output: { format: parseOutputFormat(value) } };
|
|
30
34
|
case "output.detail":
|
|
@@ -47,6 +51,10 @@ export function getConfigValue(config, key) {
|
|
|
47
51
|
return config.llm ?? null;
|
|
48
52
|
case "registries":
|
|
49
53
|
return config.registries ?? DEFAULT_CONFIG.registries ?? [];
|
|
54
|
+
case "remoteStashSources":
|
|
55
|
+
return config.remoteStashSources ?? [];
|
|
56
|
+
case "stashes":
|
|
57
|
+
return config.stashes ?? [];
|
|
50
58
|
case "output.format":
|
|
51
59
|
return config.output?.format ?? null;
|
|
52
60
|
case "output.detail":
|
|
@@ -63,6 +71,8 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
63
71
|
case "embedding":
|
|
64
72
|
case "llm":
|
|
65
73
|
case "registries":
|
|
74
|
+
case "remoteStashSources":
|
|
75
|
+
case "stashes":
|
|
66
76
|
case "output.format":
|
|
67
77
|
case "output.detail":
|
|
68
78
|
return mergeConfigValue(config, parseConfigValue(key, rawValue));
|
|
@@ -80,6 +90,10 @@ export function unsetConfigValue(config, key) {
|
|
|
80
90
|
return { ...config, llm: undefined };
|
|
81
91
|
case "registries":
|
|
82
92
|
return { ...config, registries: undefined };
|
|
93
|
+
case "remoteStashSources":
|
|
94
|
+
return { ...config, remoteStashSources: undefined };
|
|
95
|
+
case "stashes":
|
|
96
|
+
return { ...config, stashes: undefined };
|
|
83
97
|
case "output.format":
|
|
84
98
|
return { ...config, output: mergeOutputConfig(config.output, { format: undefined }) };
|
|
85
99
|
case "output.detail":
|
|
@@ -89,15 +103,24 @@ export function unsetConfigValue(config, key) {
|
|
|
89
103
|
}
|
|
90
104
|
}
|
|
91
105
|
export function listConfig(config) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
const result = {
|
|
107
|
+
semanticSearch: config.semanticSearch,
|
|
108
|
+
registries: config.registries ?? DEFAULT_CONFIG.registries ?? [],
|
|
95
109
|
output: mergeOutputConfig(DEFAULT_CONFIG.output, config.output) ?? null,
|
|
96
110
|
stashDir: config.stashDir ?? null,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
registries: config.registries ?? DEFAULT_CONFIG.registries ?? [],
|
|
111
|
+
installed: config.installed ?? [],
|
|
112
|
+
stashes: config.stashes ?? [],
|
|
100
113
|
};
|
|
114
|
+
if (config.embedding)
|
|
115
|
+
result.embedding = config.embedding;
|
|
116
|
+
if (config.llm)
|
|
117
|
+
result.llm = config.llm;
|
|
118
|
+
// Show legacy keys only if they still have content
|
|
119
|
+
if (config.searchPaths?.length)
|
|
120
|
+
result.searchPaths = config.searchPaths;
|
|
121
|
+
if (config.remoteStashSources?.length)
|
|
122
|
+
result.remoteStashSources = config.remoteStashSources;
|
|
123
|
+
return result;
|
|
101
124
|
}
|
|
102
125
|
function mergeConfigValue(config, partial) {
|
|
103
126
|
return {
|
|
@@ -236,3 +259,39 @@ function parseUnknownPositiveInteger(value, key) {
|
|
|
236
259
|
}
|
|
237
260
|
return value;
|
|
238
261
|
}
|
|
262
|
+
function parseStashesValue(value) {
|
|
263
|
+
if (value === "null" || value === "")
|
|
264
|
+
return undefined;
|
|
265
|
+
let parsed;
|
|
266
|
+
try {
|
|
267
|
+
parsed = JSON.parse(value);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
throw new UsageError(`Invalid value for stashes: expected JSON array of {type, path?, url?, name?, enabled?, options?} objects`);
|
|
271
|
+
}
|
|
272
|
+
if (!Array.isArray(parsed)) {
|
|
273
|
+
throw new UsageError(`Invalid value for stashes: expected a JSON array`);
|
|
274
|
+
}
|
|
275
|
+
return parsed.map((entry, i) => {
|
|
276
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
277
|
+
throw new UsageError(`Invalid value for stashes[${i}]: expected an object with a "type" field`);
|
|
278
|
+
}
|
|
279
|
+
const obj = entry;
|
|
280
|
+
if (typeof obj.type !== "string" || !obj.type) {
|
|
281
|
+
throw new UsageError(`Invalid value for stashes[${i}]: "type" is required`);
|
|
282
|
+
}
|
|
283
|
+
const result = { type: obj.type };
|
|
284
|
+
if (typeof obj.path === "string" && obj.path)
|
|
285
|
+
result.path = obj.path;
|
|
286
|
+
if (typeof obj.url === "string" && obj.url)
|
|
287
|
+
result.url = obj.url;
|
|
288
|
+
if (typeof obj.name === "string" && obj.name)
|
|
289
|
+
result.name = obj.name;
|
|
290
|
+
if (typeof obj.enabled === "boolean")
|
|
291
|
+
result.enabled = obj.enabled;
|
|
292
|
+
if (typeof obj.options === "object" && obj.options !== null && !Array.isArray(obj.options)) {
|
|
293
|
+
result.options = obj.options;
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
});
|
|
297
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -23,10 +23,11 @@ export function getConfigPath() {
|
|
|
23
23
|
}
|
|
24
24
|
// ── Load / Save / Update ────────────────────────────────────────────────────
|
|
25
25
|
let cachedConfig;
|
|
26
|
-
export function loadConfig() {
|
|
26
|
+
export function loadConfig(opts) {
|
|
27
27
|
const configPath = getConfigPath();
|
|
28
|
+
let stat;
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
+
stat = fs.statSync(configPath);
|
|
30
31
|
if (cachedConfig && cachedConfig.path === configPath && cachedConfig.mtime === stat.mtimeMs) {
|
|
31
32
|
return cachedConfig.config;
|
|
32
33
|
}
|
|
@@ -37,10 +38,9 @@ export function loadConfig() {
|
|
|
37
38
|
return { ...DEFAULT_CONFIG };
|
|
38
39
|
}
|
|
39
40
|
const raw = readConfigObject(configPath);
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
// API keys
|
|
43
|
-
// rather than stored in the config file.
|
|
41
|
+
const expanded = raw ? expandEnvVars(raw) : undefined;
|
|
42
|
+
const config = expanded ? pickKnownKeys(expanded) : { ...DEFAULT_CONFIG };
|
|
43
|
+
// Legacy: inject API keys from well-known env vars when not set via ${} substitution
|
|
44
44
|
if (config.embedding && !config.embedding.apiKey) {
|
|
45
45
|
const envKey = process.env.AKM_EMBED_API_KEY?.trim();
|
|
46
46
|
if (envKey)
|
|
@@ -51,23 +51,101 @@ export function loadConfig() {
|
|
|
51
51
|
if (envKey)
|
|
52
52
|
config.llm.apiKey = envKey;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
if (!opts?.readOnly) {
|
|
55
|
+
// Migrate installed[source: "local"] → stashes[type: "filesystem"]
|
|
56
|
+
try {
|
|
57
|
+
migrateLocalInstalledToStashes(config);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.warn("[agentikit] Warning: config migration (local→stashes) failed:", err instanceof Error ? err.message : String(err));
|
|
61
|
+
}
|
|
62
|
+
// Migrate remoteStashSources → stashes[]
|
|
63
|
+
try {
|
|
64
|
+
migrateRemoteStashSourcesToStashes(config);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.warn("[agentikit] Warning: config migration (remoteStashSources→stashes) failed:", err instanceof Error ? err.message : String(err));
|
|
68
|
+
}
|
|
61
69
|
}
|
|
70
|
+
// Cache the parsed config with its path and mtime for subsequent calls.
|
|
71
|
+
// Reuse the stat already obtained above (avoids a second syscall + TOCTOU gap).
|
|
72
|
+
cachedConfig = { config, path: configPath, mtime: stat.mtimeMs };
|
|
62
73
|
return config;
|
|
63
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Migrate installed entries with source "local" to stashes[] as filesystem entries.
|
|
77
|
+
* Local directories are search paths, not registry kits — they don't need version
|
|
78
|
+
* tracking, cache management, or update support.
|
|
79
|
+
*
|
|
80
|
+
* Mutates the config in place and persists to disk if any entries are migrated.
|
|
81
|
+
*/
|
|
82
|
+
function migrateLocalInstalledToStashes(config) {
|
|
83
|
+
const installed = config.installed;
|
|
84
|
+
if (!installed)
|
|
85
|
+
return;
|
|
86
|
+
const localEntries = installed.filter((e) => e.source === "local");
|
|
87
|
+
if (localEntries.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
const stashes = [...(config.stashes ?? [])];
|
|
90
|
+
const existingPaths = new Set(stashes.filter((s) => !!s.path).map((s) => path.resolve(s.path)));
|
|
91
|
+
let migrated = 0;
|
|
92
|
+
for (const entry of localEntries) {
|
|
93
|
+
const resolved = path.resolve(entry.stashRoot);
|
|
94
|
+
if (existingPaths.has(resolved))
|
|
95
|
+
continue;
|
|
96
|
+
stashes.push({
|
|
97
|
+
type: "filesystem",
|
|
98
|
+
path: resolved,
|
|
99
|
+
name: entry.id,
|
|
100
|
+
});
|
|
101
|
+
existingPaths.add(resolved);
|
|
102
|
+
migrated++;
|
|
103
|
+
}
|
|
104
|
+
if (migrated === 0)
|
|
105
|
+
return;
|
|
106
|
+
// Remove local entries from installed, add to stashes
|
|
107
|
+
config.installed = installed.filter((e) => e.source !== "local");
|
|
108
|
+
config.stashes = stashes;
|
|
109
|
+
saveConfig(config);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Migrate remoteStashSources[] to stashes[] entries.
|
|
113
|
+
* Each remote source becomes a typed stash entry (e.g. type: "openviking").
|
|
114
|
+
*
|
|
115
|
+
* Mutates the config in place and persists to disk if any entries are migrated.
|
|
116
|
+
*/
|
|
117
|
+
function migrateRemoteStashSourcesToStashes(config) {
|
|
118
|
+
const remoteSources = config.remoteStashSources;
|
|
119
|
+
if (!remoteSources || remoteSources.length === 0)
|
|
120
|
+
return;
|
|
121
|
+
const stashes = [...(config.stashes ?? [])];
|
|
122
|
+
const existingUrls = new Set(stashes.filter((s) => !!s.url).map((s) => s.url));
|
|
123
|
+
let migrated = 0;
|
|
124
|
+
for (const entry of remoteSources) {
|
|
125
|
+
if (!entry.url || existingUrls.has(entry.url))
|
|
126
|
+
continue;
|
|
127
|
+
stashes.push({
|
|
128
|
+
type: entry.type ?? "openviking",
|
|
129
|
+
url: entry.url,
|
|
130
|
+
name: entry.name,
|
|
131
|
+
options: entry.options,
|
|
132
|
+
});
|
|
133
|
+
existingUrls.add(entry.url);
|
|
134
|
+
migrated++;
|
|
135
|
+
}
|
|
136
|
+
if (migrated === 0)
|
|
137
|
+
return;
|
|
138
|
+
config.stashes = stashes;
|
|
139
|
+
config.remoteStashSources = undefined;
|
|
140
|
+
saveConfig(config);
|
|
141
|
+
}
|
|
64
142
|
export function saveConfig(config) {
|
|
65
143
|
cachedConfig = undefined;
|
|
66
144
|
const configPath = getConfigPath();
|
|
67
145
|
const dir = path.dirname(configPath);
|
|
68
146
|
fs.mkdirSync(dir, { recursive: true });
|
|
69
147
|
const sanitized = sanitizeConfigForWrite(config);
|
|
70
|
-
const tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
148
|
+
const tmpPath = `${configPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
71
149
|
try {
|
|
72
150
|
fs.writeFileSync(tmpPath, `${JSON.stringify(sanitized, null, 2)}\n`, "utf8");
|
|
73
151
|
fs.renameSync(tmpPath, configPath);
|
|
@@ -89,19 +167,37 @@ export function saveConfig(config) {
|
|
|
89
167
|
*/
|
|
90
168
|
function sanitizeConfigForWrite(config) {
|
|
91
169
|
const sanitized = { ...config };
|
|
92
|
-
if (
|
|
93
|
-
const { apiKey, ...rest } =
|
|
170
|
+
if (config.embedding) {
|
|
171
|
+
const { apiKey, ...rest } = config.embedding;
|
|
94
172
|
sanitized.embedding = rest;
|
|
95
173
|
}
|
|
96
|
-
if (
|
|
97
|
-
const { apiKey, ...rest } =
|
|
174
|
+
if (config.llm) {
|
|
175
|
+
const { apiKey, ...rest } = config.llm;
|
|
98
176
|
sanitized.llm = rest;
|
|
99
177
|
}
|
|
178
|
+
// Drop empty/migrated keys to keep config clean
|
|
179
|
+
if (!config.searchPaths?.length)
|
|
180
|
+
delete sanitized.searchPaths;
|
|
181
|
+
if (!config.remoteStashSources?.length)
|
|
182
|
+
delete sanitized.remoteStashSources;
|
|
100
183
|
return sanitized;
|
|
101
184
|
}
|
|
102
185
|
export function updateConfig(partial) {
|
|
103
186
|
const current = loadConfig();
|
|
187
|
+
// Shallow-merge for top-level scalar fields; deep-merge known object-type config keys.
|
|
104
188
|
const merged = { ...current, ...partial };
|
|
189
|
+
// Deep-merge output — partial update should not wipe sibling keys
|
|
190
|
+
if (current.output && partial.output && partial.output !== current.output) {
|
|
191
|
+
merged.output = { ...current.output, ...partial.output };
|
|
192
|
+
}
|
|
193
|
+
// Deep-merge embedding — only when both sides are objects and partial does not intend to clear
|
|
194
|
+
if (current.embedding && partial.embedding && partial.embedding !== current.embedding) {
|
|
195
|
+
merged.embedding = { ...current.embedding, ...partial.embedding };
|
|
196
|
+
}
|
|
197
|
+
// Deep-merge llm — same pattern
|
|
198
|
+
if (current.llm && partial.llm && partial.llm !== current.llm) {
|
|
199
|
+
merged.llm = { ...current.llm, ...partial.llm };
|
|
200
|
+
}
|
|
105
201
|
saveConfig(merged);
|
|
106
202
|
return merged;
|
|
107
203
|
}
|
|
@@ -129,6 +225,12 @@ function pickKnownKeys(raw) {
|
|
|
129
225
|
const registries = parseRegistriesConfig(raw.registries);
|
|
130
226
|
if (registries)
|
|
131
227
|
config.registries = registries;
|
|
228
|
+
const remoteStash = parseStashesConfig(raw.remoteStashSources);
|
|
229
|
+
if (remoteStash)
|
|
230
|
+
config.remoteStashSources = remoteStash;
|
|
231
|
+
const stashes = parseStashesConfig(raw.stashes);
|
|
232
|
+
if (stashes)
|
|
233
|
+
config.stashes = stashes;
|
|
132
234
|
const output = parseOutputConfig(raw.output);
|
|
133
235
|
if (output)
|
|
134
236
|
config.output = output;
|
|
@@ -147,6 +249,49 @@ function parseOutputConfig(value) {
|
|
|
147
249
|
}
|
|
148
250
|
return Object.keys(output).length > 0 ? output : undefined;
|
|
149
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Field names that hold URLs and must NOT have env var substitution applied.
|
|
254
|
+
* Expanding ${VAR} inside a URL could leak secrets by redirecting requests to
|
|
255
|
+
* an attacker-controlled server if the config file is world-readable.
|
|
256
|
+
*/
|
|
257
|
+
const URL_FIELD_NAMES = new Set(["url", "endpoint", "artifactUrl"]);
|
|
258
|
+
/**
|
|
259
|
+
* Recursively expand `${VAR}` references in all string values.
|
|
260
|
+
* Supports `${VAR}`, `${VAR:-default}`, and bare `$VAR` at the start of a value.
|
|
261
|
+
* Non-string values pass through unchanged.
|
|
262
|
+
*
|
|
263
|
+
* URL-type fields (named `url`, `endpoint`, `artifactUrl`, or whose value starts
|
|
264
|
+
* with `http://` / `https://`) are skipped to prevent secret injection into URLs.
|
|
265
|
+
*/
|
|
266
|
+
function expandEnvVars(value, fieldName) {
|
|
267
|
+
if (typeof value === "string") {
|
|
268
|
+
// Skip URL-type fields by name or by value prefix
|
|
269
|
+
if ((fieldName !== undefined && URL_FIELD_NAMES.has(fieldName)) ||
|
|
270
|
+
value.startsWith("http://") ||
|
|
271
|
+
value.startsWith("https://")) {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
return value.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, braced, bare) => {
|
|
275
|
+
if (braced) {
|
|
276
|
+
const [name, ...rest] = braced.split(":-");
|
|
277
|
+
const fallback = rest.join(":-");
|
|
278
|
+
return process.env[name] ?? fallback ?? "";
|
|
279
|
+
}
|
|
280
|
+
return process.env[bare] ?? "";
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(value)) {
|
|
284
|
+
return value.map((item) => expandEnvVars(item));
|
|
285
|
+
}
|
|
286
|
+
if (value !== null && typeof value === "object") {
|
|
287
|
+
const out = {};
|
|
288
|
+
for (const [k, v] of Object.entries(value)) {
|
|
289
|
+
out[k] = expandEnvVars(v, k);
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
150
295
|
function readConfigObject(configPath) {
|
|
151
296
|
try {
|
|
152
297
|
const text = fs.readFileSync(configPath, "utf8");
|
|
@@ -168,7 +313,6 @@ export function stripJsonComments(text) {
|
|
|
168
313
|
let result = "";
|
|
169
314
|
let i = 0;
|
|
170
315
|
let inString = false;
|
|
171
|
-
let stringChar = "";
|
|
172
316
|
while (i < text.length) {
|
|
173
317
|
if (inString) {
|
|
174
318
|
if (text[i] === "\\") {
|
|
@@ -176,16 +320,16 @@ export function stripJsonComments(text) {
|
|
|
176
320
|
i += 2;
|
|
177
321
|
continue;
|
|
178
322
|
}
|
|
179
|
-
if (text[i] ===
|
|
323
|
+
if (text[i] === '"') {
|
|
180
324
|
inString = false;
|
|
181
325
|
}
|
|
182
326
|
result += text[i];
|
|
183
327
|
i++;
|
|
184
328
|
continue;
|
|
185
329
|
}
|
|
186
|
-
|
|
330
|
+
// JSON only uses double-quoted strings; single quotes are not valid JSON
|
|
331
|
+
if (text[i] === '"') {
|
|
187
332
|
inString = true;
|
|
188
|
-
stringChar = text[i];
|
|
189
333
|
result += text[i];
|
|
190
334
|
i++;
|
|
191
335
|
continue;
|
|
@@ -213,6 +357,10 @@ function parseEmbeddingConfig(value) {
|
|
|
213
357
|
const obj = value;
|
|
214
358
|
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
215
359
|
return undefined;
|
|
360
|
+
if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
|
|
361
|
+
console.warn(`[agentikit] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
216
364
|
if (typeof obj.model !== "string" || !obj.model)
|
|
217
365
|
return undefined;
|
|
218
366
|
const result = {
|
|
@@ -242,6 +390,10 @@ function parseLlmConfig(value) {
|
|
|
242
390
|
const obj = value;
|
|
243
391
|
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
244
392
|
return undefined;
|
|
393
|
+
if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
|
|
394
|
+
console.warn(`[agentikit] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
245
397
|
if (typeof obj.model !== "string" || !obj.model)
|
|
246
398
|
return undefined;
|
|
247
399
|
const result = {
|
|
@@ -324,6 +476,38 @@ function parseRegistriesConfig(value) {
|
|
|
324
476
|
// which overrides the default. Only return undefined if the field was not an array.
|
|
325
477
|
return entries;
|
|
326
478
|
}
|
|
479
|
+
function parseStashesConfig(value) {
|
|
480
|
+
if (!Array.isArray(value))
|
|
481
|
+
return undefined;
|
|
482
|
+
const entries = value
|
|
483
|
+
.map((entry) => parseStashConfigEntry(entry))
|
|
484
|
+
.filter((entry) => entry !== undefined);
|
|
485
|
+
return entries;
|
|
486
|
+
}
|
|
487
|
+
function parseStashConfigEntry(value) {
|
|
488
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
489
|
+
return undefined;
|
|
490
|
+
const obj = value;
|
|
491
|
+
const type = asNonEmptyString(obj.type);
|
|
492
|
+
if (!type)
|
|
493
|
+
return undefined;
|
|
494
|
+
const entry = { type };
|
|
495
|
+
const entryPath = asNonEmptyString(obj.path);
|
|
496
|
+
if (entryPath)
|
|
497
|
+
entry.path = entryPath;
|
|
498
|
+
const url = asNonEmptyString(obj.url);
|
|
499
|
+
if (url)
|
|
500
|
+
entry.url = url;
|
|
501
|
+
const name = asNonEmptyString(obj.name);
|
|
502
|
+
if (name)
|
|
503
|
+
entry.name = name;
|
|
504
|
+
if (typeof obj.enabled === "boolean")
|
|
505
|
+
entry.enabled = obj.enabled;
|
|
506
|
+
if (typeof obj.options === "object" && obj.options !== null && !Array.isArray(obj.options)) {
|
|
507
|
+
entry.options = obj.options;
|
|
508
|
+
}
|
|
509
|
+
return entry;
|
|
510
|
+
}
|
|
327
511
|
function parseRegistryConfigEntry(value) {
|
|
328
512
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
329
513
|
return undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic factory-map utility.
|
|
3
|
+
*
|
|
4
|
+
* Creates a lightweight registry that maps string keys to factory functions.
|
|
5
|
+
* Both registry-factory.ts (kit discovery) and stash-provider-factory.ts
|
|
6
|
+
* (stash source providers) are built on this utility.
|
|
7
|
+
*/
|
|
8
|
+
export function createProviderRegistry() {
|
|
9
|
+
const map = new Map();
|
|
10
|
+
return {
|
|
11
|
+
register(type, factory) {
|
|
12
|
+
map.set(type, factory);
|
|
13
|
+
},
|
|
14
|
+
resolve(type) {
|
|
15
|
+
return map.get(type) ?? null;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|