@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.
Files changed (94) hide show
  1. package/dist/AgentFactory.cjs +152 -0
  2. package/dist/AgentFactory.d.ts +121 -0
  3. package/dist/AgentFactory.d.ts.map +1 -0
  4. package/dist/AgentFactory.js +132 -0
  5. package/dist/AgentManager.cjs +226 -0
  6. package/dist/AgentManager.d.ts +191 -0
  7. package/dist/AgentManager.d.ts.map +1 -0
  8. package/dist/AgentManager.js +192 -0
  9. package/dist/config/config-enrichment.cjs +23 -3
  10. package/dist/config/config-enrichment.d.ts +20 -5
  11. package/dist/config/config-enrichment.d.ts.map +1 -1
  12. package/dist/config/config-enrichment.js +22 -3
  13. package/dist/config/config-manager.cjs +340 -3
  14. package/dist/config/config-manager.d.ts +158 -7
  15. package/dist/config/config-manager.d.ts.map +1 -1
  16. package/dist/config/config-manager.js +325 -3
  17. package/dist/config/discover-prompts.cjs +103 -0
  18. package/dist/config/discover-prompts.d.ts +28 -0
  19. package/dist/config/discover-prompts.d.ts.map +1 -0
  20. package/dist/config/discover-prompts.js +73 -0
  21. package/dist/config/errors.cjs +2 -2
  22. package/dist/config/errors.js +2 -2
  23. package/dist/config/index.cjs +14 -2
  24. package/dist/config/index.d.ts +2 -2
  25. package/dist/config/index.d.ts.map +1 -1
  26. package/dist/config/index.js +21 -3
  27. package/dist/index.cjs +109 -6
  28. package/dist/index.d.ts +9 -6
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +111 -6
  31. package/dist/installation.cjs +239 -0
  32. package/dist/installation.d.ts +72 -0
  33. package/dist/installation.d.ts.map +1 -0
  34. package/dist/installation.js +202 -0
  35. package/dist/models/custom-models.cjs +157 -0
  36. package/dist/models/custom-models.d.ts +94 -0
  37. package/dist/models/custom-models.d.ts.map +1 -0
  38. package/dist/models/custom-models.js +117 -0
  39. package/dist/models/index.cjs +89 -0
  40. package/dist/models/index.d.ts +11 -0
  41. package/dist/models/index.d.ts.map +1 -0
  42. package/dist/models/index.js +68 -0
  43. package/dist/models/path-resolver.cjs +154 -0
  44. package/dist/models/path-resolver.d.ts +77 -0
  45. package/dist/models/path-resolver.d.ts.map +1 -0
  46. package/dist/models/path-resolver.js +108 -0
  47. package/dist/models/state-manager.cjs +220 -0
  48. package/dist/models/state-manager.d.ts +138 -0
  49. package/dist/models/state-manager.d.ts.map +1 -0
  50. package/dist/models/state-manager.js +184 -0
  51. package/dist/preferences/error-codes.cjs +2 -0
  52. package/dist/preferences/error-codes.d.ts +3 -1
  53. package/dist/preferences/error-codes.d.ts.map +1 -1
  54. package/dist/preferences/error-codes.js +2 -0
  55. package/dist/preferences/index.d.ts +1 -1
  56. package/dist/preferences/index.d.ts.map +1 -1
  57. package/dist/preferences/loader.cjs +32 -6
  58. package/dist/preferences/loader.d.ts +23 -4
  59. package/dist/preferences/loader.d.ts.map +1 -1
  60. package/dist/preferences/loader.js +32 -6
  61. package/dist/preferences/schemas.cjs +21 -3
  62. package/dist/preferences/schemas.d.ts +52 -24
  63. package/dist/preferences/schemas.d.ts.map +1 -1
  64. package/dist/preferences/schemas.js +28 -4
  65. package/dist/registry/registry.cjs +28 -45
  66. package/dist/registry/registry.d.ts +8 -6
  67. package/dist/registry/registry.d.ts.map +1 -1
  68. package/dist/registry/registry.js +26 -44
  69. package/dist/registry/types.d.ts +11 -13
  70. package/dist/registry/types.d.ts.map +1 -1
  71. package/dist/resolver.cjs +82 -43
  72. package/dist/resolver.d.ts +7 -5
  73. package/dist/resolver.d.ts.map +1 -1
  74. package/dist/resolver.js +83 -44
  75. package/dist/utils/api-key-resolver.cjs +19 -1
  76. package/dist/utils/api-key-resolver.d.ts.map +1 -1
  77. package/dist/utils/api-key-resolver.js +19 -1
  78. package/dist/utils/api-key-store.cjs +46 -0
  79. package/dist/utils/api-key-store.d.ts +27 -0
  80. package/dist/utils/api-key-store.d.ts.map +1 -1
  81. package/dist/utils/api-key-store.js +44 -0
  82. package/dist/utils/env-file.cjs +20 -68
  83. package/dist/utils/env-file.d.ts +2 -1
  84. package/dist/utils/env-file.d.ts.map +1 -1
  85. package/dist/utils/env-file.js +20 -68
  86. package/dist/writer.cjs +20 -2
  87. package/dist/writer.d.ts +1 -0
  88. package/dist/writer.d.ts.map +1 -1
  89. package/dist/writer.js +20 -2
  90. package/package.json +2 -2
  91. package/dist/AgentOrchestrator.cjs +0 -263
  92. package/dist/AgentOrchestrator.d.ts +0 -191
  93. package/dist/AgentOrchestrator.d.ts.map +0 -1
  94. package/dist/AgentOrchestrator.js +0 -239
package/dist/resolver.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
- import { isPath } from "./utils/path.js";
3
+ import { isPath, getDextoGlobalPath, resolveBundledScript } from "./utils/path.js";
4
4
  import {
5
5
  getExecutionContext,
6
6
  findDextoSourceRoot,
@@ -9,7 +9,10 @@ import {
9
9
  import { logger } from "@dexto/core";
10
10
  import { loadGlobalPreferences, globalPreferencesExist } from "./preferences/loader.js";
11
11
  import { ConfigError } from "./config/index.js";
12
- async function resolveAgentPath(nameOrPath, autoInstall = true, injectPreferences = true) {
12
+ import { RegistryError } from "./registry/errors.js";
13
+ import { AgentManager } from "./AgentManager.js";
14
+ import { installBundledAgent } from "./installation.js";
15
+ async function resolveAgentPath(nameOrPath, autoInstall = true) {
13
16
  if (nameOrPath && isPath(nameOrPath)) {
14
17
  const resolved = path.resolve(nameOrPath);
15
18
  try {
@@ -23,32 +26,67 @@ async function resolveAgentPath(nameOrPath, autoInstall = true, injectPreference
23
26
  }
24
27
  }
25
28
  if (nameOrPath) {
26
- const { getAgentRegistry } = await import("./registry/registry.js");
27
- const registry = getAgentRegistry();
28
- return await registry.resolveAgent(nameOrPath, autoInstall, injectPreferences);
29
+ return await resolveAgentByName(nameOrPath, autoInstall);
29
30
  }
30
- return await resolveDefaultAgentByContext(autoInstall, injectPreferences);
31
+ return await resolveDefaultAgentByContext(autoInstall);
31
32
  }
32
- async function resolveDefaultAgentByContext(autoInstall = true, injectPreferences = true) {
33
+ async function resolveAgentByName(agentId, autoInstall) {
34
+ const agentsDir = getDextoGlobalPath("agents");
35
+ const installedRegistryPath = path.join(agentsDir, "registry.json");
36
+ try {
37
+ const manager = new AgentManager(installedRegistryPath);
38
+ await manager.loadRegistry();
39
+ if (manager.hasAgent(agentId)) {
40
+ const agentPath = await getAgentConfigPath(agentId);
41
+ return agentPath;
42
+ }
43
+ } catch (error) {
44
+ logger.debug(`Agent '${agentId}' not found in installed registry: ${error}`);
45
+ }
46
+ if (autoInstall) {
47
+ try {
48
+ logger.info(`Auto-installing agent '${agentId}' from bundled registry`);
49
+ const configPath = await installBundledAgent(agentId);
50
+ return configPath;
51
+ } catch (error) {
52
+ logger.debug(`Failed to auto-install agent '${agentId}': ${error}`);
53
+ throw RegistryError.agentNotFound(agentId, []);
54
+ }
55
+ }
56
+ throw RegistryError.agentNotInstalledAutoInstallDisabled(agentId, []);
57
+ }
58
+ async function getAgentConfigPath(agentId) {
59
+ const agentsDir = getDextoGlobalPath("agents");
60
+ const installedRegistryPath = path.join(agentsDir, "registry.json");
61
+ const registryContent = await fs.readFile(installedRegistryPath, "utf-8");
62
+ const registry = JSON.parse(registryContent);
63
+ const agentEntry = registry.agents.find((a) => a.id === agentId);
64
+ if (!agentEntry) {
65
+ const available = registry.agents.map((a) => a.id);
66
+ throw RegistryError.agentNotFound(agentId, available);
67
+ }
68
+ return path.resolve(path.dirname(installedRegistryPath), agentEntry.configPath);
69
+ }
70
+ async function resolveDefaultAgentByContext(autoInstall = true) {
33
71
  const executionContext = getExecutionContext();
34
72
  switch (executionContext) {
35
73
  case "dexto-source":
36
- return await resolveDefaultAgentForDextoSource(autoInstall, injectPreferences);
74
+ return await resolveDefaultAgentForDextoSource(autoInstall);
37
75
  case "dexto-project":
38
- return await resolveDefaultAgentForDextoProject(autoInstall, injectPreferences);
76
+ return await resolveDefaultAgentForDextoProject(autoInstall);
39
77
  case "global-cli":
40
- return await resolveDefaultAgentForGlobalCLI(autoInstall, injectPreferences);
78
+ return await resolveDefaultAgentForGlobalCLI(autoInstall);
41
79
  default:
42
80
  throw ConfigError.unknownContext(executionContext);
43
81
  }
44
82
  }
45
- async function resolveDefaultAgentForDextoSource(autoInstall = true, injectPreferences = true) {
83
+ async function resolveDefaultAgentForDextoSource(autoInstall = true) {
46
84
  logger.debug("Resolving default agent for dexto source context");
47
85
  const sourceRoot = findDextoSourceRoot();
48
86
  if (!sourceRoot) {
49
87
  throw ConfigError.bundledNotFound("dexto source directory not found");
50
88
  }
51
- const repoConfigPath = path.join(sourceRoot, "agents", "default-agent.yml");
89
+ const repoConfigPath = path.join(sourceRoot, "agents", "coding-agent", "coding-agent.yml");
52
90
  const isDevMode = process.env.DEXTO_DEV_MODE === "true";
53
91
  if (isDevMode) {
54
92
  logger.debug("Dev mode: using repository config file");
@@ -65,13 +103,7 @@ async function resolveDefaultAgentForDextoSource(autoInstall = true, injectPrefe
65
103
  if (preferences.setup.completed) {
66
104
  logger.debug("Using user preferences in dexto-source context");
67
105
  const preferredAgentName = preferences.defaults.defaultAgent;
68
- const { getAgentRegistry } = await import("./registry/registry.js");
69
- const registry = getAgentRegistry();
70
- return await registry.resolveAgent(
71
- preferredAgentName,
72
- autoInstall,
73
- injectPreferences
74
- );
106
+ return await resolveAgentByName(preferredAgentName, autoInstall);
75
107
  }
76
108
  } catch (error) {
77
109
  logger.warn(`Failed to load preferences, falling back to repo config: ${error}`);
@@ -85,16 +117,16 @@ async function resolveDefaultAgentForDextoSource(autoInstall = true, injectPrefe
85
117
  throw ConfigError.bundledNotFound(repoConfigPath);
86
118
  }
87
119
  }
88
- async function resolveDefaultAgentForDextoProject(autoInstall = true, injectPreferences = true) {
120
+ async function resolveDefaultAgentForDextoProject(autoInstall = true) {
89
121
  logger.debug("Resolving default agent for dexto project context");
90
122
  const projectRoot = findDextoProjectRoot();
91
123
  if (!projectRoot) {
92
124
  throw ConfigError.unknownContext("dexto-project: project root not found");
93
125
  }
94
126
  const candidatePaths = [
95
- path.join(projectRoot, "default-agent.yml"),
96
- path.join(projectRoot, "agents", "default-agent.yml"),
97
- path.join(projectRoot, "src", "dexto", "agents", "default-agent.yml")
127
+ path.join(projectRoot, "coding-agent.yml"),
128
+ path.join(projectRoot, "agents", "coding-agent.yml"),
129
+ path.join(projectRoot, "src", "dexto", "agents", "coding-agent.yml")
98
130
  ];
99
131
  for (const p of candidatePaths) {
100
132
  try {
@@ -103,7 +135,7 @@ async function resolveDefaultAgentForDextoProject(autoInstall = true, injectPref
103
135
  } catch {
104
136
  }
105
137
  }
106
- logger.debug(`No project-local default-agent.yml found in ${projectRoot}`);
138
+ logger.debug(`No project-local coding-agent.yml found in ${projectRoot}`);
107
139
  if (!globalPreferencesExist()) {
108
140
  throw ConfigError.noProjectDefault(projectRoot);
109
141
  }
@@ -112,11 +144,9 @@ async function resolveDefaultAgentForDextoProject(autoInstall = true, injectPref
112
144
  throw ConfigError.setupIncomplete();
113
145
  }
114
146
  const preferredAgentName = preferences.defaults.defaultAgent;
115
- const { getAgentRegistry } = await import("./registry/registry.js");
116
- const registry = getAgentRegistry();
117
- return await registry.resolveAgent(preferredAgentName, autoInstall, injectPreferences);
147
+ return await resolveAgentByName(preferredAgentName, autoInstall);
118
148
  }
119
- async function resolveDefaultAgentForGlobalCLI(autoInstall = true, injectPreferences = true) {
149
+ async function resolveDefaultAgentForGlobalCLI(autoInstall = true) {
120
150
  logger.debug("Resolving default agent for global CLI context");
121
151
  if (!globalPreferencesExist()) {
122
152
  throw ConfigError.noGlobalPreferences();
@@ -126,24 +156,33 @@ async function resolveDefaultAgentForGlobalCLI(autoInstall = true, injectPrefere
126
156
  throw ConfigError.setupIncomplete();
127
157
  }
128
158
  const preferredAgentName = preferences.defaults.defaultAgent;
129
- const { getAgentRegistry } = await import("./registry/registry.js");
130
- const registry = getAgentRegistry();
131
- return await registry.resolveAgent(preferredAgentName, autoInstall, injectPreferences);
159
+ return await resolveAgentByName(preferredAgentName, autoInstall);
132
160
  }
133
161
  async function updateDefaultAgentPreference(agentName) {
134
- const { getAgentRegistry } = await import("./registry/registry.js");
135
- const { RegistryError } = await import("./registry/errors.js");
136
- const { isPath: isPath2 } = await import("@dexto/core");
137
- const registry = getAgentRegistry();
138
- if (isPath2(agentName) || !registry.hasAgent(agentName)) {
139
- const available = Object.keys(registry.getAvailableAgents());
140
- throw RegistryError.agentNotFound(agentName, available);
141
- }
142
- const { updateGlobalPreferences } = await import("./preferences/loader.js");
143
- await updateGlobalPreferences({
144
- defaults: { defaultAgent: agentName }
145
- });
146
- logger.info(`Updated default agent preference to: ${agentName}`);
162
+ const agentsDir = getDextoGlobalPath("agents");
163
+ const installedRegistryPath = path.join(agentsDir, "registry.json");
164
+ const bundledRegistryPath = resolveBundledScript("agents/agent-registry.json");
165
+ const registriesToCheck = [
166
+ { path: installedRegistryPath, name: "installed" },
167
+ { path: bundledRegistryPath, name: "bundled" }
168
+ ];
169
+ for (const registry of registriesToCheck) {
170
+ try {
171
+ const manager = new AgentManager(registry.path);
172
+ await manager.loadRegistry();
173
+ if (manager.hasAgent(agentName)) {
174
+ const { updateGlobalPreferences } = await import("./preferences/loader.js");
175
+ await updateGlobalPreferences({
176
+ defaults: { defaultAgent: agentName }
177
+ });
178
+ logger.info(`Updated default agent preference to: ${agentName}`);
179
+ return;
180
+ }
181
+ } catch (error) {
182
+ logger.debug(`Agent '${agentName}' not found in ${registry.name} registry: ${error}`);
183
+ }
184
+ }
185
+ throw RegistryError.agentNotFound(agentName, []);
147
186
  }
148
187
  export {
149
188
  resolveAgentPath,
@@ -31,7 +31,25 @@ const PROVIDER_API_KEY_MAP = {
31
31
  google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY"],
32
32
  groq: ["GROQ_API_KEY"],
33
33
  cohere: ["COHERE_API_KEY"],
34
- xai: ["XAI_API_KEY", "X_AI_API_KEY"]
34
+ xai: ["XAI_API_KEY", "X_AI_API_KEY"],
35
+ openrouter: ["OPENROUTER_API_KEY"],
36
+ litellm: ["LITELLM_API_KEY", "LITELLM_KEY"],
37
+ glama: ["GLAMA_API_KEY"],
38
+ // Vertex uses ADC (Application Default Credentials), not API keys
39
+ // GOOGLE_APPLICATION_CREDENTIALS points to service account JSON (optional)
40
+ // Primary config is GOOGLE_VERTEX_PROJECT (required) + GOOGLE_VERTEX_LOCATION (optional)
41
+ vertex: [],
42
+ // Bedrock supports two auth methods:
43
+ // 1. AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (simplest)
44
+ // 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_REGION (IAM credentials)
45
+ // AWS_SESSION_TOKEN (optional, for temporary credentials)
46
+ bedrock: ["AWS_BEARER_TOKEN_BEDROCK"],
47
+ // Local providers don't require API keys
48
+ local: [],
49
+ // Native node-llama-cpp execution
50
+ ollama: []
51
+ // Ollama server (may optionally use OLLAMA_API_KEY for remote servers)
52
+ // TODO: dexto: ['DEXTO_API_KEY'],
35
53
  // perplexity: ['PERPLEXITY_API_KEY'],
36
54
  // together: ['TOGETHER_API_KEY'],
37
55
  // fireworks: ['FIREWORKS_API_KEY'],
@@ -1 +1 @@
1
- {"version":3,"file":"api-key-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/api-key-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;GAGG;AAGH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAY9D,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAelF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAGpE"}
1
+ {"version":3,"file":"api-key-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/api-key-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;GAGG;AAGH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CA4B9D,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAelF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAGpE"}
@@ -6,7 +6,25 @@ const PROVIDER_API_KEY_MAP = {
6
6
  google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY"],
7
7
  groq: ["GROQ_API_KEY"],
8
8
  cohere: ["COHERE_API_KEY"],
9
- xai: ["XAI_API_KEY", "X_AI_API_KEY"]
9
+ xai: ["XAI_API_KEY", "X_AI_API_KEY"],
10
+ openrouter: ["OPENROUTER_API_KEY"],
11
+ litellm: ["LITELLM_API_KEY", "LITELLM_KEY"],
12
+ glama: ["GLAMA_API_KEY"],
13
+ // Vertex uses ADC (Application Default Credentials), not API keys
14
+ // GOOGLE_APPLICATION_CREDENTIALS points to service account JSON (optional)
15
+ // Primary config is GOOGLE_VERTEX_PROJECT (required) + GOOGLE_VERTEX_LOCATION (optional)
16
+ vertex: [],
17
+ // Bedrock supports two auth methods:
18
+ // 1. AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (simplest)
19
+ // 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_REGION (IAM credentials)
20
+ // AWS_SESSION_TOKEN (optional, for temporary credentials)
21
+ bedrock: ["AWS_BEARER_TOKEN_BEDROCK"],
22
+ // Local providers don't require API keys
23
+ local: [],
24
+ // Native node-llama-cpp execution
25
+ ollama: []
26
+ // Ollama server (may optionally use OLLAMA_API_KEY for remote servers)
27
+ // TODO: dexto: ['DEXTO_API_KEY'],
10
28
  // perplexity: ['PERPLEXITY_API_KEY'],
11
29
  // together: ['TOGETHER_API_KEY'],
12
30
  // fireworks: ['FIREWORKS_API_KEY'],
@@ -18,6 +18,8 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var api_key_store_exports = {};
20
20
  __export(api_key_store_exports, {
21
+ SHARED_API_KEY_PROVIDERS: () => SHARED_API_KEY_PROVIDERS,
22
+ determineApiKeyStorage: () => determineApiKeyStorage,
21
23
  getProviderKeyStatus: () => getProviderKeyStatus,
22
24
  listProviderKeyStatus: () => listProviderKeyStatus,
23
25
  saveProviderApiKey: () => saveProviderApiKey
@@ -37,6 +39,27 @@ async function saveProviderApiKey(provider, apiKey, startPath) {
37
39
  return { envVar, targetEnvPath };
38
40
  }
39
41
  function getProviderKeyStatus(provider) {
42
+ if (provider === "vertex") {
43
+ const projectId = process.env.GOOGLE_VERTEX_PROJECT;
44
+ return {
45
+ hasApiKey: Boolean(projectId && projectId.trim()),
46
+ envVar: "GOOGLE_VERTEX_PROJECT"
47
+ };
48
+ }
49
+ if (provider === "bedrock") {
50
+ const apiKey = process.env.AWS_BEARER_TOKEN_BEDROCK;
51
+ if (apiKey && apiKey.trim()) {
52
+ return {
53
+ hasApiKey: true,
54
+ envVar: "AWS_BEARER_TOKEN_BEDROCK"
55
+ };
56
+ }
57
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
58
+ return {
59
+ hasApiKey: Boolean(region && region.trim()),
60
+ envVar: "AWS_REGION"
61
+ };
62
+ }
40
63
  const envVar = (0, import_api_key_resolver.getPrimaryApiKeyEnvVar)(provider);
41
64
  const key = (0, import_api_key_resolver.resolveApiKeyForProvider)(provider);
42
65
  return { hasApiKey: Boolean(key && key.trim()), envVar };
@@ -48,8 +71,31 @@ function listProviderKeyStatus() {
48
71
  }
49
72
  return result;
50
73
  }
74
+ const SHARED_API_KEY_PROVIDERS = ["glama", "openrouter", "litellm"];
75
+ function determineApiKeyStorage(provider, userEnteredKey, providerHasKey, existingProviderKey) {
76
+ const result = {
77
+ saveToProviderEnvVar: false,
78
+ saveAsPerModel: false
79
+ };
80
+ if (!userEnteredKey) {
81
+ return result;
82
+ }
83
+ const hasSharedEnvVarKey = SHARED_API_KEY_PROVIDERS.includes(provider);
84
+ if (hasSharedEnvVarKey) {
85
+ if (!providerHasKey) {
86
+ result.saveToProviderEnvVar = true;
87
+ } else if (existingProviderKey && userEnteredKey !== existingProviderKey) {
88
+ result.saveAsPerModel = true;
89
+ }
90
+ } else {
91
+ result.saveAsPerModel = true;
92
+ }
93
+ return result;
94
+ }
51
95
  // Annotate the CommonJS export names for ESM import in node:
52
96
  0 && (module.exports = {
97
+ SHARED_API_KEY_PROVIDERS,
98
+ determineApiKeyStorage,
53
99
  getProviderKeyStatus,
54
100
  listProviderKeyStatus,
55
101
  saveProviderApiKey
@@ -15,4 +15,31 @@ export declare function listProviderKeyStatus(): Record<string, {
15
15
  hasApiKey: boolean;
16
16
  envVar: string;
17
17
  }>;
18
+ /**
19
+ * Providers that use a shared env var for API keys (vs per-endpoint like openai-compatible).
20
+ * For these providers, we save to the env var if none exists, otherwise per-model override.
21
+ */
22
+ export declare const SHARED_API_KEY_PROVIDERS: readonly ["glama", "openrouter", "litellm"];
23
+ export type ApiKeyStorageStrategy = {
24
+ /** Save the key to provider env var (e.g., GLAMA_API_KEY) */
25
+ saveToProviderEnvVar: boolean;
26
+ /** Save the key as per-model override in custom model config */
27
+ saveAsPerModel: boolean;
28
+ };
29
+ /**
30
+ * Determine where to store an API key for a custom model.
31
+ *
32
+ * Logic:
33
+ * - For glama/openrouter/litellm (shared env var providers):
34
+ * - If NO provider key exists → save to provider env var for reuse
35
+ * - If provider key EXISTS and user entered SAME key → don't save (uses fallback)
36
+ * - If provider key EXISTS and user entered DIFFERENT key → save as per-model override
37
+ * - For openai-compatible: always save as per-model (each endpoint needs own key)
38
+ *
39
+ * @param provider - The custom model provider
40
+ * @param userEnteredKey - The API key entered by user (trimmed, may be empty)
41
+ * @param providerHasKey - Whether the provider already has a key configured
42
+ * @param existingProviderKey - The existing provider key value (for comparison)
43
+ */
44
+ export declare function determineApiKeyStorage(provider: string, userEnteredKey: string | undefined, providerHasKey: boolean, existingProviderKey: string | undefined): ApiKeyStorageStrategy;
18
45
  //# sourceMappingURL=api-key-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-key-store.d.ts","sourceRoot":"","sources":["../../src/utils/api-key-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;GAGG;AACH,wBAAsB,kBAAkB,CACpC,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAYpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,WAAW,GAAG;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAClB,CAIA;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAM9F"}
1
+ {"version":3,"file":"api-key-store.d.ts","sourceRoot":"","sources":["../../src/utils/api-key-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;GAGG;AACH,wBAAsB,kBAAkB,CACpC,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAYpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,WAAW,GAAG;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAClB,CA4CA;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAM9F;AAED;;;GAGG;AACH,eAAO,MAAM,wBAAwB,6CAA8C,CAAC;AAEpF,MAAM,MAAM,qBAAqB,GAAG;IAChC,6DAA6D;IAC7D,oBAAoB,EAAE,OAAO,CAAC;IAC9B,gEAAgE;IAChE,cAAc,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,cAAc,EAAE,OAAO,EACvB,mBAAmB,EAAE,MAAM,GAAG,SAAS,GACxC,qBAAqB,CA2BvB"}
@@ -12,6 +12,27 @@ async function saveProviderApiKey(provider, apiKey, startPath) {
12
12
  return { envVar, targetEnvPath };
13
13
  }
14
14
  function getProviderKeyStatus(provider) {
15
+ if (provider === "vertex") {
16
+ const projectId = process.env.GOOGLE_VERTEX_PROJECT;
17
+ return {
18
+ hasApiKey: Boolean(projectId && projectId.trim()),
19
+ envVar: "GOOGLE_VERTEX_PROJECT"
20
+ };
21
+ }
22
+ if (provider === "bedrock") {
23
+ const apiKey = process.env.AWS_BEARER_TOKEN_BEDROCK;
24
+ if (apiKey && apiKey.trim()) {
25
+ return {
26
+ hasApiKey: true,
27
+ envVar: "AWS_BEARER_TOKEN_BEDROCK"
28
+ };
29
+ }
30
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
31
+ return {
32
+ hasApiKey: Boolean(region && region.trim()),
33
+ envVar: "AWS_REGION"
34
+ };
35
+ }
15
36
  const envVar = getPrimaryApiKeyEnvVar(provider);
16
37
  const key = resolveApiKeyForProvider(provider);
17
38
  return { hasApiKey: Boolean(key && key.trim()), envVar };
@@ -23,7 +44,30 @@ function listProviderKeyStatus() {
23
44
  }
24
45
  return result;
25
46
  }
47
+ const SHARED_API_KEY_PROVIDERS = ["glama", "openrouter", "litellm"];
48
+ function determineApiKeyStorage(provider, userEnteredKey, providerHasKey, existingProviderKey) {
49
+ const result = {
50
+ saveToProviderEnvVar: false,
51
+ saveAsPerModel: false
52
+ };
53
+ if (!userEnteredKey) {
54
+ return result;
55
+ }
56
+ const hasSharedEnvVarKey = SHARED_API_KEY_PROVIDERS.includes(provider);
57
+ if (hasSharedEnvVarKey) {
58
+ if (!providerHasKey) {
59
+ result.saveToProviderEnvVar = true;
60
+ } else if (existingProviderKey && userEnteredKey !== existingProviderKey) {
61
+ result.saveAsPerModel = true;
62
+ }
63
+ } else {
64
+ result.saveAsPerModel = true;
65
+ }
66
+ return result;
67
+ }
26
68
  export {
69
+ SHARED_API_KEY_PROVIDERS,
70
+ determineApiKeyStorage,
27
71
  getProviderKeyStatus,
28
72
  listProviderKeyStatus,
29
73
  saveProviderApiKey
@@ -33,84 +33,36 @@ __export(env_file_exports, {
33
33
  module.exports = __toCommonJS(env_file_exports);
34
34
  var path = __toESM(require("node:path"), 1);
35
35
  var import_node_fs = require("node:fs");
36
- const DEXTO_ENV_KEYS = [
37
- "OPENAI_API_KEY",
38
- "ANTHROPIC_API_KEY",
39
- "GOOGLE_GENERATIVE_AI_API_KEY",
40
- "GROQ_API_KEY",
41
- "COHERE_API_KEY",
42
- "XAI_API_KEY",
43
- "DEXTO_LOG_LEVEL"
44
- ];
45
- function isDextoEnvKey(value) {
46
- return DEXTO_ENV_KEYS.includes(value);
47
- }
48
36
  async function updateEnvFile(envFilePath, updates) {
49
37
  await import_node_fs.promises.mkdir(path.dirname(envFilePath), { recursive: true });
50
- let envLines = [];
38
+ let content = "";
51
39
  try {
52
- const existingEnv = await import_node_fs.promises.readFile(envFilePath, "utf8");
53
- envLines = existingEnv.split("\n");
40
+ content = await import_node_fs.promises.readFile(envFilePath, "utf8");
54
41
  } catch {
55
42
  }
56
- const currentValues = {};
57
- envLines.forEach((line) => {
58
- const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
59
- if (match && match[1] && isDextoEnvKey(match[1])) {
60
- const value = match[2] ?? "";
61
- currentValues[match[1]] = value;
43
+ const lines = content.split("\n");
44
+ const updatedKeys = /* @__PURE__ */ new Set();
45
+ const updatedLines = lines.map((line) => {
46
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
47
+ if (match && match[1] && match[1] in updates) {
48
+ const key = match[1];
49
+ updatedKeys.add(key);
50
+ return `${key}=${updates[key]}`;
62
51
  }
52
+ return line;
63
53
  });
64
- const updatedValues = Object.fromEntries(
65
- DEXTO_ENV_KEYS.map((key) => {
66
- const update = updates[key];
67
- const current = currentValues[key];
68
- const fallback = key === "DEXTO_LOG_LEVEL" ? "info" : "";
69
- const value = update !== void 0 ? update : current ?? fallback;
70
- return [key, value];
71
- })
72
- );
73
- const sectionHeader = "## Dexto env variables";
74
- const headerIndex = envLines.findIndex((line) => line.trim() === sectionHeader);
75
- let contentLines;
76
- if (headerIndex !== -1) {
77
- const beforeSection = envLines.slice(0, headerIndex);
78
- let sectionEnd = headerIndex + 1;
79
- while (sectionEnd < envLines.length && envLines[sectionEnd]?.trim() !== "") {
80
- sectionEnd++;
81
- }
82
- if (sectionEnd < envLines.length && envLines[sectionEnd]?.trim() === "") {
83
- sectionEnd++;
54
+ for (const [key, value] of Object.entries(updates)) {
55
+ if (!updatedKeys.has(key)) {
56
+ if (updatedLines.length > 0 && updatedLines[updatedLines.length - 1] !== "") {
57
+ updatedLines.push("");
58
+ }
59
+ updatedLines.push(`${key}=${value}`);
84
60
  }
85
- const afterSection = envLines.slice(sectionEnd);
86
- contentLines = [...beforeSection, ...afterSection];
87
- } else {
88
- contentLines = envLines;
89
61
  }
90
- const existingEnvVars = {};
91
- contentLines.forEach((line) => {
92
- const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
93
- if (match && match[1] && isDextoEnvKey(match[1])) {
94
- const value = match[2] ?? "";
95
- existingEnvVars[match[1]] = value;
96
- }
97
- });
98
- if (contentLines.length > 0) {
99
- if (contentLines[contentLines.length - 1]?.trim() !== "") {
100
- contentLines.push("");
101
- }
102
- } else {
103
- contentLines.push("");
104
- }
105
- contentLines.push(sectionHeader);
106
- for (const key of DEXTO_ENV_KEYS) {
107
- if (key in existingEnvVars && !(key in updates)) {
108
- continue;
109
- }
110
- contentLines.push(`${key}=${updatedValues[key]}`);
62
+ if (updatedLines[updatedLines.length - 1] !== "") {
63
+ updatedLines.push("");
111
64
  }
112
- contentLines.push("");
113
- await import_node_fs.promises.writeFile(envFilePath, contentLines.join("\n"), "utf8");
65
+ await import_node_fs.promises.writeFile(envFilePath, updatedLines.join("\n"), "utf8");
114
66
  }
115
67
  // Annotate the CommonJS export names for ESM import in node:
116
68
  0 && (module.exports = {
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Update a .env file with Dexto environment variables, ensuring our section stays consistent.
2
+ * Update a .env file with the provided key-value pairs.
3
+ * Existing keys are updated in place, new keys are appended.
3
4
  */
4
5
  export declare function updateEnvFile(envFilePath: string, updates: Record<string, string>): Promise<void>;
5
6
  //# sourceMappingURL=env-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"env-file.d.ts","sourceRoot":"","sources":["../../src/utils/env-file.ts"],"names":[],"mappings":"AAsBA;;GAEG;AACH,wBAAsB,aAAa,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAgFf"}
1
+ {"version":3,"file":"env-file.d.ts","sourceRoot":"","sources":["../../src/utils/env-file.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,aAAa,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAyCf"}
@@ -1,83 +1,35 @@
1
1
  import * as path from "node:path";
2
2
  import { promises as fs } from "node:fs";
3
- const DEXTO_ENV_KEYS = [
4
- "OPENAI_API_KEY",
5
- "ANTHROPIC_API_KEY",
6
- "GOOGLE_GENERATIVE_AI_API_KEY",
7
- "GROQ_API_KEY",
8
- "COHERE_API_KEY",
9
- "XAI_API_KEY",
10
- "DEXTO_LOG_LEVEL"
11
- ];
12
- function isDextoEnvKey(value) {
13
- return DEXTO_ENV_KEYS.includes(value);
14
- }
15
3
  async function updateEnvFile(envFilePath, updates) {
16
4
  await fs.mkdir(path.dirname(envFilePath), { recursive: true });
17
- let envLines = [];
5
+ let content = "";
18
6
  try {
19
- const existingEnv = await fs.readFile(envFilePath, "utf8");
20
- envLines = existingEnv.split("\n");
7
+ content = await fs.readFile(envFilePath, "utf8");
21
8
  } catch {
22
9
  }
23
- const currentValues = {};
24
- envLines.forEach((line) => {
25
- const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
26
- if (match && match[1] && isDextoEnvKey(match[1])) {
27
- const value = match[2] ?? "";
28
- currentValues[match[1]] = value;
10
+ const lines = content.split("\n");
11
+ const updatedKeys = /* @__PURE__ */ new Set();
12
+ const updatedLines = lines.map((line) => {
13
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
14
+ if (match && match[1] && match[1] in updates) {
15
+ const key = match[1];
16
+ updatedKeys.add(key);
17
+ return `${key}=${updates[key]}`;
29
18
  }
19
+ return line;
30
20
  });
31
- const updatedValues = Object.fromEntries(
32
- DEXTO_ENV_KEYS.map((key) => {
33
- const update = updates[key];
34
- const current = currentValues[key];
35
- const fallback = key === "DEXTO_LOG_LEVEL" ? "info" : "";
36
- const value = update !== void 0 ? update : current ?? fallback;
37
- return [key, value];
38
- })
39
- );
40
- const sectionHeader = "## Dexto env variables";
41
- const headerIndex = envLines.findIndex((line) => line.trim() === sectionHeader);
42
- let contentLines;
43
- if (headerIndex !== -1) {
44
- const beforeSection = envLines.slice(0, headerIndex);
45
- let sectionEnd = headerIndex + 1;
46
- while (sectionEnd < envLines.length && envLines[sectionEnd]?.trim() !== "") {
47
- sectionEnd++;
48
- }
49
- if (sectionEnd < envLines.length && envLines[sectionEnd]?.trim() === "") {
50
- sectionEnd++;
21
+ for (const [key, value] of Object.entries(updates)) {
22
+ if (!updatedKeys.has(key)) {
23
+ if (updatedLines.length > 0 && updatedLines[updatedLines.length - 1] !== "") {
24
+ updatedLines.push("");
25
+ }
26
+ updatedLines.push(`${key}=${value}`);
51
27
  }
52
- const afterSection = envLines.slice(sectionEnd);
53
- contentLines = [...beforeSection, ...afterSection];
54
- } else {
55
- contentLines = envLines;
56
28
  }
57
- const existingEnvVars = {};
58
- contentLines.forEach((line) => {
59
- const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
60
- if (match && match[1] && isDextoEnvKey(match[1])) {
61
- const value = match[2] ?? "";
62
- existingEnvVars[match[1]] = value;
63
- }
64
- });
65
- if (contentLines.length > 0) {
66
- if (contentLines[contentLines.length - 1]?.trim() !== "") {
67
- contentLines.push("");
68
- }
69
- } else {
70
- contentLines.push("");
71
- }
72
- contentLines.push(sectionHeader);
73
- for (const key of DEXTO_ENV_KEYS) {
74
- if (key in existingEnvVars && !(key in updates)) {
75
- continue;
76
- }
77
- contentLines.push(`${key}=${updatedValues[key]}`);
29
+ if (updatedLines[updatedLines.length - 1] !== "") {
30
+ updatedLines.push("");
78
31
  }
79
- contentLines.push("");
80
- await fs.writeFile(envFilePath, contentLines.join("\n"), "utf8");
32
+ await fs.writeFile(envFilePath, updatedLines.join("\n"), "utf8");
81
33
  }
82
34
  export {
83
35
  updateEnvFile