@dexto/agent-management 1.3.0 → 1.5.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/AgentFactory.cjs +152 -0
- package/dist/AgentFactory.d.ts +121 -0
- package/dist/AgentFactory.d.ts.map +1 -0
- package/dist/AgentFactory.js +132 -0
- package/dist/AgentManager.cjs +226 -0
- package/dist/AgentManager.d.ts +191 -0
- package/dist/AgentManager.d.ts.map +1 -0
- package/dist/AgentManager.js +192 -0
- package/dist/config/config-enrichment.cjs +23 -3
- package/dist/config/config-enrichment.d.ts +20 -5
- package/dist/config/config-enrichment.d.ts.map +1 -1
- package/dist/config/config-enrichment.js +22 -3
- package/dist/config/config-manager.cjs +340 -3
- package/dist/config/config-manager.d.ts +158 -7
- package/dist/config/config-manager.d.ts.map +1 -1
- package/dist/config/config-manager.js +325 -3
- package/dist/config/discover-prompts.cjs +103 -0
- package/dist/config/discover-prompts.d.ts +28 -0
- package/dist/config/discover-prompts.d.ts.map +1 -0
- package/dist/config/discover-prompts.js +73 -0
- package/dist/config/errors.cjs +2 -2
- package/dist/config/errors.js +2 -2
- package/dist/config/index.cjs +14 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +21 -3
- package/dist/index.cjs +109 -6
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +111 -6
- package/dist/installation.cjs +239 -0
- package/dist/installation.d.ts +72 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +202 -0
- package/dist/models/custom-models.cjs +157 -0
- package/dist/models/custom-models.d.ts +94 -0
- package/dist/models/custom-models.d.ts.map +1 -0
- package/dist/models/custom-models.js +117 -0
- package/dist/models/index.cjs +89 -0
- package/dist/models/index.d.ts +11 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +68 -0
- package/dist/models/path-resolver.cjs +154 -0
- package/dist/models/path-resolver.d.ts +77 -0
- package/dist/models/path-resolver.d.ts.map +1 -0
- package/dist/models/path-resolver.js +108 -0
- package/dist/models/state-manager.cjs +220 -0
- package/dist/models/state-manager.d.ts +138 -0
- package/dist/models/state-manager.d.ts.map +1 -0
- package/dist/models/state-manager.js +184 -0
- package/dist/preferences/error-codes.cjs +2 -0
- package/dist/preferences/error-codes.d.ts +3 -1
- package/dist/preferences/error-codes.d.ts.map +1 -1
- package/dist/preferences/error-codes.js +2 -0
- package/dist/preferences/index.d.ts +1 -1
- package/dist/preferences/index.d.ts.map +1 -1
- package/dist/preferences/loader.cjs +32 -6
- package/dist/preferences/loader.d.ts +23 -4
- package/dist/preferences/loader.d.ts.map +1 -1
- package/dist/preferences/loader.js +32 -6
- package/dist/preferences/schemas.cjs +21 -3
- package/dist/preferences/schemas.d.ts +52 -24
- package/dist/preferences/schemas.d.ts.map +1 -1
- package/dist/preferences/schemas.js +28 -4
- package/dist/registry/registry.cjs +28 -45
- package/dist/registry/registry.d.ts +8 -6
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +26 -44
- package/dist/registry/types.d.ts +11 -13
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/resolver.cjs +82 -43
- package/dist/resolver.d.ts +7 -5
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +83 -44
- package/dist/utils/api-key-resolver.cjs +19 -1
- package/dist/utils/api-key-resolver.d.ts.map +1 -1
- package/dist/utils/api-key-resolver.js +19 -1
- package/dist/utils/api-key-store.cjs +46 -0
- package/dist/utils/api-key-store.d.ts +27 -0
- package/dist/utils/api-key-store.d.ts.map +1 -1
- package/dist/utils/api-key-store.js +44 -0
- package/dist/utils/env-file.cjs +20 -68
- package/dist/utils/env-file.d.ts +2 -1
- package/dist/utils/env-file.d.ts.map +1 -1
- package/dist/utils/env-file.js +20 -68
- package/dist/writer.cjs +20 -2
- package/dist/writer.d.ts +1 -0
- package/dist/writer.d.ts.map +1 -1
- package/dist/writer.js +20 -2
- package/package.json +2 -2
- package/dist/AgentOrchestrator.cjs +0 -263
- package/dist/AgentOrchestrator.d.ts +0 -191
- package/dist/AgentOrchestrator.d.ts.map +0 -1
- package/dist/AgentOrchestrator.js +0 -239
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AgentMetadata } from './AgentManager.js';
|
|
2
|
+
export interface InstallOptions {
|
|
3
|
+
/** Directory where agents are stored (default: ~/.dexto/agents) */
|
|
4
|
+
agentsDir?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Install agent from bundled registry to local directory
|
|
8
|
+
*
|
|
9
|
+
* @param agentId ID of the agent to install from bundled registry
|
|
10
|
+
* @param options Installation options
|
|
11
|
+
* @returns Path to the installed agent's main config file
|
|
12
|
+
*
|
|
13
|
+
* @throws {DextoRuntimeError} If agent not found in bundled registry or installation fails
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* await installBundledAgent('coding-agent');
|
|
18
|
+
* console.log('Agent installed to ~/.dexto/agents/coding-agent');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function installBundledAgent(agentId: string, options?: InstallOptions): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Install custom agent from local path
|
|
24
|
+
*
|
|
25
|
+
* @param agentId Unique ID for the custom agent
|
|
26
|
+
* @param sourcePath Absolute path to agent YAML file or directory
|
|
27
|
+
* @param metadata Agent metadata (name, description, author, tags)
|
|
28
|
+
* @param options Installation options
|
|
29
|
+
* @returns Path to the installed agent's main config file
|
|
30
|
+
*
|
|
31
|
+
* @throws {DextoRuntimeError} If agent ID already exists or installation fails
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* await installCustomAgent('my-agent', '/path/to/agent.yml', {
|
|
36
|
+
* name: 'My Agent',
|
|
37
|
+
* description: 'Custom agent for my use case',
|
|
38
|
+
* author: 'John Doe',
|
|
39
|
+
* tags: ['custom']
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function installCustomAgent(agentId: string, sourcePath: string, metadata: Pick<AgentMetadata, 'name' | 'description' | 'author' | 'tags'>, options?: InstallOptions): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Uninstall agent by removing it from disk and user registry
|
|
46
|
+
*
|
|
47
|
+
* @param agentId ID of the agent to uninstall
|
|
48
|
+
* @param options Installation options
|
|
49
|
+
*
|
|
50
|
+
* @throws {DextoRuntimeError} If agent not installed
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* await uninstallAgent('my-custom-agent');
|
|
55
|
+
* console.log('Agent uninstalled');
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function uninstallAgent(agentId: string, options?: InstallOptions): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* List installed agents
|
|
61
|
+
*
|
|
62
|
+
* @param options Installation options
|
|
63
|
+
* @returns Array of installed agent IDs
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const installed = await listInstalledAgents();
|
|
68
|
+
* console.log(installed); // ['coding-agent', 'my-custom-agent']
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function listInstalledAgents(options?: InstallOptions): Promise<string[]>;
|
|
72
|
+
//# sourceMappingURL=installation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installation.d.ts","sourceRoot":"","sources":["../src/installation.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC3B,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAuCD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,CAAC,CA6FjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CACpC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC,EACzE,OAAO,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,CAAC,CA2FjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B7F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYrF"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { logger } from "@dexto/core";
|
|
4
|
+
import { getDextoGlobalPath, resolveBundledScript, copyDirectory } from "./utils/path.js";
|
|
5
|
+
import { RegistryError } from "./registry/errors.js";
|
|
6
|
+
import { ConfigError } from "./config/errors.js";
|
|
7
|
+
function getAgentsDir(options) {
|
|
8
|
+
return options?.agentsDir ?? getDextoGlobalPath("agents");
|
|
9
|
+
}
|
|
10
|
+
function getUserRegistryPath(agentsDir) {
|
|
11
|
+
return path.join(agentsDir, "registry.json");
|
|
12
|
+
}
|
|
13
|
+
async function loadUserRegistry(registryPath) {
|
|
14
|
+
try {
|
|
15
|
+
const content = await fs.readFile(registryPath, "utf-8");
|
|
16
|
+
return JSON.parse(content);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === "ENOENT") {
|
|
19
|
+
return { agents: [] };
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function saveUserRegistry(registryPath, registry) {
|
|
25
|
+
await fs.mkdir(path.dirname(registryPath), { recursive: true });
|
|
26
|
+
await fs.writeFile(registryPath, JSON.stringify(registry, null, 2));
|
|
27
|
+
}
|
|
28
|
+
async function installBundledAgent(agentId, options) {
|
|
29
|
+
const agentsDir = getAgentsDir(options);
|
|
30
|
+
const bundledRegistryPath = resolveBundledScript("agents/agent-registry.json");
|
|
31
|
+
logger.info(`Installing agent: ${agentId}`);
|
|
32
|
+
let bundledRegistry;
|
|
33
|
+
try {
|
|
34
|
+
const content = await fs.readFile(bundledRegistryPath, "utf-8");
|
|
35
|
+
bundledRegistry = JSON.parse(content);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw RegistryError.registryParseError(
|
|
38
|
+
bundledRegistryPath,
|
|
39
|
+
error instanceof Error ? error.message : String(error)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const agentEntry = bundledRegistry.agents[agentId];
|
|
43
|
+
if (!agentEntry) {
|
|
44
|
+
const available = Object.keys(bundledRegistry.agents);
|
|
45
|
+
throw RegistryError.agentNotFound(agentId, available);
|
|
46
|
+
}
|
|
47
|
+
const targetDir = path.join(agentsDir, agentId);
|
|
48
|
+
try {
|
|
49
|
+
await fs.access(targetDir);
|
|
50
|
+
logger.info(`Agent '${agentId}' already installed`);
|
|
51
|
+
const mainFile = agentEntry.main || path.basename(agentEntry.source);
|
|
52
|
+
return path.join(targetDir, mainFile);
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
56
|
+
const sourcePath = resolveBundledScript(`agents/${agentEntry.source}`);
|
|
57
|
+
const tempDir = `${targetDir}.tmp.${Date.now()}`;
|
|
58
|
+
try {
|
|
59
|
+
if (agentEntry.source.endsWith("/")) {
|
|
60
|
+
await copyDirectory(sourcePath, tempDir);
|
|
61
|
+
} else {
|
|
62
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
63
|
+
const targetFile = path.join(tempDir, path.basename(sourcePath));
|
|
64
|
+
await fs.copyFile(sourcePath, targetFile);
|
|
65
|
+
}
|
|
66
|
+
await fs.rename(tempDir, targetDir);
|
|
67
|
+
logger.info(`\u2713 Installed agent '${agentId}' to ${targetDir}`);
|
|
68
|
+
const userRegistryPath = getUserRegistryPath(agentsDir);
|
|
69
|
+
const userRegistry = await loadUserRegistry(userRegistryPath);
|
|
70
|
+
if (!userRegistry.agents.some((a) => a.id === agentId)) {
|
|
71
|
+
const mainFile = agentEntry.main || path.basename(agentEntry.source);
|
|
72
|
+
userRegistry.agents.push({
|
|
73
|
+
id: agentId,
|
|
74
|
+
name: agentEntry.name,
|
|
75
|
+
description: agentEntry.description,
|
|
76
|
+
configPath: `./${agentId}/${mainFile}`,
|
|
77
|
+
author: agentEntry.author,
|
|
78
|
+
tags: agentEntry.tags
|
|
79
|
+
});
|
|
80
|
+
await saveUserRegistry(userRegistryPath, userRegistry);
|
|
81
|
+
}
|
|
82
|
+
return path.join(targetDir, agentEntry.main || path.basename(agentEntry.source));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
try {
|
|
85
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
throw RegistryError.installationFailed(
|
|
89
|
+
agentId,
|
|
90
|
+
error instanceof Error ? error.message : String(error)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function installCustomAgent(agentId, sourcePath, metadata, options) {
|
|
95
|
+
const agentsDir = getAgentsDir(options);
|
|
96
|
+
const targetDir = path.join(agentsDir, agentId);
|
|
97
|
+
logger.info(`Installing custom agent: ${agentId}`);
|
|
98
|
+
try {
|
|
99
|
+
const bundledRegistryPath = resolveBundledScript("agents/agent-registry.json");
|
|
100
|
+
const bundledContent = await fs.readFile(bundledRegistryPath, "utf-8");
|
|
101
|
+
const bundledRegistry = JSON.parse(bundledContent);
|
|
102
|
+
if (agentId in bundledRegistry.agents) {
|
|
103
|
+
throw RegistryError.customAgentNameConflict(agentId);
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof Error && error.message.includes("conflicts with builtin")) {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
logger.debug(
|
|
110
|
+
`Could not validate against bundled registry: ${error instanceof Error ? error.message : String(error)}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await fs.access(targetDir);
|
|
115
|
+
throw RegistryError.agentAlreadyExists(agentId);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error.code !== "ENOENT") {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const resolvedSource = path.resolve(sourcePath);
|
|
122
|
+
let stat;
|
|
123
|
+
try {
|
|
124
|
+
stat = await fs.stat(resolvedSource);
|
|
125
|
+
} catch (_error) {
|
|
126
|
+
throw ConfigError.fileNotFound(resolvedSource);
|
|
127
|
+
}
|
|
128
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
129
|
+
try {
|
|
130
|
+
if (stat.isDirectory()) {
|
|
131
|
+
await copyDirectory(resolvedSource, targetDir);
|
|
132
|
+
} else {
|
|
133
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
134
|
+
const filename = path.basename(resolvedSource);
|
|
135
|
+
await fs.copyFile(resolvedSource, path.join(targetDir, filename));
|
|
136
|
+
}
|
|
137
|
+
logger.info(`\u2713 Installed custom agent '${agentId}' to ${targetDir}`);
|
|
138
|
+
const userRegistryPath = getUserRegistryPath(agentsDir);
|
|
139
|
+
const userRegistry = await loadUserRegistry(userRegistryPath);
|
|
140
|
+
const configFile = stat.isDirectory() ? "agent.yml" : path.basename(resolvedSource);
|
|
141
|
+
userRegistry.agents.push({
|
|
142
|
+
id: agentId,
|
|
143
|
+
name: metadata.name || agentId,
|
|
144
|
+
description: metadata.description,
|
|
145
|
+
configPath: `./${agentId}/${configFile}`,
|
|
146
|
+
author: metadata.author,
|
|
147
|
+
tags: metadata.tags || []
|
|
148
|
+
});
|
|
149
|
+
await saveUserRegistry(userRegistryPath, userRegistry);
|
|
150
|
+
return path.join(targetDir, configFile);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
try {
|
|
153
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
throw RegistryError.installationFailed(
|
|
157
|
+
agentId,
|
|
158
|
+
error instanceof Error ? error.message : String(error)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function uninstallAgent(agentId, options) {
|
|
163
|
+
const agentsDir = getAgentsDir(options);
|
|
164
|
+
const targetDir = path.join(agentsDir, agentId);
|
|
165
|
+
logger.info(`Uninstalling agent: ${agentId}`);
|
|
166
|
+
try {
|
|
167
|
+
await fs.access(targetDir);
|
|
168
|
+
} catch (_error) {
|
|
169
|
+
throw RegistryError.agentNotInstalled(agentId);
|
|
170
|
+
}
|
|
171
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
172
|
+
logger.info(`\u2713 Removed agent directory: ${targetDir}`);
|
|
173
|
+
const userRegistryPath = getUserRegistryPath(agentsDir);
|
|
174
|
+
try {
|
|
175
|
+
const userRegistry = await loadUserRegistry(userRegistryPath);
|
|
176
|
+
userRegistry.agents = userRegistry.agents.filter((a) => a.id !== agentId);
|
|
177
|
+
await saveUserRegistry(userRegistryPath, userRegistry);
|
|
178
|
+
logger.info(`\u2713 Removed '${agentId}' from user registry`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.warn(
|
|
181
|
+
`Failed to update user registry: ${error instanceof Error ? error.message : String(error)}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function listInstalledAgents(options) {
|
|
186
|
+
const agentsDir = getAgentsDir(options);
|
|
187
|
+
try {
|
|
188
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
189
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (error.code === "ENOENT") {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export {
|
|
198
|
+
installBundledAgent,
|
|
199
|
+
installCustomAgent,
|
|
200
|
+
listInstalledAgents,
|
|
201
|
+
uninstallAgent
|
|
202
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var custom_models_exports = {};
|
|
30
|
+
__export(custom_models_exports, {
|
|
31
|
+
CUSTOM_MODEL_PROVIDERS: () => CUSTOM_MODEL_PROVIDERS,
|
|
32
|
+
CustomModelSchema: () => CustomModelSchema,
|
|
33
|
+
deleteCustomModel: () => deleteCustomModel,
|
|
34
|
+
getCustomModel: () => getCustomModel,
|
|
35
|
+
getCustomModelsPath: () => getCustomModelsPath,
|
|
36
|
+
loadCustomModels: () => loadCustomModels,
|
|
37
|
+
saveCustomModel: () => saveCustomModel
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(custom_models_exports);
|
|
40
|
+
var import_zod = require("zod");
|
|
41
|
+
var import_fs = require("fs");
|
|
42
|
+
var path = __toESM(require("path"), 1);
|
|
43
|
+
var import_path = require("../utils/path.js");
|
|
44
|
+
const CUSTOM_MODEL_PROVIDERS = [
|
|
45
|
+
"openai-compatible",
|
|
46
|
+
"openrouter",
|
|
47
|
+
"litellm",
|
|
48
|
+
"glama",
|
|
49
|
+
"bedrock",
|
|
50
|
+
"ollama",
|
|
51
|
+
"local",
|
|
52
|
+
"vertex"
|
|
53
|
+
];
|
|
54
|
+
const CustomModelSchema = import_zod.z.object({
|
|
55
|
+
name: import_zod.z.string().min(1),
|
|
56
|
+
provider: import_zod.z.enum(CUSTOM_MODEL_PROVIDERS).default("openai-compatible"),
|
|
57
|
+
baseURL: import_zod.z.string().url().optional(),
|
|
58
|
+
displayName: import_zod.z.string().optional(),
|
|
59
|
+
maxInputTokens: import_zod.z.number().int().positive().optional(),
|
|
60
|
+
maxOutputTokens: import_zod.z.number().int().positive().optional(),
|
|
61
|
+
// Optional per-model API key. For openai-compatible this is the primary key source.
|
|
62
|
+
// For litellm/glama/openrouter this overrides the provider-level env var key.
|
|
63
|
+
apiKey: import_zod.z.string().optional(),
|
|
64
|
+
// File path for local GGUF models. Required when provider is 'local'.
|
|
65
|
+
// Stores the absolute path to the .gguf file on disk.
|
|
66
|
+
filePath: import_zod.z.string().optional()
|
|
67
|
+
}).superRefine((data, ctx) => {
|
|
68
|
+
if ((data.provider === "openai-compatible" || data.provider === "litellm") && !data.baseURL) {
|
|
69
|
+
ctx.addIssue({
|
|
70
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
71
|
+
path: ["baseURL"],
|
|
72
|
+
message: `Base URL is required for ${data.provider} provider`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (data.provider === "local" && !data.filePath) {
|
|
76
|
+
ctx.addIssue({
|
|
77
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
78
|
+
path: ["filePath"],
|
|
79
|
+
message: "File path is required for local provider"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (data.provider === "local" && data.filePath && !data.filePath.endsWith(".gguf")) {
|
|
83
|
+
ctx.addIssue({
|
|
84
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
85
|
+
path: ["filePath"],
|
|
86
|
+
message: "File path must be a .gguf file"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const StorageSchema = import_zod.z.object({
|
|
91
|
+
version: import_zod.z.literal(1),
|
|
92
|
+
models: import_zod.z.array(CustomModelSchema)
|
|
93
|
+
});
|
|
94
|
+
function getCustomModelsPath() {
|
|
95
|
+
return (0, import_path.getDextoGlobalPath)("models", "custom-models.json");
|
|
96
|
+
}
|
|
97
|
+
async function loadCustomModels() {
|
|
98
|
+
const filePath = getCustomModelsPath();
|
|
99
|
+
try {
|
|
100
|
+
const content = await import_fs.promises.readFile(filePath, "utf-8");
|
|
101
|
+
const parsed = StorageSchema.safeParse(JSON.parse(content));
|
|
102
|
+
if (!parsed.success) {
|
|
103
|
+
console.warn(
|
|
104
|
+
`[custom-models] Failed to parse ${filePath}: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
|
105
|
+
);
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
return parsed.data.models;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error.code === "ENOENT") {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function saveCustomModel(model) {
|
|
117
|
+
const parsed = CustomModelSchema.safeParse(model);
|
|
118
|
+
if (!parsed.success) {
|
|
119
|
+
throw new Error(`Invalid model: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
120
|
+
}
|
|
121
|
+
const models = await loadCustomModels();
|
|
122
|
+
const existingIndex = models.findIndex((m) => m.name === parsed.data.name);
|
|
123
|
+
if (existingIndex >= 0) {
|
|
124
|
+
models[existingIndex] = parsed.data;
|
|
125
|
+
} else {
|
|
126
|
+
models.push(parsed.data);
|
|
127
|
+
}
|
|
128
|
+
await writeCustomModels(models);
|
|
129
|
+
}
|
|
130
|
+
async function deleteCustomModel(name) {
|
|
131
|
+
const models = await loadCustomModels();
|
|
132
|
+
const filtered = models.filter((m) => m.name !== name);
|
|
133
|
+
if (filtered.length === models.length) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
await writeCustomModels(filtered);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
async function getCustomModel(name) {
|
|
140
|
+
const models = await loadCustomModels();
|
|
141
|
+
return models.find((m) => m.name === name) ?? null;
|
|
142
|
+
}
|
|
143
|
+
async function writeCustomModels(models) {
|
|
144
|
+
const filePath = getCustomModelsPath();
|
|
145
|
+
await import_fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
146
|
+
await import_fs.promises.writeFile(filePath, JSON.stringify({ version: 1, models }, null, 2), "utf-8");
|
|
147
|
+
}
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
CUSTOM_MODEL_PROVIDERS,
|
|
151
|
+
CustomModelSchema,
|
|
152
|
+
deleteCustomModel,
|
|
153
|
+
getCustomModel,
|
|
154
|
+
getCustomModelsPath,
|
|
155
|
+
loadCustomModels,
|
|
156
|
+
saveCustomModel
|
|
157
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Models Persistence
|
|
3
|
+
*
|
|
4
|
+
* Manages saved custom model configurations for openai-compatible and openrouter providers.
|
|
5
|
+
* Stored in ~/.dexto/models/custom-models.json
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
/** Providers that support custom models */
|
|
9
|
+
export declare const CUSTOM_MODEL_PROVIDERS: readonly ["openai-compatible", "openrouter", "litellm", "glama", "bedrock", "ollama", "local", "vertex"];
|
|
10
|
+
export type CustomModelProvider = (typeof CUSTOM_MODEL_PROVIDERS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Schema for a saved custom model configuration.
|
|
13
|
+
* - openai-compatible: requires baseURL, optional per-model apiKey
|
|
14
|
+
* - openrouter: baseURL is auto-injected, maxInputTokens from registry
|
|
15
|
+
* - litellm: requires baseURL, uses LITELLM_API_KEY or per-model override
|
|
16
|
+
* - glama: fixed baseURL, uses GLAMA_API_KEY or per-model override
|
|
17
|
+
* - bedrock: no baseURL, uses AWS credentials from environment
|
|
18
|
+
* - ollama: optional baseURL (defaults to http://localhost:11434)
|
|
19
|
+
* - local: no baseURL, uses local GGUF files via node-llama-cpp
|
|
20
|
+
* - vertex: no baseURL, uses Google Cloud ADC
|
|
21
|
+
*
|
|
22
|
+
* TODO: For hosted deployments, API keys should be stored in a secure
|
|
23
|
+
* key management service (e.g., AWS Secrets Manager, HashiCorp Vault)
|
|
24
|
+
* rather than in the local JSON file. Current approach is suitable for
|
|
25
|
+
* local CLI usage where the file is in ~/.dexto/ (user-private).
|
|
26
|
+
*/
|
|
27
|
+
export declare const CustomModelSchema: z.ZodEffects<z.ZodObject<{
|
|
28
|
+
name: z.ZodString;
|
|
29
|
+
provider: z.ZodDefault<z.ZodEnum<["openai-compatible", "openrouter", "litellm", "glama", "bedrock", "ollama", "local", "vertex"]>>;
|
|
30
|
+
baseURL: z.ZodOptional<z.ZodString>;
|
|
31
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
32
|
+
maxInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
35
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
name: string;
|
|
38
|
+
provider: "openai-compatible" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama";
|
|
39
|
+
apiKey?: string | undefined;
|
|
40
|
+
baseURL?: string | undefined;
|
|
41
|
+
filePath?: string | undefined;
|
|
42
|
+
displayName?: string | undefined;
|
|
43
|
+
maxInputTokens?: number | undefined;
|
|
44
|
+
maxOutputTokens?: number | undefined;
|
|
45
|
+
}, {
|
|
46
|
+
name: string;
|
|
47
|
+
provider?: "openai-compatible" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
|
|
48
|
+
apiKey?: string | undefined;
|
|
49
|
+
baseURL?: string | undefined;
|
|
50
|
+
filePath?: string | undefined;
|
|
51
|
+
displayName?: string | undefined;
|
|
52
|
+
maxInputTokens?: number | undefined;
|
|
53
|
+
maxOutputTokens?: number | undefined;
|
|
54
|
+
}>, {
|
|
55
|
+
name: string;
|
|
56
|
+
provider: "openai-compatible" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama";
|
|
57
|
+
apiKey?: string | undefined;
|
|
58
|
+
baseURL?: string | undefined;
|
|
59
|
+
filePath?: string | undefined;
|
|
60
|
+
displayName?: string | undefined;
|
|
61
|
+
maxInputTokens?: number | undefined;
|
|
62
|
+
maxOutputTokens?: number | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
name: string;
|
|
65
|
+
provider?: "openai-compatible" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
|
|
66
|
+
apiKey?: string | undefined;
|
|
67
|
+
baseURL?: string | undefined;
|
|
68
|
+
filePath?: string | undefined;
|
|
69
|
+
displayName?: string | undefined;
|
|
70
|
+
maxInputTokens?: number | undefined;
|
|
71
|
+
maxOutputTokens?: number | undefined;
|
|
72
|
+
}>;
|
|
73
|
+
export type CustomModel = z.output<typeof CustomModelSchema>;
|
|
74
|
+
/**
|
|
75
|
+
* Get the path to the custom models storage file.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getCustomModelsPath(): string;
|
|
78
|
+
/**
|
|
79
|
+
* Load custom models from storage.
|
|
80
|
+
*/
|
|
81
|
+
export declare function loadCustomModels(): Promise<CustomModel[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Save a custom model to storage.
|
|
84
|
+
*/
|
|
85
|
+
export declare function saveCustomModel(model: CustomModel): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Delete a custom model by name.
|
|
88
|
+
*/
|
|
89
|
+
export declare function deleteCustomModel(name: string): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Get a specific custom model by name.
|
|
92
|
+
*/
|
|
93
|
+
export declare function getCustomModel(name: string): Promise<CustomModel | null>;
|
|
94
|
+
//# sourceMappingURL=custom-models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-models.d.ts","sourceRoot":"","sources":["../../src/models/custom-models.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,2CAA2C;AAC3C,eAAO,MAAM,sBAAsB,0GASzB,CAAC;AACX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CxB,CAAC;AAEP,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAO7D;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAmB/D;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBvE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAG9E"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { getDextoGlobalPath } from "../utils/path.js";
|
|
5
|
+
const CUSTOM_MODEL_PROVIDERS = [
|
|
6
|
+
"openai-compatible",
|
|
7
|
+
"openrouter",
|
|
8
|
+
"litellm",
|
|
9
|
+
"glama",
|
|
10
|
+
"bedrock",
|
|
11
|
+
"ollama",
|
|
12
|
+
"local",
|
|
13
|
+
"vertex"
|
|
14
|
+
];
|
|
15
|
+
const CustomModelSchema = z.object({
|
|
16
|
+
name: z.string().min(1),
|
|
17
|
+
provider: z.enum(CUSTOM_MODEL_PROVIDERS).default("openai-compatible"),
|
|
18
|
+
baseURL: z.string().url().optional(),
|
|
19
|
+
displayName: z.string().optional(),
|
|
20
|
+
maxInputTokens: z.number().int().positive().optional(),
|
|
21
|
+
maxOutputTokens: z.number().int().positive().optional(),
|
|
22
|
+
// Optional per-model API key. For openai-compatible this is the primary key source.
|
|
23
|
+
// For litellm/glama/openrouter this overrides the provider-level env var key.
|
|
24
|
+
apiKey: z.string().optional(),
|
|
25
|
+
// File path for local GGUF models. Required when provider is 'local'.
|
|
26
|
+
// Stores the absolute path to the .gguf file on disk.
|
|
27
|
+
filePath: z.string().optional()
|
|
28
|
+
}).superRefine((data, ctx) => {
|
|
29
|
+
if ((data.provider === "openai-compatible" || data.provider === "litellm") && !data.baseURL) {
|
|
30
|
+
ctx.addIssue({
|
|
31
|
+
code: z.ZodIssueCode.custom,
|
|
32
|
+
path: ["baseURL"],
|
|
33
|
+
message: `Base URL is required for ${data.provider} provider`
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (data.provider === "local" && !data.filePath) {
|
|
37
|
+
ctx.addIssue({
|
|
38
|
+
code: z.ZodIssueCode.custom,
|
|
39
|
+
path: ["filePath"],
|
|
40
|
+
message: "File path is required for local provider"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (data.provider === "local" && data.filePath && !data.filePath.endsWith(".gguf")) {
|
|
44
|
+
ctx.addIssue({
|
|
45
|
+
code: z.ZodIssueCode.custom,
|
|
46
|
+
path: ["filePath"],
|
|
47
|
+
message: "File path must be a .gguf file"
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const StorageSchema = z.object({
|
|
52
|
+
version: z.literal(1),
|
|
53
|
+
models: z.array(CustomModelSchema)
|
|
54
|
+
});
|
|
55
|
+
function getCustomModelsPath() {
|
|
56
|
+
return getDextoGlobalPath("models", "custom-models.json");
|
|
57
|
+
}
|
|
58
|
+
async function loadCustomModels() {
|
|
59
|
+
const filePath = getCustomModelsPath();
|
|
60
|
+
try {
|
|
61
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
62
|
+
const parsed = StorageSchema.safeParse(JSON.parse(content));
|
|
63
|
+
if (!parsed.success) {
|
|
64
|
+
console.warn(
|
|
65
|
+
`[custom-models] Failed to parse ${filePath}: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
|
66
|
+
);
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return parsed.data.models;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.code === "ENOENT") {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function saveCustomModel(model) {
|
|
78
|
+
const parsed = CustomModelSchema.safeParse(model);
|
|
79
|
+
if (!parsed.success) {
|
|
80
|
+
throw new Error(`Invalid model: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
const models = await loadCustomModels();
|
|
83
|
+
const existingIndex = models.findIndex((m) => m.name === parsed.data.name);
|
|
84
|
+
if (existingIndex >= 0) {
|
|
85
|
+
models[existingIndex] = parsed.data;
|
|
86
|
+
} else {
|
|
87
|
+
models.push(parsed.data);
|
|
88
|
+
}
|
|
89
|
+
await writeCustomModels(models);
|
|
90
|
+
}
|
|
91
|
+
async function deleteCustomModel(name) {
|
|
92
|
+
const models = await loadCustomModels();
|
|
93
|
+
const filtered = models.filter((m) => m.name !== name);
|
|
94
|
+
if (filtered.length === models.length) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
await writeCustomModels(filtered);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
async function getCustomModel(name) {
|
|
101
|
+
const models = await loadCustomModels();
|
|
102
|
+
return models.find((m) => m.name === name) ?? null;
|
|
103
|
+
}
|
|
104
|
+
async function writeCustomModels(models) {
|
|
105
|
+
const filePath = getCustomModelsPath();
|
|
106
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
107
|
+
await fs.writeFile(filePath, JSON.stringify({ version: 1, models }, null, 2), "utf-8");
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
CUSTOM_MODEL_PROVIDERS,
|
|
111
|
+
CustomModelSchema,
|
|
112
|
+
deleteCustomModel,
|
|
113
|
+
getCustomModel,
|
|
114
|
+
getCustomModelsPath,
|
|
115
|
+
loadCustomModels,
|
|
116
|
+
saveCustomModel
|
|
117
|
+
};
|