feat: model taxonomy gains gpt-5.6-sol + per-host generation defaults

Adds 'gpt-5.6-sol' to the model taxonomy with exact-match-only resolution
(Terra/Luna/suffixed IDs deliberately fall back to generic gpt) and replaces
the hardcoded 'claude' generation default with a validated
HostConfig.defaultModel: codex renders the gpt profile when --model is
absent, every other host keeps claude. Codex ship golden regenerated
accordingly; ADDING_A_HOST documents the new field.
This commit is contained in:
Garry Tan
2026-08-18 13:10:27 -07:00
parent c86e6472eb
commit 819414eda7
8 changed files with 87 additions and 21 deletions
+10 -5
View File
@@ -92,12 +92,13 @@ const HOST_ARG_VAL: HostArg = (() => {
let HOST: Host = HOST_ARG_VAL === 'all' ? 'claude' : HOST_ARG_VAL;
// ─── Model Overlay Selection ────────────────────────────────
// --model is explicit. We do NOT auto-detect from host (host ≠ model).
// Default is 'claude'. Missing overlay file → empty string (graceful).
// --model is explicit. Without it, each host uses HostConfig.defaultModel.
// Host defaults are generation fallbacks, not claims that host === model.
// Missing overlay file → empty string (graceful).
import { ALL_MODEL_NAMES, resolveModel, type Model } from './models';
const MODEL_ARG = process.argv.find(a => a.startsWith('--model'));
const MODEL_ARG_VAL: Model = (() => {
if (!MODEL_ARG) return 'claude';
const MODEL_ARG_VAL: Model | null = (() => {
if (!MODEL_ARG) return null;
const val = MODEL_ARG.includes('=') ? MODEL_ARG.split('=')[1] : process.argv[process.argv.indexOf(MODEL_ARG) + 1];
const resolved = resolveModel(val);
if (!resolved) {
@@ -106,6 +107,10 @@ const MODEL_ARG_VAL: Model = (() => {
return resolved;
})();
function generationModelForHost(host: Host): Model {
return MODEL_ARG_VAL ?? getHostConfig(host).defaultModel;
}
// ─── Catalog Mode (v1.45.0.0 T4) ────────────────────────────
// 'trim' (default): shorten frontmatter description to lead sentence and
// move routing/voice prose into a "## When to invoke" body section.
@@ -753,7 +758,7 @@ function buildContext(
const interactive = interactiveMatch ? interactiveMatch[1] === 'true' : undefined;
return {
skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host],
preambleTier, model: MODEL_ARG_VAL, interactive, explainLevel: EXPLAIN_LEVEL,
preambleTier, model: generationModelForHost(host), interactive, explainLevel: EXPLAIN_LEVEL,
};
}
+10
View File
@@ -14,6 +14,9 @@
* platform-detect, uninstall
*/
import type { Model } from './models';
import { validateModel } from './models';
export interface HostConfig {
/** Unique host identifier (e.g., 'opencode'). Must match filename in hosts/. */
name: string;
@@ -24,6 +27,9 @@ export interface HostConfig {
/** Alternative binary names (e.g., ['droid'] for factory). */
cliAliases?: string[];
/** Model overlay used when generation does not receive an explicit --model. */
defaultModel: Model;
// --- Path Configuration ---
/** Global install path relative to $HOME (e.g., '.config/opencode/skills/gstack'). */
globalRoot: string;
@@ -119,6 +125,10 @@ export function validateHostConfig(config: HostConfig, validResolverNames?: Read
}
}
}
const modelError = validateModel(config.defaultModel);
if (modelError) {
errors.push(`defaultModel ${modelError}`);
}
if (!PATH_REGEX.test(config.globalRoot)) {
errors.push(`globalRoot '${config.globalRoot}' contains invalid characters`);
}
+15 -5
View File
@@ -2,13 +2,17 @@
* Model taxonomy — neutral module with no imports from hosts/ or resolvers/.
*
* Model families supported by model overlays in model-overlays/{family}.md.
* Host configs can reference these as `defaultModel` strings (validated at
* Host configs reference these as `defaultModel` strings (validated at
* generation time), but the model axis is independent of the host axis.
*
* IMPORTANT: host ≠ model. Claude Code can run any Claude model (Opus, Sonnet,
* Haiku, future). Codex CLI runs GPT/o-series models. Cursor and OpenCode can
* front multiple providers. We do NOT auto-detect the model from the host —
* users pass --model explicitly. Default is 'claude'.
* front multiple providers. The generator does NOT auto-detect the model from
* the host — users can pass --model explicitly, otherwise each host supplies
* its own generation default. Exception outside this module: ./setup detects
* the Codex model from ${CODEX_HOME:-~/.codex}/config.toml
* (scripts/resolve-codex-generation-model.ts) and passes it as an explicit
* --model.
*/
export const ALL_MODEL_NAMES = [
@@ -19,6 +23,7 @@ export const ALL_MODEL_NAMES = [
'sonnet-5',
'gpt',
'gpt-5.4',
'gpt-5.6-sol',
'gemini',
'o-series',
] as const;
@@ -29,10 +34,11 @@ export type Model = (typeof ALL_MODEL_NAMES)[number];
* Resolve a model argument from CLI input to a known Model family.
*
* Precedence rules:
* 1. Exact match against ALL_MODEL_NAMES → return as-is.
* 1. Exact match against ALL_MODEL_NAMES → return as-is. This is the ONLY
* path that selects `gpt-5.6-sol` — Sol is intentionally exact-only.
* 2. Family heuristics for common variants:
* - `gpt-5.4-mini`, `gpt-5.4-turbo`, `gpt-5.4-*` → `gpt-5.4`
* - `gpt-*` (anything else GPT) → `gpt`
* - `gpt-*` (anything else GPT, including other 5.6 variants) → `gpt`
* - `o3`, `o4`, `o4-mini`, `o1`, `o1-mini`, `o1-pro` → `o-series`
* - `claude-*` (sonnet, opus, haiku, any version) → `claude`
* - `gemini-*` (2.5-pro, flash, etc.) → `gemini`
@@ -52,6 +58,10 @@ export function resolveModel(input: string): Model | null {
}
// Family heuristics
// Sol never reaches here — the exact match above already returned it. Do
// not add a Sol family pattern: Terra, Luna, future 5.6 variants, and
// suffixed model IDs must NOT inherit Sol's behavioral profile; they fall
// through to the generic `gpt` family below.
if (/^gpt-5\.4(-|$)/.test(s)) return 'gpt-5.4';
if (/^gpt(-|$)/.test(s)) return 'gpt';
if (/^o[0-9]+(-|$)/.test(s)) return 'o-series';