import { type SelectModelDeps } from "../boxes/select-model.js"; import { type ProjectDetectionOptions } from "../project-resolution.js"; import type { ModelRouting } from "#shared/agent-definition.js"; import type { Prompter } from "../prompter.js"; import { runVercelFlow } from "./vercel.js"; /** The current model id, its routing, and whether `/model` can rewrite it. */ export interface CurrentAgentModel { id: string | null; routing: ModelRouting | null; /** * The authored `model` is a string the source editor can rewrite. False for a * source-backed SDK model call (`gateway(...)`, `anthropic(...)`), which is * not a string literal — independent of how the model routes. */ editable: boolean; } /** Injected for tests; defaults to the real reads, fetches, and source edit. */ export interface ModelFlowDeps { /** * Reads the model the runtime currently serves and how it routes; both null * before the first compile. */ readCurrentModel: (appRoot: string) => Promise; /** Applies the picked slug to authored source. */ applyModel: (input: { appRoot: string; slug: string; }) => Promise; /** Catalog fetch behind the shared model picker. */ selectModel?: SelectModelDeps; /** Reads how the model is backed right now, for the menu's provider row. */ detectProviderStatus: typeof detectModelProviderStatus; /** The provider sub-flow behind the menu's provider row. */ runVercelFlow: typeof runVercelFlow; } /** * How the agent's model is backed right now, as far as the local directory * shows: a linked Vercel project, a gateway credential in an env file, or * nothing detectable. An external provider (own ANTHROPIC_API_KEY etc.) * leaves no marker eve owns, so it reads as `unset`. */ export type ModelProviderStatus = { kind: "unset"; } | { kind: "gateway-project"; projectName: string; teamName?: string; } | { kind: "gateway-key"; envKey: "AI_GATEWAY_API_KEY" | "VERCEL_OIDC_TOKEN"; envFile: string; }; /** * A provider sub-flow run that actually moved the provider: the credential * the link flow verified landed in an env file (when one did), paired with * the re-detected {@link ModelProviderStatus} — the same read the menu's * provider row shows, so every surface reports one truth. The sub-flow's * external-provider branch only shows instructions — nothing changes on * disk — so it never surfaces as an outcome. */ export interface ModelProviderOutcome { credential?: "VERCEL_OIDC_TOKEN" | "AI_GATEWAY_API_KEY"; status: ModelProviderStatus; } export type ModelFlowResult = { kind: "cancelled"; } | { kind: "done"; /** The last apply line, when the model was changed this session. */ modelMessage?: string; /** The last provider sub-flow outcome, when one ran to completion. */ providerOutcome?: ModelProviderOutcome; }; export declare const MODEL_MENU_MESSAGE = ""; /** * Reads the provider status the menu shows. Detection order matters: a linked * project subsumes any pulled credential (the link is what the user manages), * and `AI_GATEWAY_API_KEY` outranks `VERCEL_OIDC_TOKEN` because it is the one * the provider sub-flow's own-key branch writes. */ export declare function detectModelProviderStatus(appRoot: string, options?: ProjectDetectionOptions): Promise; /** * THE MODEL FLOW for the dev TUI's `/model`: a two-row action menu that * loops, uniting the model pick and the provider setup behind one entry * point. "Change model" runs the same searchable AI Gateway catalog picker * onboarding uses ({@link selectModel}), pre-selected on the model the * runtime currently serves, then the static source edit that bakes the * choice into `agent.ts` (activation is the dev server's HMR watcher). * The provider row runs {@link runVercelFlow} — the provider gate (AI * Gateway or your own), then link-or-paste-a-key. * Completed model and provider changes return to the prompt with their result. * Cancelled flows and external-provider instructions return to the menu. */ export declare function runModelFlow(input: { appRoot: string; prompter: Prompter; signal?: AbortSignal; deps?: Partial; }): Promise; /** The outcome of applying a model slug to the agent's authored source. */ export type ApplyModelOutcome = { kind: "changed"; to: string; } | { kind: "unchanged"; model: string; } /** Invalid slug or an uneditable source — `message` says which and why. */ | { kind: "rejected"; message: string; }; /** The one-line transcript form of an apply outcome (`/model `'s reply). */ export declare function formatApplyModelOutcome(outcome: ApplyModelOutcome): string; /** * Applies a `/model ` change to the local agent's authored source. * * This is the caller layer for the static source-change registry: it * validates the slug against the AI Gateway model catalog, then edits * `agent.ts` via {@link createStaticSourceChange}. Activation is the dev * server's HMR watcher; {@link formatApplyModelOutcome} renders the outcome * as the TUI's one-line reply. */ export declare function changeAgentModel(input: { readonly appRoot: string; readonly slug: string; }): Promise; /** * Refusal message when `/model` can't rewrite the model — it is a source-backed * SDK model call (`gateway(...)`, `anthropic(...)`), not a string literal — or * null when the model is an editable string. Editability is independent of * routing: a `gateway(...)` call is gateway-routed yet still uneditable here. */ export declare function modelChangeRefusalForUneditableModel(appRoot: string): Promise;