import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { isDeepStrictEqual } from "node:util"; import { materializeProfile, resolveDefaultProfileForProvider, USER_PROFILE_TEMPLATES, } from "../config/default-profile-catalog.js"; import type { DefaultProfileKey } from "../config/default-profile-names.js"; import { getIsPlatform } from "../config/env-registry.js"; import { invalidateConfigCache } from "../config/loader.js"; import { type DefaultProviderConfig, DefaultProviderSchema, isByokDefaultProviderChoice, LLMConfigBase, type LLMProvider, type ProfileEntry, } from "../config/schemas/llm.js"; import { ROUTING_IDENTITY_PROVIDERS } from "../providers/inference/auth.js"; import { getLogger } from "../util/logger.js"; import { completedProfileBody } from "./custom-profile-ensure.js"; const log = getLogger("byok-default-profile-ensure"); // Converts BYOK-hatched installs from the hatch-era profile layout (disabled // managed stubs for the default keys plus editable `custom-*` copies) onto // the code-defined default profiles: the stubs and unedited copies are // removed so `balanced`/`quality-optimized`/`cost-optimized` resolve active // and read-only from the default provider's column of the intent x provider // matrix, and every named reference to a removed `custom-*` entry is // repointed at the bare key. A `custom-*` copy the user edited is kept // untouched as an ordinary user profile, references included. // // The pass also runs on installs whose default provider is now `vellum` // (hatched BYOK, later platform-connected): the copy is compared against // the provider recorded in its own body, with corroboration that the // provider is hatch provenance and not a user re-provision (see // `isKnownUneditedBody` / `uniformCopyProvider`). // // "Unedited" is judged against what hatch seeding actually left on disk, not // the raw template: both the copy and the template are normalized through the // completion `ensureCompleteCustomProfiles` bakes onto every user-source // profile each boot, the model is accepted from the current intent resolution // or a git-verified historical era (`HISTORICAL_INTENT_MODELS`), and `label`/ // `status` are user overlay state: a rename or disable survives conversion // as a thin managed stub on the bare key (except a rename colliding with // the frozen hatch-stub label, which is dropped at the carry arm). // `llm.advisorProfile` and `llm.activeProfile` are re-validated in the same // write because `seedInferenceProfiles` runs earlier in boot and judged the // pre-conversion state. // // This is a boot ensure pass rather than a workspace migration because // "unedited" is judged against the live catalog: the comparison template is // `materializeProfile(USER_PROFILE_TEMPLATES[...])`, which resolves the // current per-provider model intents, and migrations are frozen // self-contained snapshots that may not import it (see // workspace/migrations/AGENTS.md). Running unconditionally each boot (the // `ensureDefaultProvider` pattern) also covers configs restored from backups // and freshly-hatched installs whose seeder still wrote the legacy layout. // // Idempotent and write-avoidant: the file is rewritten only when at least one // stub or copy was removed. /** * The default keys BYOK hatching writes to disk. `latency-optimized` is absent * by construction: no install carries a hatch stub or a * `custom-latency-optimized` copy for it, and listing it here would make * `uniformCopyProvider` demand a copy that cannot exist and convert nothing. */ const HATCH_ERA_PROFILE_KEYS = [ "balanced", "quality-optimized", "cost-optimized", ] as const satisfies readonly DefaultProfileKey[]; type HatchEraProfileKey = (typeof HATCH_ERA_PROFILE_KEYS)[number]; /** * The exact stub shapes BYOK hatching left on each default key: thin (only * the workspace-owned overlay fields), `source: "managed"`, the frozen * per-key label, and a `status` of `"disabled"` (seeded at hatch, #30367), * `"active"` (re-enabled through the guard; safe to delete because the bare * key resolves active post-conversion), or no `status` key at all (installs * that already existed when #30367 landed got only the label rewrite; * migration 126 thinned those bodies to `{ source, label }`). Deletion * requires the full shape: a thin managed entry differing in any other way * (a guard-side edit on the bare key, or a non-frozen label or status * carried off a retired copy by this pass) is user overlay state and stays. * The carry arm below never writes the frozen label, so a match is always * hatch-written modulo the status toggle. A managed-source entry with any * other key (a platform overlay body) is not a stub and is likewise left * alone. */ const STUB_ONLY_KEYS = new Set(["source", "status", "label", "thinking"]); const HATCH_STUB_LABELS: Record = { balanced: "Balanced (Managed)", "quality-optimized": "Quality (Managed)", "cost-optimized": "Speed (Managed)", }; /** * Migration 097 and `repairAdaptiveThinkingOnManagedProfiles` stamp exactly * this `thinking` value onto managed anthropic-backed entries, hatch stubs * included, so live stubs commonly carry it (confirmed on real workspaces). * A stub's `thinking` must equal this frozen shape byte-for-byte to count as * machinery-written; any other value is treated as user state and keeps the * entry. The carry arm never writes `thinking`, so the idempotency invariant * (deletion predicate matches nothing the carry arm can produce) holds. */ const REPAIR_WRITTEN_THINKING = { enabled: true, streamThinking: true }; function isHatchStub( key: HatchEraProfileKey, entry: Record, ): boolean { return ( entry.source === "managed" && Object.keys(entry).every((k) => STUB_ONLY_KEYS.has(k)) && (!("status" in entry) || entry.status === "disabled" || entry.status === "active") && (!("thinking" in entry) || isDeepStrictEqual(entry.thinking, REPAIR_WRITTEN_THINKING)) && entry.label === HATCH_STUB_LABELS[key] ); } /** * Hatch label suffix on the `custom-*` copies written between #29755 * (2026-05-05) and #30232 (2026-05-10); a copy carrying * `"