Skip to content

Commit 65711f4

Browse files
authored
Merge pull request #30 from Multiplier-Labs/feat/workflow-model-selection
Add model selection to workflow definitions
2 parents 181e3ea + a33e712 commit 65711f4

File tree

7 files changed

+62
-4
lines changed

7 files changed

+62
-4
lines changed

server/workflow-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface ReviewRepoConfig {
2222
/** Workflow kind to run. Defaults to 'code-review.daily'. */
2323
kind?: string
2424
customPrompt?: string
25+
/** Claude model to use for this workflow (e.g. 'claude-sonnet-4-6'). Omit for system default. */
26+
model?: string
2527
}
2628

2729
export interface WorkflowConfig {

server/workflow-loader.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* outputDir: .codekin/reports/code-review
2121
* filenameSuffix: _code-review-daily.md
2222
* commitMessage: chore: code review
23+
* model: claude-sonnet-4-6 # optional — defaults to system default
2324
* ---
2425
* You are performing a daily automated code review...
2526
*
@@ -54,6 +55,7 @@ export interface WorkflowDef {
5455
outputDir: string
5556
filenameSuffix: string
5657
commitMessage: string
58+
model?: string
5759
prompt: string
5860
}
5961

@@ -88,6 +90,7 @@ function parseMdWorkflow(content: string, sourcePath: string): WorkflowDef {
8890
outputDir: meta.outputDir,
8991
filenameSuffix: meta.filenameSuffix,
9092
commitMessage: meta.commitMessage,
93+
model: meta.model,
9194
prompt,
9295
}
9396
}
@@ -233,9 +236,11 @@ function registerWorkflow(engine: WorkflowEngine, sessions: SessionManager, def:
233236
const repoPath = input.repoPath as string
234237
const repoName = (input.repoName as string) || repoPath.split('/').pop() || 'unknown'
235238

239+
const model = (input.model as string | undefined) || def.model
236240
const session = sessions.create(`${def.sessionPrefix}:${repoName}`, repoPath, {
237241
source: 'workflow',
238242
groupDir: repoPath,
243+
model,
239244
})
240245

241246
console.log(`[workflow:${def.kind}] Created session ${session.id} for ${repoName} (run ${ctx.runId})`)

server/workflow-routes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function syncSchedules(sessions?: SessionManager) {
6969
id: repo.id,
7070
kind: repo.kind ?? 'code-review.daily',
7171
cronExpression: repo.cronExpression,
72-
input: { repoPath: repo.repoPath, repoName: repo.name, customPrompt: repo.customPrompt },
72+
input: { repoPath: repo.repoPath, repoName: repo.name, customPrompt: repo.customPrompt, model: repo.model },
7373
enabled: repo.enabled,
7474
})
7575
}
@@ -248,7 +248,7 @@ export function createWorkflowRouter(verifyToken: VerifyFn, extractToken: Extrac
248248
})
249249

250250
router.post('/config/repos', (req, res) => {
251-
const { id, name, repoPath, cronExpression, enabled, customPrompt, kind } = req.body as Partial<ReviewRepoConfig>
251+
const { id, name, repoPath, cronExpression, enabled, customPrompt, kind, model } = req.body as Partial<ReviewRepoConfig>
252252
if (!id || !name || !repoPath || !cronExpression) {
253253
return res.status(400).json({ error: 'Missing required fields: id, name, repoPath, cronExpression' })
254254
}
@@ -268,6 +268,7 @@ export function createWorkflowRouter(verifyToken: VerifyFn, extractToken: Extrac
268268
enabled: enabled !== false,
269269
kind,
270270
customPrompt,
271+
model,
271272
})
272273

273274
// Re-sync schedules with updated config

src/components/AddWorkflowModal.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { useRepos } from '../hooks/useRepos'
1313
import { listKinds } from '../lib/workflowApi'
1414
import type { ReviewRepoConfig, WorkflowKindInfo } from '../lib/workflowApi'
1515
import {
16-
WORKFLOW_KINDS, DAY_PRESETS, DAY_INDIVIDUAL,
16+
WORKFLOW_KINDS, DAY_PRESETS, DAY_INDIVIDUAL, MODEL_OPTIONS,
1717
buildCron, describeCron, slugify, kindCategory,
1818
isBiweeklyDow,
1919
} from '../lib/workflowHelpers'
@@ -31,6 +31,7 @@ interface FormState {
3131
cronMinute: number
3232
cronDow: string
3333
customPrompt: string
34+
model: string
3435
}
3536

3637
interface Props {
@@ -337,6 +338,27 @@ function StepSchedule({
337338
</div>
338339
</div>
339340

341+
{/* Model selection */}
342+
<div>
343+
<label className="block text-[13px] font-medium text-neutral-3 mb-2">Model</label>
344+
<div className="flex gap-1.5">
345+
{MODEL_OPTIONS.map(m => (
346+
<button
347+
key={m.value}
348+
type="button"
349+
onClick={() => onChange({ model: m.value })}
350+
className={`rounded-md border px-3 py-1.5 text-[13px] font-medium transition-colors ${
351+
form.model === m.value
352+
? 'border-accent-6 bg-accent-9/40 text-accent-2'
353+
: 'border-neutral-7 bg-neutral-10 text-neutral-3 hover:border-neutral-6 hover:text-neutral-2'
354+
}`}
355+
>
356+
{m.label}
357+
</button>
358+
))}
359+
</div>
360+
</div>
361+
340362
<div>
341363
<label className="block text-[13px] font-medium text-neutral-3 mb-1.5">
342364
Focus areas <span className="text-neutral-5 font-normal">(optional)</span>
@@ -369,6 +391,7 @@ export function AddWorkflowModal({ token, onClose, onAdd }: Props) {
369391
cronMinute: 0,
370392
cronDow: '*',
371393
customPrompt: '',
394+
model: '',
372395
})
373396
const [saving, setSaving] = useState(false)
374397
const [formError, setFormError] = useState<string | null>(null)
@@ -412,6 +435,7 @@ export function AddWorkflowModal({ token, onClose, onAdd }: Props) {
412435
kind: form.kind,
413436
enabled: true,
414437
customPrompt: form.customPrompt.trim() || undefined,
438+
model: form.model || undefined,
415439
})
416440
onClose()
417441
} catch (err) {

src/components/EditWorkflowModal.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useState } from 'react'
77
import { IconX, IconLoader2 } from '@tabler/icons-react'
88
import type { ReviewRepoConfig } from '../lib/workflowApi'
99
import {
10-
WORKFLOW_KINDS, DAY_PRESETS, DAY_INDIVIDUAL, isBiweeklyDow,
10+
WORKFLOW_KINDS, DAY_PRESETS, DAY_INDIVIDUAL, MODEL_OPTIONS, isBiweeklyDow,
1111
buildCron, parseCron, describeCron, kindLabel,
1212
} from '../lib/workflowHelpers'
1313
import { CategoryBadge } from './WorkflowBadges'
@@ -36,6 +36,7 @@ export function EditWorkflowModal({ repo, onClose, onSave }: Props) {
3636
cronMinute: parsed.minute,
3737
cronDow: parsed.dow,
3838
customPrompt: repo.customPrompt ?? '',
39+
model: repo.model ?? '',
3940
})
4041
const [saving, setSaving] = useState(false)
4142
const [formError, setFormError] = useState<string | null>(null)
@@ -49,6 +50,7 @@ export function EditWorkflowModal({ repo, onClose, onSave }: Props) {
4950
kind: form.kind,
5051
cronExpression: buildCron(form.cronHour, form.cronDow, form.cronMinute),
5152
customPrompt: form.customPrompt.trim() || undefined,
53+
model: form.model || undefined,
5254
})
5355
onClose()
5456
} catch (err) {
@@ -162,6 +164,23 @@ export function EditWorkflowModal({ repo, onClose, onSave }: Props) {
162164
</div>
163165
</div>
164166

167+
{/* Model selection */}
168+
<div>
169+
<label className="block text-[13px] font-medium text-neutral-3 mb-2">Model</label>
170+
<div className="flex gap-1.5">
171+
{MODEL_OPTIONS.map(m => (
172+
<button
173+
key={m.value}
174+
type="button"
175+
onClick={() => setForm(f => ({ ...f, model: m.value }))}
176+
className={btnClass(form.model === m.value)}
177+
>
178+
{m.label}
179+
</button>
180+
))}
181+
</div>
182+
</div>
183+
165184
{/* Custom prompt */}
166185
<div>
167186
<label className="block text-[13px] font-medium text-neutral-3 mb-1.5">

src/lib/workflowApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export interface ReviewRepoConfig {
6666
enabled: boolean
6767
kind?: string
6868
customPrompt?: string
69+
model?: string
6970
}
7071

7172
export interface WorkflowConfig {

src/lib/workflowHelpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export const WORKFLOW_KINDS = [
1515
{ value: 'complexity.weekly', label: 'Complexity Report', category: 'assessment' as WorkflowCategory },
1616
]
1717

18+
export const MODEL_OPTIONS = [
19+
{ value: '', label: 'Default (Opus)' },
20+
{ value: 'claude-sonnet-4-6', label: 'Sonnet 4.6' },
21+
{ value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' },
22+
]
23+
1824
export const DAY_PRESETS = [
1925
{ label: 'Daily', dow: '*' },
2026
{ label: 'Weekdays', dow: '1-5' },

0 commit comments

Comments
 (0)