Browse Source

refactor(council): extract normalizeCouncillorModels to a pure helper

Move the councillor model-chain normalization into a standalone
utils module with no schema/validation dependency, so CouncilManager
no longer transitively pulls in zod via council-schema. The schema
module re-exports the helper so existing imports are unaffected.

Addresses Greptile P2 on PR #664 (runtime dependency from manager
value-importing the schema module).
Jiajun0413 1 month ago
parent
commit
a97ea0e5a2
3 changed files with 37 additions and 21 deletions
  1. 6 16
      src/config/council-schema.ts
  2. 2 5
      src/council/council-manager.ts
  3. 29 0
      src/utils/councillor-models.ts

+ 6 - 16
src/config/council-schema.ts

@@ -1,4 +1,10 @@
 import { z } from 'zod';
 import { z } from 'zod';
+import {
+  type CouncillorModelEntry,
+  normalizeCouncillorModels,
+} from '../utils/councillor-models';
+
+export type { CouncillorModelEntry };
 
 
 /**
 /**
  * Validates model IDs in "provider/model" format.
  * Validates model IDs in "provider/model" format.
@@ -11,9 +17,6 @@ const ModelIdSchema = z
     'Expected provider/model format (e.g. "openai/gpt-5.4-mini")',
     'Expected provider/model format (e.g. "openai/gpt-5.4-mini")',
   );
   );
 
 
-/** A single model in a councillor fallback chain, with optional variant. */
-export type CouncillorModelEntry = { id: string; variant?: string };
-
 const CouncillorModelEntrySchema = z.object({
 const CouncillorModelEntrySchema = z.object({
   id: ModelIdSchema,
   id: ModelIdSchema,
   variant: z.string().optional(),
   variant: z.string().optional(),
@@ -35,19 +38,6 @@ const CouncillorModelSchema = z
       'tried in order until one responds.',
       'tried in order until one responds.',
   );
   );
 
 
-/** Flatten a councillor model config into an ordered list of model entries. */
-export function normalizeCouncillorModels(
-  model: string | Array<string | CouncillorModelEntry>,
-  fallbackVariant?: string,
-): CouncillorModelEntry[] {
-  const raw = Array.isArray(model) ? model : [model];
-  return raw.map((entry) =>
-    typeof entry === 'string'
-      ? { id: entry, variant: fallbackVariant }
-      : { id: entry.id, variant: entry.variant ?? fallbackVariant },
-  );
-}
-
 /**
 /**
  * Configuration for a single councillor within a preset.
  * Configuration for a single councillor within a preset.
  * Each councillor is an independent LLM that processes the same prompt.
  * Each councillor is an independent LLM that processes the same prompt.

+ 2 - 5
src/council/council-manager.ts

@@ -15,11 +15,8 @@ import {
   COUNCILLOR_STAGGER_MS,
   COUNCILLOR_STAGGER_MS,
   TMUX_SPAWN_DELAY_MS,
   TMUX_SPAWN_DELAY_MS,
 } from '../config/constants';
 } from '../config/constants';
-import {
-  type CouncillorConfig,
-  type CouncilResult,
-  normalizeCouncillorModels,
-} from '../config/council-schema';
+import type { CouncillorConfig, CouncilResult } from '../config/council-schema';
+import { normalizeCouncillorModels } from '../utils/councillor-models';
 import { log } from '../utils/logger';
 import { log } from '../utils/logger';
 import {
 import {
   extractSessionResult,
   extractSessionResult,

+ 29 - 0
src/utils/councillor-models.ts

@@ -0,0 +1,29 @@
+/**
+ * Pure helpers for councillor model fallback chains.
+ *
+ * Kept free of any schema/validation library import so that runtime consumers
+ * (e.g. `CouncilManager`) can resolve a councillor's ordered model chain
+ * without pulling in zod or the config schema module.
+ */
+
+/** A single model in a councillor fallback chain, with optional variant. */
+export type CouncillorModelEntry = { id: string; variant?: string };
+
+/**
+ * Flatten a councillor model config into an ordered list of model entries.
+ *
+ * Accepts either a single "provider/model" string or an ordered fallback
+ * chain (array of strings and/or `{ id, variant }` entries). Entries that
+ * don't carry their own variant fall back to the shared `fallbackVariant`.
+ */
+export function normalizeCouncillorModels(
+  model: string | Array<string | CouncillorModelEntry>,
+  fallbackVariant?: string,
+): CouncillorModelEntry[] {
+  const raw = Array.isArray(model) ? model : [model];
+  return raw.map((entry) =>
+    typeof entry === 'string'
+      ? { id: entry, variant: fallbackVariant }
+      : { id: entry.id, variant: entry.variant ?? fallbackVariant },
+  );
+}