eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
138 lines (137 loc) • 6.04 kB
TypeScript
import { AI_GATEWAY_API_KEY_ENV_VAR } from "../ai-gateway-api-key.js";
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<CurrentAgentModel>;
/** Applies the picked slug to authored source. */
applyModel: (input: {
appRoot: string;
slug: string;
}) => Promise<ApplyModelOutcome>;
/** 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: typeof AI_GATEWAY_API_KEY_ENV_VAR | "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" | typeof AI_GATEWAY_API_KEY_ENV_VAR;
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<ModelProviderStatus>;
/**
* 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;
/** Opens provider setup before the root menu when runtime evidence requires it. */
initialStep?: "provider";
signal?: AbortSignal;
deps?: Partial<ModelFlowDeps>;
}): Promise<ModelFlowResult>;
/** 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 <slug>`'s reply). */
export declare function formatApplyModelOutcome(outcome: ApplyModelOutcome): string;
/**
* Applies a `/model <slug>` 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<ApplyModelOutcome>;
/**
* 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<string | null>;