Skip to content

Commit 349b52c

Browse files
Waishnavclaude
andcommitted
feat(agents): add agentProviders config + init/doctor probe
Load ordered enable-list from config/env, probe available providers on init and doctor, and filter workflow CLI providers by enabled∩live. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c921419 commit 349b52c

3 files changed

Lines changed: 124 additions & 4 deletions

File tree

src/cli.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import { runLocalAgentProvider } from "./local-agent-adapters.js";
1515
import {
1616
isLocalAgentProvider,
1717
loadLocalAgentProfiles,
18+
LOCAL_AGENT_PROVIDERS,
1819
type LocalAgentProfile,
1920
} from "./local-agent-profiles.js";
2021
import {
2122
assertLocalAgentProviderAvailable,
2223
formatLocalAgentProviderAvailabilitySummary,
24+
getLocalAgentProviderAvailabilitySnapshot,
2325
} from "./local-agent-availability.js";
2426
import {
2527
formatAvailableLocalAgentTargets,
@@ -169,12 +171,18 @@ async function runInit({ force }: { force: boolean }): Promise<void> {
169171
validate: validateRequiredPublicBaseUrl,
170172
}));
171173

174+
const subagents = resolveSubagentsFlag(files.config);
175+
const agentProviders =
176+
subagents === true
177+
? probeAndBuildAgentProviders(files.config.agentProviders)
178+
: files.config.agentProviders;
172179
const config: DevspaceUserConfig = {
173180
host: files.config.host ?? "127.0.0.1",
174181
port,
175182
allowedRoots,
176183
publicBaseUrl,
177-
subagents: resolveSubagentsFlag(files.config),
184+
subagents,
185+
...(agentProviders ? { agentProviders } : {}),
178186
};
179187
const auth = {
180188
ownerToken: files.auth.ownerToken ?? generateOwnerToken(),
@@ -277,11 +285,71 @@ async function runDoctor(): Promise<void> {
277285
console.log(`Public MCP URL: ${new URL("/mcp", config.publicBaseUrl).toString()}`);
278286
console.log(`Allowed roots: ${config.allowedRoots.join(", ")}`);
279287
console.log(`Allowed hosts: ${config.allowedHosts.join(", ")}`);
288+
console.log(`Subagents: ${config.subagents ? "enabled" : "disabled"}`);
289+
if (config.subagents) {
290+
const snapshot = getLocalAgentProviderAvailabilitySnapshot();
291+
console.log(
292+
`Agent providers (live): ${formatLocalAgentProviderAvailabilitySummary(snapshot)}`,
293+
);
294+
if (config.agentProviders) {
295+
console.log(
296+
`Agent providers (enabled): ${
297+
config.agentProviders.enabled.length
298+
? config.agentProviders.enabled.join(", ")
299+
: "(empty — no providers)"
300+
}`,
301+
);
302+
if (config.agentProviders.detectedAt) {
303+
console.log(`Agent providers last probe: ${config.agentProviders.detectedAt}`);
304+
}
305+
} else {
306+
console.log("Agent providers (config): missing (compat = all available)");
307+
}
308+
309+
// Refresh lastProbe write-back when subagents on and config exists
310+
if (files.configExists) {
311+
const refreshed = probeAndBuildAgentProviders(files.config.agentProviders);
312+
writeDevspaceConfig({
313+
...files.config,
314+
agentProviders: {
315+
// keep user enable-list if set; only refresh probe metadata + available adds when empty
316+
enabled:
317+
files.config.agentProviders?.enabled ?? refreshed.enabled,
318+
detectedAt: refreshed.detectedAt,
319+
lastProbe: refreshed.lastProbe,
320+
},
321+
});
322+
console.log(`Agent providers probe written to ${files.configPath}`);
323+
}
324+
}
280325
} catch (error) {
281326
console.log(`Config status: ${error instanceof Error ? error.message : String(error)}`);
282327
}
283328
}
284329

330+
/** Probe PATH and build AgentProvidersConfig (available ids in product order). */
331+
function probeAndBuildAgentProviders(
332+
existing?: DevspaceUserConfig["agentProviders"],
333+
): NonNullable<DevspaceUserConfig["agentProviders"]> {
334+
const snapshot = getLocalAgentProviderAvailabilitySnapshot();
335+
const available = new Set(
336+
snapshot.filter((row) => row.available).map((row) => row.name),
337+
);
338+
const enabled =
339+
existing?.enabled && existing.enabled.length > 0
340+
? existing.enabled.filter((id) => LOCAL_AGENT_PROVIDERS.includes(id as never))
341+
: LOCAL_AGENT_PROVIDERS.filter((id) => available.has(id));
342+
return {
343+
enabled,
344+
detectedAt: new Date().toISOString(),
345+
lastProbe: snapshot.map((row) => ({
346+
id: row.name,
347+
available: row.available,
348+
detail: row.reason,
349+
})),
350+
};
351+
}
352+
285353
function runConfigCommand(args: string[]): void {
286354
const [subcommand, key, ...rest] = args;
287355
const files = loadDevspaceFiles();

src/config.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { expandHomePath } from "./roots.js";
44
import type { LoggingConfig, LogFormat, LogLevel } from "./logger.js";
55
import type { OAuthConfig } from "./oauth-provider.js";
66
import { devspaceAgentsDir, devspaceSkillsDir, loadDevspaceFiles } from "./user-config.js";
7+
import type { AgentProvidersConfig } from "./workflow-types.js";
8+
import {
9+
isLocalAgentProvider,
10+
LOCAL_AGENT_PROVIDERS,
11+
type LocalAgentProvider,
12+
} from "./local-agent-profiles.js";
713

814
export type ToolMode = "minimal" | "full" | "codex";
915
export type WidgetMode = "off" | "changes" | "full";
@@ -26,6 +32,12 @@ export interface ServerConfig {
2632
devspaceSkillsDir: string;
2733
devspaceAgentsDir: string;
2834
subagents: boolean;
35+
/**
36+
* Resolved enable-list for agent providers.
37+
* Missing user config → undefined (compat: all live providers).
38+
* Explicit empty → no providers.
39+
*/
40+
agentProviders?: AgentProvidersConfig;
2941
agentDir: string;
3042
logging: LoggingConfig;
3143
}
@@ -234,11 +246,46 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {
234246
env.DEVSPACE_SUBAGENTS === undefined
235247
? files.config.subagents === true
236248
: parseBoolean(env.DEVSPACE_SUBAGENTS),
249+
agentProviders: parseAgentProvidersConfig(
250+
env.DEVSPACE_AGENT_PROVIDERS,
251+
files.config.agentProviders,
252+
),
237253
agentDir: resolve(expandHomePath(env.DEVSPACE_AGENT_DIR ?? files.config.agentDir ?? defaultAgentDir())),
238254
logging: parseLoggingConfig(env),
239255
};
240256
}
241257

258+
/**
259+
* Env `DEVSPACE_AGENT_PROVIDERS=codex,claude` replaces enabled list.
260+
* Missing config → undefined (compat all-available).
261+
* Explicit enabled: [] stays empty.
262+
*/
263+
export function parseAgentProvidersConfig(
264+
envValue: string | undefined,
265+
fileConfig: AgentProvidersConfig | undefined,
266+
): AgentProvidersConfig | undefined {
267+
if (envValue !== undefined) {
268+
const enabled = envValue
269+
.split(",")
270+
.map((entry) => entry.trim())
271+
.filter((entry): entry is LocalAgentProvider => isLocalAgentProvider(entry));
272+
return { enabled };
273+
}
274+
if (!fileConfig) return undefined;
275+
const enabled = (fileConfig.enabled ?? []).filter((id): id is LocalAgentProvider =>
276+
isLocalAgentProvider(id),
277+
);
278+
return {
279+
enabled,
280+
detectedAt: fileConfig.detectedAt,
281+
lastProbe: fileConfig.lastProbe,
282+
};
283+
}
284+
285+
export function defaultAgentProvidersOrder(): LocalAgentProvider[] {
286+
return [...LOCAL_AGENT_PROVIDERS];
287+
}
288+
242289
function parsePublicBaseUrl(value: string): string {
243290
const parsed = new URL(value);
244291
parsed.hash = "";

src/workflow-cli.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export async function runWorkflowWorker(
281281
try {
282282
const source = await readFile(claimed.scriptPath, "utf8");
283283
const parsed = parseWorkflowScript(source, { filename: claimed.scriptPath });
284-
const enabledProviders = resolveEnabledProviders();
284+
const enabledProviders = resolveEnabledProviders(config.agentProviders);
285285
const concurrency = resolveWorkflowConcurrency(
286286
parsed.meta.concurrency,
287287
availableParallelism(),
@@ -474,10 +474,15 @@ function formatRunLine(
474474
return `${run.id} ${run.status} ${run.name}${err}`;
475475
}
476476

477-
function resolveEnabledProviders(): string[] {
477+
function resolveEnabledProviders(
478+
agentProviders?: ServerConfig["agentProviders"],
479+
): string[] {
478480
const snapshot = getLocalAgentProviderAvailabilitySnapshot();
479481
const live = new Set(snapshot.filter((row) => row.available).map((row) => row.name));
480-
return LOCAL_AGENT_PROVIDERS.filter((id) => live.has(id));
482+
if (!agentProviders) {
483+
return LOCAL_AGENT_PROVIDERS.filter((id) => live.has(id));
484+
}
485+
return agentProviders.enabled.filter((id) => live.has(id as never));
481486
}
482487

483488
function splitFlags(args: string[]): {

0 commit comments

Comments
 (0)