fix(#3705): consult model_policy in the install-time bake so agent frontmatter matches dispatch (#3863)
* test(#3705): failing-first coverage for model_policy in the install-time bake * fix(#3705): consult model_policy in the install-time bake so frontmatter matches dispatch * fix(#3705): inject the effective runtime into the policy so runtime_tiers is reached * test(#3705): use assert.doesNotMatch, the assertion that exists * chore(#3705): backfill changeset PR number --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -45,8 +45,9 @@ import modelCatalog = require('./model-catalog.cjs');
|
||||
const { MODEL_PROFILES: GSD_MODEL_PROFILES } = modelCatalog as unknown as { MODEL_PROFILES: Record<string, Record<string, string>> };
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-resolver.cjs is an export= CommonJS module
|
||||
import modelResolverModule = require('./model-resolver.cjs');
|
||||
const { resolveTierEntry: gsdResolveTierEntry } = modelResolverModule as {
|
||||
const { resolveTierEntry: gsdResolveTierEntry, resolveModelPolicy: gsdResolveModelPolicy } = modelResolverModule as {
|
||||
resolveTierEntry: (opts: { runtime: string; tier: string; overrides: unknown }) => { model?: string } | null;
|
||||
resolveModelPolicy: (policy: unknown, tier: string | null | undefined) => string | null;
|
||||
};
|
||||
|
||||
interface ReadOptions {
|
||||
@@ -129,6 +130,14 @@ interface RuntimeProfileMergedConfig {
|
||||
runtime: string | null;
|
||||
model_profile: string;
|
||||
model_profile_overrides: unknown;
|
||||
/**
|
||||
* #3705: the provider-neutral policy (#49). Install-time never read it, so a
|
||||
* project configured for a non-Anthropic provider had its agent frontmatter
|
||||
* rebaked to catalog `anthropic/claude-*` IDs on every update while
|
||||
* dispatch-time resolution honoured the policy correctly — two answers from
|
||||
* one config, and only the frontmatter is what a spawn actually uses.
|
||||
*/
|
||||
model_policy: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +178,11 @@ function readGsdRuntimeProfileResolver(targetDir: string | null = null): Runtime
|
||||
(projectConfig && projectConfig.model_profile_overrides) ||
|
||||
(homeDefaults && homeDefaults.model_profile_overrides) ||
|
||||
null,
|
||||
// #3705: same project-wins-over-home precedence as every other key here.
|
||||
model_policy:
|
||||
(projectConfig && projectConfig.model_policy) ||
|
||||
(homeDefaults && homeDefaults.model_policy) ||
|
||||
null,
|
||||
};
|
||||
|
||||
if (!merged.runtime) return null;
|
||||
@@ -188,6 +202,33 @@ function readGsdRuntimeProfileResolver(targetDir: string | null = null): Runtime
|
||||
if (!agentModels) return null;
|
||||
const tier = agentModels[profile] || agentModels.balanced;
|
||||
if (!tier) return null;
|
||||
// #3705: policy sits between the per-agent override and the tier table —
|
||||
// the position dispatch uses. Measured against `query resolve-model` on the
|
||||
// same config: policy-only -> the policy model; tier-overrides-only -> the
|
||||
// override; BOTH -> the policy model. So policy outranks
|
||||
// `model_profile_overrides`, and an explicit `model_overrides[agent]`
|
||||
// (applied by resolveAgentModelOverride below) outranks policy.
|
||||
//
|
||||
// Calls the exported owner (#49) rather than re-deriving tier -> model here:
|
||||
// a second implementation of that mapping is the very divergence this issue
|
||||
// is, one layer down. A null from it (unknown provider, missing tier key, a
|
||||
// `runtime_tiers` miss) falls through to the tier table — never to null,
|
||||
// which would omit a frontmatter key that used to be written.
|
||||
// The policy object must carry the effective runtime before it is resolved.
|
||||
// `resolveModelPolicy`'s `runtime_tiers` branch reads `policy['runtime']`, but
|
||||
// the documented config shape (docs/CONFIGURATION.md) puts `runtime` at the
|
||||
// TOP level and keeps only `provider`/`runtime_tiers` inside `model_policy`.
|
||||
// Dispatch injects it — `{ ...config.model_policy, runtime: effectiveRuntime }`
|
||||
// (model-resolver.cts) — and passing the policy unmodified here silently
|
||||
// skipped `runtime_tiers` entirely, falling through to the flat hi/med/lo keys
|
||||
// or the catalog tier. That is the same "catalog Anthropic ID baked over a
|
||||
// configured provider" defect this fix exists to close, so the injection is
|
||||
// mirrored rather than assumed.
|
||||
const policyForRuntime = merged.model_policy
|
||||
? { ...(merged.model_policy as Record<string, unknown>), runtime }
|
||||
: null;
|
||||
const policyModel = gsdResolveModelPolicy(policyForRuntime, tier);
|
||||
if (policyModel) return { model: policyModel };
|
||||
return gsdResolveTierEntry({
|
||||
runtime,
|
||||
tier,
|
||||
|
||||
Reference in New Issue
Block a user