/** * Operator-facing decision operations behind `cleo decide config|status|ask`. * * All logic lives here (the CLI handlers are thin, arch gate 6). Every value * these functions return is secret-free: the API key appears only as a masked * last-4 preview. * * @task T12491 * @epic T12486 */ import type { DecideProfileListResult, DecideProviderState as DecideProviderStateContract, DecisionAnswer, DecisionProviderCapabilities, DecisionProviderKind, DecisionProviderUsage } from '@cleocode/contracts'; import { type DecideCredentialsSummary, type SealedDecideConnection } from './credentials.js'; import { type SpendLedger, type SpendLedgerHealth, type SpendResetOptions, type SpendResetReceipt } from './spend.js'; /** Default deadline for the `GET /v1/models` probe, ms. */ export declare const DEFAULT_DECIDE_PROBE_TIMEOUT_MS = 3000; /** Default deadline for `cleo decide ask`, ms (generous: it is a debug tool). */ export declare const DEFAULT_DECIDE_ASK_TIMEOUT_MS = 10000; /** * Reachability of the configured provider. * * - `reachable` — `GET /v1/models` answered 2xx. * - `unauthorized` — the provider rejected the key (401, or a 403 that is not a key limit). * - `key_limit_reached` — the key's monthly decision limit is reached (403 * `key_limit_exceeded`); decisions resume when the UTC month rolls over. * - `unconfigured` — no valid base URL + key stored. * - `unreachable` — network failure, timeout, or any other HTTP status. */ export type DecideProviderState = DecideProviderStateContract; /** Month-to-date spend against the CLEO cap (D11159). */ export interface DecideSpendSummary { /** UTC month, `YYYY-MM`. */ readonly month: string; /** Micro-dollars recorded this month. */ readonly spentMicros: number; /** Micro-dollars reserved by calls in flight. */ readonly reservedMicros: number; /** The cap, micro-dollars (`decide.budget.monthlyMicros`). */ readonly capMicros: number; /** Whether sites are degraded to their heuristics because the cap is reached. */ readonly capReached: boolean; /** ISO time until which the key's monthly limit stops decisions, when set. */ readonly keyLimitedUntil?: string; } /** Result of {@link probeDecideProvider}. */ export interface DecideProbeResult { /** Reachability verdict. */ readonly state: DecideProviderState; /** Profile id probed (`/`, T12733), when known. */ readonly profile?: string; /** Configured base URL. */ readonly baseUrl?: string; /** Masked key preview. */ readonly keyPreview?: string; /** Configured default model, when set. */ readonly model?: string; /** Whether `GET /v1/models` succeeded (`skipped` when unconfigured). */ readonly modelsEndpoint: 'ok' | 'failed' | 'skipped'; /** Model names the key may use, when listed. */ readonly models?: readonly string[]; /** HTTP status of the probe, when one was received. */ readonly httpStatus?: number; /** Probe latency, ms. */ readonly latencyMs?: number; /** Secret-free explanation or warning. */ readonly detail?: string; /** Registered decision sites (T12662). */ readonly sites: number; /** What the provider supports beyond the Jev minimum (T12664). */ readonly capabilities?: DecisionProviderCapabilities; /** Last usage/balance read (refreshed at most every 10 minutes). */ readonly usage?: DecisionProviderUsage & { readonly fetchedAt: string; }; /** Month-to-date spend against the cap. */ readonly spend?: DecideSpendSummary; /** Spend-ledger health; `corrupt` or `unavailable` means every site falls back. */ readonly spendLedger?: SpendLedgerHealth; } /** Options for {@link probeDecideProvider}. */ export interface DecideProbeOptions { /** Connection to probe. Default: the stored connection. */ readonly connection?: SealedDecideConnection | null; /** `fetch` implementation; tests inject a stub. */ readonly fetch?: typeof fetch; /** Deadline, ms. Default {@link DEFAULT_DECIDE_PROBE_TIMEOUT_MS}. */ readonly timeoutMs?: number; /** Spend ledger; `null` omits spend. Default: the machine-wide ledger. */ readonly spend?: SpendLedger | null; /** Monthly cap, micro-dollars. Default: `decide.budget.monthlyMicros`, else $1. */ readonly capMicros?: number; /** Spend-ledger path to inspect. Default: `/decide/spend.json`. */ readonly spendStatePath?: string; /** Provider-state file (capabilities + usage cache). Default: `/decide/provider-state.json`. */ readonly providerStatePath?: string; /** Wall clock, epoch ms. Default `Date.now`. */ readonly now?: () => number; /** * Re-detect capabilities even when the cached read is fresh. Set after the * settings change so the provider's extensions apply immediately (T12713). */ readonly refreshCapabilities?: boolean; } /** Message naming the repair for a ledger that cannot be read. */ export declare const SPEND_LEDGER_REPAIR_HINT = "The System One spend ledger cannot be read, so every site uses its heuristic (the cap fails closed). Run `cleo decide budget reset` to start a fresh ledger; the old file is kept as a receipt."; /** * Probe the provider with `GET {baseUrl}/v1/models` under a short deadline. * Never throws. * * @param opts - Connection, `fetch` and deadline. * @returns The reachability verdict and, when listed, the available models. */ export declare function probeDecideProvider(opts?: DecideProbeOptions): Promise; /** Input for {@link configureDecide}. */ export interface DecideConfigureInput { /** * Provider kind. Omitted → the provider of a `/` profile, * else inferred from an override `baseUrl` (the layahost origin → * `layahost`, any other → `jev`), else the active profile's provider, else * `layahost`. */ readonly provider?: DecisionProviderKind; /** * `default` (the provider's preset URL, resolved at call time) or an * override URL. Omitted → the profile's stored setting, else `default`. */ readonly baseUrl?: string; /** API key. Omitted → the profile's stored key is kept. */ readonly apiKey?: string; /** * Profile to add or update (T12733): `/` or a bare name; * other profiles are kept. Omitted → the active profile when it has the * same provider, else `/default`. */ readonly profile?: string; /** * Make the profile active. Omitted → true for the first profile or one that * is already active, else false (adding a second key never silently * switches everyday decisions). */ readonly activate?: boolean; /** * Default model. Omitted → the stored model when the URL is unchanged; * otherwise the preset default (`layahost`), else the first model the * provider lists (`jev`). */ readonly model?: string; /** `fetch` for the model lookup and the post-save probe; tests inject a stub. */ readonly fetch?: typeof fetch; /** Deadline for the model lookup and the probe, ms. */ readonly timeoutMs?: number; } /** Result of {@link configureDecide}. */ export interface DecideConfigureResult extends DecideCredentialsSummary { /** Whether the configured profile is now the active one. */ readonly active: boolean; /** Where the stored model came from. */ readonly modelSource: 'flag' | 'stored' | 'preset' | 'provider-listing' | 'none'; /** Reachability verdict of the probe run after saving. */ readonly providerState: DecideProviderState; /** Capabilities detected after saving (the provider's extensions are active from now on). */ readonly capabilities?: DecisionProviderCapabilities; /** Secret-free warning, e.g. when no model could be resolved. */ readonly warning?: string; } /** * Store a profile's settings, merging omitted values with the stored ones, * then probe the provider and re-detect its capabilities so its extensions * apply immediately. * * A model is always stored when one can be known, because the Jev * `/v1/systemone` endpoint rejects a request without one (422, and every * site silently falls back). The model comes from, in order: the flag; the * stored model (same URL); the provider preset (`layahost` → * `LAYAHOST_DEFAULT_MODEL`); the first model the provider lists. Only a * `jev` provider whose listing fails can end with no model, and then a * warning says how to set one. * * A URL whose host differs from the stored one must come with a fresh key: * the stored key is never re-used (or sent in the model probe) for a new host. * * Profiles (T12733): the settings go to one profile, `/` * (see {@link DecideConfigureInput.profile}); every other profile is kept, * and a new profile needs its own key. The URL is stored as `default` unless * overridden. The post-save probe targets that profile, active or not. * * @param input - Provider, URL, key, optional model, profile and activation. * @returns Secret-free summary plus the model's provenance and the probe verdict. * @throws {DecideCredentialsError} On an invalid URL or profile, a provider * without a preset URL and no override, a blank key, an invalid model name, * a new profile without a key, or a host change without a fresh key. */ export declare function configureDecide(input: DecideConfigureInput): Promise; /** Options for {@link listDecideProfilesReport}. */ export interface DecideProfilesReportOptions { /** Probe each profile's `GET /v1/models`. Default false (no network). */ readonly probe?: boolean; /** `fetch` for the probes; tests inject a stub. */ readonly fetch?: typeof fetch; /** Deadline per probe, ms. Default {@link DEFAULT_DECIDE_PROBE_TIMEOUT_MS}. */ readonly timeoutMs?: number; /** Provider-state file for the probes. Default: `/decide/provider-state.json`. */ readonly providerStatePath?: string; } /** * `cleo decide profiles`: every stored profile with the active one marked and * keys masked; with `probe`, each profile's reachability (probed in parallel). * Never throws. * * @param opts - Whether to probe, plus the injectable `fetch` and deadline. * @returns The secret-free profile list. */ export declare function listDecideProfilesReport(opts?: DecideProfilesReportOptions): Promise; /** * Remove the stored settings (and rotated backups of them). * * @returns Whether anything was stored, plus the (now empty) summary. */ export declare function clearDecideConfig(): Promise; /** Input for {@link askDecideDebug}. */ export interface DecideAskInput { /** The state to judge. */ readonly state: string; /** The yes/no question (becomes the `noul` criteria). */ readonly question: string; /** Deadline, ms. Default {@link DEFAULT_DECIDE_ASK_TIMEOUT_MS}. */ readonly timeoutMs?: number; /** Project root for the audit line. Default: the resolved project root. */ readonly projectRoot?: string; /** Profile to ask through (T12733), `/`. Default: the active profile. */ readonly profile?: string; } /** Result of {@link askDecideDebug}. */ export interface DecideAskResult { /** The yes/no question that was asked (so a human render can show it). */ readonly question?: string; /** The state that was judged (echoed for the human render). */ readonly state?: string; /** Id of the profile asked through, when one was configured (T12733). */ readonly profile?: string; /** The typed answer. */ readonly answer: DecisionAnswer; /** `provider`, `cache` or `fallback`. */ readonly source: 'provider' | 'cache' | 'fallback'; /** Why the heuristic answered, when it did. */ readonly fallbackReason?: string; /** End-to-end latency, ms. */ readonly latencyMs: number; /** Provider-reported cost, USD. */ readonly costUsd?: number; /** Provider request id. */ readonly requestId?: string; /** Configured default model, when set. */ readonly model?: string; /** Masked key preview, when configured. */ readonly keyPreview?: string; } /** * Ask one yes/no question through the configured provider (debug surface). * Uses no cache, so a repeat is a real round trip. Never throws. * * @param input - State, question and deadline. * @returns The typed answer with source, fallback reason, latency and cost. */ export declare function askDecideDebug(input: DecideAskInput): Promise; /** * `cleo decide budget reset`: start a fresh spend ledger for this month, * moving the old file aside as a receipt. The repair for a corrupt ledger, * which otherwise keeps every site on its heuristic. A readable ledger is * refused unless `opts.force`, and its month-to-date spend is carried over, * so a reset never lifts a reached cap. * * @param statePath - Ledger path. Default: `/decide/spend.json`. * @param opts - `force` resets a ledger that is not corrupt. * @returns The receipt: previous health, carried spend and where the old file went. * @throws {SpendResetRefusedError} When the ledger is not corrupt and `force` is not set. */ export declare function resetDecideBudget(statePath?: string, opts?: SpendResetOptions): Promise; //# sourceMappingURL=operations.d.ts.map