File size: 3,550 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
 * Build the OmniRoute `customModels` entries for Factory Droid's `settings.json`.
 *
 * Ported from upstream PR decolua/9router#618 (author Anurag Saxena) β€” multi-model
 * support for the Factory Droid CLI tool. Adapted to OmniRoute branding
 * (`custom:OmniRoute-<i>`) and extracted as a pure helper so the route-handler
 * logic is unit-testable without touching the filesystem.
 */

export interface DroidCustomModelOptions {
  /** Already-normalized base URL (callers must ensure /v1 suffix). */
  baseUrl: string;
  /** API key to embed in every entry. */
  apiKey: string;
  /**
   * Model id (e.g. `"openai/gpt-5"`) that should appear first in the array.
   * When omitted (or not in `models`), entry index 0 stays first.
   * When the empty string `""` is passed explicitly, no reordering happens
   * β€” the caller signalled "do not promote any model to default".
   */
  activeModel?: string;
}

export interface DroidCustomModelEntry {
  model: string;
  id: string;
  index: number;
  baseUrl: string;
  apiKey: string;
  displayName: string;
  maxOutputTokens: number;
  noImageSupport: boolean;
  provider: string;
}

/**
 * Returns the trimmed, deduplicated, non-empty model ids in input order.
 * Accepts either a `models` array (multi-model, upstream #618) or a legacy
 * `model` string (single-model, pre-#618 behavior).
 */
export function normalizeDroidModelList(input: {
  model?: unknown;
  models?: unknown;
}): string[] {
  const raw: unknown[] = Array.isArray(input.models)
    ? input.models
    : typeof input.model === "string"
      ? [input.model]
      : [];

  const seen = new Set<string>();
  const out: string[] = [];
  for (const m of raw) {
    if (typeof m !== "string") continue;
    const trimmed = m.trim();
    if (!trimmed || seen.has(trimmed)) continue;
    seen.add(trimmed);
    out.push(trimmed);
  }
  return out;
}

/**
 * Build the final `customModels` array. Throws when `models` is empty β€”
 * callers should validate before invoking.
 */
export function buildDroidCustomModels(
  models: string[],
  opts: DroidCustomModelOptions
): DroidCustomModelEntry[] {
  if (models.length === 0) {
    throw new Error("buildDroidCustomModels requires at least one model");
  }

  // Default index resolution:
  //   - undefined  β†’ 0 (first entry stays first)
  //   - ""         β†’ -1 (do not promote anything)
  //   - <value>    β†’ index of value in models (or 0 if not found)
  let defaultIndex: number;
  if (typeof opts.activeModel === "string") {
    if (opts.activeModel === "") {
      defaultIndex = -1;
    } else {
      const idx = models.indexOf(opts.activeModel);
      defaultIndex = idx >= 0 ? idx : 0;
    }
  } else {
    defaultIndex = 0;
  }

  const entries: DroidCustomModelEntry[] = models.map((m, i) => ({
    model: m,
    id: `custom:OmniRoute-${i}`,
    index: i,
    baseUrl: opts.baseUrl,
    apiKey: opts.apiKey,
    displayName: m,
    maxOutputTokens: 131072,
    noImageSupport: false,
    provider: "openai",
  }));

  if (defaultIndex > 0 && entries[defaultIndex]) {
    const [defaultEntry] = entries.splice(defaultIndex, 1);
    entries.unshift({ ...defaultEntry, index: 0 });
    entries.forEach((e, i) => {
      e.index = i;
      e.id = `custom:OmniRoute-${i}`;
    });
  }

  return entries;
}

/** True when a `customModels` entry was written by OmniRoute (any index). */
export function isOmniRouteCustomModel(entry: { id?: unknown } | null | undefined): boolean {
  return typeof entry?.id === "string" && entry.id.startsWith("custom:OmniRoute");
}