Skip to content

Commit f6c635a

Browse files
committed
refactor(providers): extract shared utility task logic into utilityTask module
- consolidate commitGen and titleGen around shared helper - simplify ProviderIcon by removing duplicated logic
1 parent 5a0f27f commit f6c635a

4 files changed

Lines changed: 202 additions & 270 deletions

File tree

src/renderer/components/providers/ProviderIcon.tsx

Lines changed: 20 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import type { CSSProperties, ReactNode } from "react";
22
import type { StatusTone } from "./statusTone";
3+
import {
4+
getUtilityTaskCandidates,
5+
getUtilityTaskDefaultsHint,
6+
resolveUtilityTaskConfig,
7+
type UtilityTaskCandidateAgent,
8+
type UtilityTaskConfigAgent,
9+
type UtilityTaskDefaults,
10+
} from "./utilityTask";
11+
12+
export { AUTO_PROVIDER_PREFERENCE_ORDER, sortByAutoPreference } from "./utilityTask";
313

414
// --- Icon registry ---
515

@@ -259,35 +269,9 @@ export function getConfigNormalizer(kind: string): ConfigNormalizer | undefined
259269
return CONFIG_NORMALIZER_REGISTRY.get(kind);
260270
}
261271

262-
/**
263-
* Preference order used by Auto for utility tasks (title gen, commit gen, conflict resolver).
264-
* Codex first by user preference; others fall back alphabetically afterwards by registration order.
265-
*/
266-
export const AUTO_PROVIDER_PREFERENCE_ORDER: readonly string[] = [
267-
"codex",
268-
"claude",
269-
"gemini",
270-
"opencode",
271-
"cursor",
272-
"copilot",
273-
];
274-
275-
export function sortByAutoPreference<T extends { kind: string }>(items: readonly T[]): T[] {
276-
const rank = (kind: string) => {
277-
const idx = AUTO_PROVIDER_PREFERENCE_ORDER.indexOf(kind);
278-
return idx < 0 ? AUTO_PROVIDER_PREFERENCE_ORDER.length : idx;
279-
};
280-
return [...items].sort((a, b) => rank(a.kind) - rank(b.kind));
281-
}
282-
283272
// --- Commit generation defaults registry ---
284273

285-
export interface CommitGenDefaults {
286-
label?: string;
287-
hint?: string;
288-
model: string;
289-
effort: string;
290-
}
274+
export interface CommitGenDefaults extends UtilityTaskDefaults {}
291275

292276
const COMMIT_GEN_REGISTRY = new Map<string, CommitGenDefaults>();
293277

@@ -300,24 +284,12 @@ export function getCommitGenDefaults(kind: string): CommitGenDefaults | undefine
300284
}
301285

302286
export function getCommitGenDefaultsHint(): string | undefined {
303-
const entries = [...COMMIT_GEN_REGISTRY.values()]
304-
.flatMap((defaults) =>
305-
defaults.hint && defaults.label ? [`${defaults.label} -> ${defaults.hint}`] : [],
306-
)
307-
.sort()
308-
.join(", ");
309-
310-
return entries ? `Defaults: ${entries}` : undefined;
287+
return getUtilityTaskDefaultsHint(COMMIT_GEN_REGISTRY.values());
311288
}
312289

313290
// --- Title generation defaults registry ---
314291

315-
export interface TitleGenDefaults {
316-
label?: string;
317-
hint?: string;
318-
model: string;
319-
effort: string;
320-
}
292+
export interface TitleGenDefaults extends UtilityTaskDefaults {}
321293

322294
const TITLE_GEN_REGISTRY = new Map<string, TitleGenDefaults>();
323295

@@ -330,24 +302,12 @@ export function getTitleGenDefaults(kind: string): TitleGenDefaults | undefined
330302
}
331303

332304
export function getTitleGenDefaultsHint(): string | undefined {
333-
const entries = [...TITLE_GEN_REGISTRY.values()]
334-
.flatMap((defaults) =>
335-
defaults.hint && defaults.label ? [`${defaults.label} -> ${defaults.hint}`] : [],
336-
)
337-
.sort()
338-
.join(", ");
339-
340-
return entries ? `Defaults: ${entries}` : undefined;
305+
return getUtilityTaskDefaultsHint(TITLE_GEN_REGISTRY.values());
341306
}
342307

343308
// --- Conflict resolver defaults registry ---
344309

345-
export interface ConflictResolverDefaults {
346-
label?: string;
347-
hint?: string;
348-
model: string;
349-
effort: string;
350-
}
310+
export interface ConflictResolverDefaults extends UtilityTaskDefaults {}
351311

352312
const CONFLICT_RESOLVER_REGISTRY = new Map<string, ConflictResolverDefaults>();
353313

@@ -360,73 +320,22 @@ export function getConflictResolverDefaults(kind: string): ConflictResolverDefau
360320
}
361321

362322
export function getConflictResolverDefaultsHint(): string | undefined {
363-
const entries = [...CONFLICT_RESOLVER_REGISTRY.values()]
364-
.flatMap((defaults) =>
365-
defaults.hint && defaults.label ? [`${defaults.label} -> ${defaults.hint}`] : [],
366-
)
367-
.sort()
368-
.join(", ");
369-
370-
return entries ? `Defaults: ${entries}` : undefined;
323+
return getUtilityTaskDefaultsHint(CONFLICT_RESOLVER_REGISTRY.values());
371324
}
372325

373-
interface ConflictResolverAgentLike {
374-
kind: string;
375-
installed?: boolean;
376-
authState?: string;
377-
capabilities: { models: { id: string }[] };
378-
}
379-
380-
function hasPreferredConflictResolverModel(agent: ConflictResolverAgentLike): boolean {
381-
const defaults = getConflictResolverDefaults(agent.kind);
382-
if (!defaults?.model) return true;
383-
return agent.capabilities.models.some((m) => m.id === defaults.model);
384-
}
326+
type ConflictResolverAgentLike = UtilityTaskCandidateAgent;
385327

386328
export function getConflictResolverCandidates<T extends ConflictResolverAgentLike>(
387329
agentStatuses: readonly T[],
388330
provider: string,
389331
): T[] {
390-
const available = agentStatuses.filter((a) => a.installed !== false && a.authState !== "missing");
391-
if (provider === "auto") {
392-
const withPreferred = available.filter(hasPreferredConflictResolverModel);
393-
return sortByAutoPreference(withPreferred.length > 0 ? withPreferred : available);
394-
}
395-
return available.filter((agent) => agent.kind === provider);
332+
return getUtilityTaskCandidates(agentStatuses, provider, getConflictResolverDefaults);
396333
}
397334

398335
export function resolveConflictResolverConfig(
399-
agent:
400-
| {
401-
kind: string;
402-
capabilities: {
403-
models: { id: string }[];
404-
efforts: string[];
405-
modelEfforts: Record<string, string[]>;
406-
defaultEffort?: string | undefined;
407-
};
408-
}
409-
| undefined,
336+
agent: UtilityTaskConfigAgent | undefined,
410337
model: string,
411338
effort: string,
412339
): { model: string; effort: string; availableEfforts: string[] } {
413-
if (!agent) return { model: "", effort: "", availableEfforts: [] };
414-
415-
const defaults = getConflictResolverDefaults(agent.kind);
416-
const nextModel = agent.capabilities.models.some((m) => m.id === model)
417-
? model
418-
: defaults?.model && agent.capabilities.models.some((m) => m.id === defaults.model)
419-
? defaults.model
420-
: (agent.capabilities.models[0]?.id ?? "");
421-
422-
const modelEfforts = agent.capabilities.modelEfforts[nextModel];
423-
const availableEfforts = modelEfforts?.length ? modelEfforts : agent.capabilities.efforts;
424-
if (availableEfforts.length === 0) return { model: nextModel, effort: "", availableEfforts };
425-
426-
if (availableEfforts.includes(effort)) return { model: nextModel, effort, availableEfforts };
427-
428-
const fallback = [defaults?.effort, agent.capabilities.defaultEffort, availableEfforts[0]].find(
429-
(c) => c && availableEfforts.includes(c!),
430-
);
431-
return { model: nextModel, effort: fallback ?? "", availableEfforts };
340+
return resolveUtilityTaskConfig(agent, model, effort, getConflictResolverDefaults);
432341
}

src/renderer/components/providers/commitGen.ts

Lines changed: 7 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,8 @@ import type {
44
GenerateCommitMessageResult,
55
ProjectLocation,
66
} from "@/shared/contracts";
7-
import { getCommitGenDefaults, sortByAutoPreference } from "./ProviderIcon";
8-
9-
function resolveCommitGenModel(agent: AgentStatus): string {
10-
const defaults = getCommitGenDefaults(agent.kind);
11-
if (defaults?.model && agent.capabilities.models.some((m) => m.id === defaults.model)) {
12-
return defaults.model;
13-
}
14-
// Fall back to any "mini" variant — commit gen is a lightweight task.
15-
const mini = agent.capabilities.models.find(
16-
(m) => /\bmini\b/i.test(m.id) || /\bmini\b/i.test(m.label),
17-
);
18-
if (mini) return mini.id;
19-
return agent.capabilities.models[0]?.id ?? "";
20-
}
21-
22-
function resolveCommitGenEfforts(agent: AgentStatus, model: string): string[] {
23-
const modelEfforts = agent.capabilities.modelEfforts[model];
24-
if (modelEfforts && modelEfforts.length > 0) {
25-
return modelEfforts;
26-
}
27-
return agent.capabilities.efforts;
28-
}
29-
30-
function isCommitGenCandidate(agent: AgentStatus): boolean {
31-
return agent.installed && agent.authState !== "missing";
32-
}
33-
34-
function hasPreferredCommitGenModel(agent: AgentStatus): boolean {
35-
const defaults = getCommitGenDefaults(agent.kind);
36-
if (!defaults?.model) return true;
37-
return agent.capabilities.models.some((m) => m.id === defaults.model);
38-
}
7+
import { getCommitGenDefaults } from "./ProviderIcon";
8+
import { getMiniModelId, getUtilityTaskCandidates, resolveUtilityTaskConfig } from "./utilityTask";
399

4010
function toErrorMessage(error: unknown): string {
4111
return error instanceof Error ? error.message : String(error);
@@ -50,58 +20,17 @@ export function resolveCommitGenConfig(
5020
effort: string;
5121
availableEfforts: string[];
5222
} {
53-
if (!agent) {
54-
return {
55-
model: "",
56-
effort: "",
57-
availableEfforts: [],
58-
};
59-
}
60-
61-
const nextModel = agent.capabilities.models.some((m) => m.id === model)
62-
? model
63-
: resolveCommitGenModel(agent);
64-
const availableEfforts = resolveCommitGenEfforts(agent, nextModel);
65-
if (availableEfforts.length === 0) {
66-
return {
67-
model: nextModel,
68-
effort: "",
69-
availableEfforts,
70-
};
71-
}
72-
73-
if (availableEfforts.includes(effort)) {
74-
return {
75-
model: nextModel,
76-
effort,
77-
availableEfforts,
78-
};
79-
}
80-
81-
const defaults = getCommitGenDefaults(agent.kind);
82-
const fallbackEffort = [
83-
defaults?.effort,
84-
agent.capabilities.defaultEffort,
85-
availableEfforts[0],
86-
].find((candidate) => Boolean(candidate) && availableEfforts.includes(candidate!));
87-
88-
return {
89-
model: nextModel,
90-
effort: fallbackEffort ?? "",
91-
availableEfforts,
92-
};
23+
return resolveUtilityTaskConfig(agent, model, effort, getCommitGenDefaults, {
24+
// Fall back to any "mini" variant — commit gen is a lightweight task.
25+
fallbackModelIds: (candidate, defaults) => [defaults?.model, getMiniModelId(candidate)],
26+
});
9327
}
9428

9529
export function getCommitGenCandidates(
9630
agentStatuses: readonly AgentStatus[],
9731
provider: string,
9832
): AgentStatus[] {
99-
const available = agentStatuses.filter(isCommitGenCandidate);
100-
if (provider === "auto") {
101-
const withPreferred = available.filter(hasPreferredCommitGenModel);
102-
return sortByAutoPreference(withPreferred.length > 0 ? withPreferred : available);
103-
}
104-
return available.filter((agent) => agent.kind === provider);
33+
return getUtilityTaskCandidates(agentStatuses, provider, getCommitGenDefaults);
10534
}
10635

10736
export async function generateCommitMessageWithFallback(input: {

0 commit comments

Comments
 (0)