/** * Typed-decision client — the one entry point CLEO call sites use for * "System One" decisions. * * `decide(siteId, req, fallback, opts)` ALWAYS resolves with a * {@link DecisionOutcome}; it never rejects because of the provider. The * pipeline is: * * 1. No provider configured → the site's heuristic (`source: 'fallback'`). * 2. Redact the state through the memory redaction module, then validate the * request with the contract schema (invalid → fallback). * 3. In-memory LRU cache hit → `source: 'cache'`. * 4. Within ONE per-site deadline (default {@link DEFAULT_DECISION_TIMEOUT_MS}): * check the monthly spend cap (`./spend.ts`, D11159), take a token from the * machine-wide request budget, then call the provider. Spend cap reached * (`budget`), key monthly limit (`key_limit_exceeded`), budget denial, * timeout, 401/402/403/429/5xx, network failure or a malformed answer → * fallback. A 429 empties the shared budget for its `retry-after`; a * 503/529 trips it as a short circuit breaker; a 403 key limit stops * decisions until the UTC month ends. Reported cost is added to the spend * ledger. A call aborted AFTER it was sent (deadline or caller) may still be * billed, so its reserved estimate is committed; only a failure before send * or an error response releases the reservation. * 5. Append one audit line (never containing the key or raw state). * * Provider-specific knowledge (endpoints, wire JSON) lives only in * `./jev-wire.ts`; this module sees the {@link DecisionProvider} port. * * @task T12490 * @epic T12486 */ import type { DecisionAnswer, DecisionOutcome, DecisionRequest, DecisionState } from '@cleocode/contracts'; import { type DecisionAuditSink } from './audit.js'; import { type DecisionBudget } from './budget.js'; import { type DecisionCache } from './cache.js'; import { type DecisionProvider, type DecisionProviderConnection } from './provider.js'; import { type SpendLedger } from './spend.js'; import type { DecideFetch } from './transport.js'; /** Default per-site deadline for the budget + provider round trip, in ms. */ export declare const DEFAULT_DECISION_TIMEOUT_MS = 300; /** * Default deadline for a whole {@link decideBatch} call, in ms (T12715). * * `POST /v1/systemone/batch` answers its items serially (2–5 s per 64 * requests), so the System One integration spec (§1, batch row) requires an * HTTP timeout of at least 30 s. Batch callers are background sites (sweeps, * the T12495 harness replay), never a latency-critical write path, so the * 300 ms single-decision default would time out every real batch. */ export declare const DEFAULT_BATCH_DECISION_TIMEOUT_MS = 30000; /** * Upper bound on the lazy capability detection {@link decideBatch} runs * before its first batch, in ms (T12715). Also bounded by * {@link CAPABILITY_DETECTION_DEADLINE_SHARE} of the remaining batch deadline. */ export declare const CAPABILITY_DETECTION_TIMEOUT_MS = 5000; /** * Largest share of the remaining {@link decideBatch} deadline the lazy * capability detection may use (T12715), so a slow detection never leaves the * batch itself without time to run. */ export declare const CAPABILITY_DETECTION_DEADLINE_SHARE = 0.4; /** Circuit-breaker trip after a 503/529 that carried no `retry-after`, ms. */ export declare const OVERLOADED_COOLDOWN_MS = 30000; /** * A call site's local heuristic. It must be total and fast: it answers every * question in the request whenever no provider answer is available. */ export type DecisionHeuristic = (req: DecisionRequest) => Readonly>; /** Options for {@link decide}. */ export interface DecideOptions { /** * Provider connection (base URL + API key + optional default model). * Absent (`undefined`) → the connection stored by `cleo decide config` * (`./credentials.ts`) is loaded. `null`, a blank key or an invalid URL → * unconfigured, and every call returns the fallback. */ readonly connection?: DecisionProviderConnection | null; /** Explicit provider; overrides `connection`. Tests inject a fake here. */ readonly provider?: DecisionProvider; /** Deadline for budget + provider, ms. Default {@link DEFAULT_DECISION_TIMEOUT_MS}. */ readonly timeoutMs?: number; /** Outcome cache; `null` disables. Default: one per-process LRU. */ readonly cache?: DecisionCache | null; /** Request budget; `null` disables. Default: the machine-wide file bucket. */ readonly budget?: DecisionBudget | null; /** Monthly spend ledger; `null` disables the cap. Default: the machine-wide file ledger. */ readonly spend?: SpendLedger | null; /** Monthly cap in micro-dollars. Default: `decide.budget.monthlyMicros`, else $1. */ readonly spendCapMicros?: number; /** Audit sink; `null` disables. Default: `/.cleo/audit/decisions.jsonl`. */ readonly audit?: DecisionAuditSink | null; /** Project root for the default audit sink. Default: the resolved CLEO project root. */ readonly projectRoot?: string; /** Cache-key namespace. Default: the Jev adapter version. */ readonly adapterVersion?: string; /** Caller cancellation; treated like a timeout (fallback, no throw). */ readonly signal?: AbortSignal; /** Transport for a provider built from `connection`. Default: the abort-complete `decideFetch`. */ readonly fetch?: DecideFetch; /** Provider-state file (detected capabilities). Default: `/decide/provider-state.json`. */ readonly providerStatePath?: string; } /** * Reset the process-default cache and budget. Tests only. * * @internal */ export declare function _resetDecideDefaultsForTest(): void; /** * Scrub secrets from a decision state before it leaves the machine, using the * shared memory redaction patterns. Strings are redacted in place; structured * state is redacted leaf by leaf so it stays valid JSON. * * @param state - Raw state. * @returns Redacted state of the same shape. */ export declare function redactDecisionState(state: DecisionState): DecisionState; /** * Cost reserved per question before a call, micro-dollars. Only a * reservation: the provider-reported cost replaces it on commit, so this is * never what is recorded (layahost bills 5–15 µ$ per decision, 2026-09). */ export declare const DECISION_COST_ESTIMATE_MICROS_PER_QUESTION = 15; /** * Ask a typed decision at call site `siteId`. * * Never rejects on provider trouble: when the provider is unconfigured, * over budget, slow, failing or wrong, the site's `fallback` heuristic answers * and the outcome carries `source: 'fallback'`. * * @param siteId - Stable call-site identifier (audited; used for per-site tuning). * @param req - Questions and state. * @param fallback - The site's heuristic; receives the original (unredacted) request. * @param opts - Provider, deadline, cache, budget and audit wiring. * @returns The decision outcome. * * @example * ```ts * const outcome = await decide( * 'tasks.duplicate-check', * { state: text, questions: { dup: { type: 'noul', criteria: 'The two tasks describe the same work' } } }, * () => ({ dup: { type: 'noul', value: false, probability: 0.2, confidence: 0.5 } }), * { connection, timeoutMs: 250 }, * ); * ``` */ export declare function decide(siteId: string, req: DecisionRequest, fallback: DecisionHeuristic, opts?: DecideOptions): Promise; /** One request of a {@link decideBatch} call, with its own heuristic. */ export interface DecisionBatchEntry { /** Questions and state. */ readonly req: DecisionRequest; /** The heuristic for this request. */ readonly fallback: DecisionHeuristic; } /** * Ask several typed decisions at call site `siteId` under ONE deadline. * * When the provider reports a batch capability (`capabilities().batch`) and * the entries fit its limits, the uncached ones go out as a single batch call * (one spend check, one budget token). Otherwise it degrades to sequential * {@link decide} calls that share the remaining deadline; entries the * deadline cannot reach fall back with reason `timeout`. Never rejects; each * entry is audited like a single decision. * * The whole-batch deadline defaults to {@link DEFAULT_BATCH_DECISION_TIMEOUT_MS} * (30 s), not the 300 ms single-decision default. In the sequential * degradation each call is additionally capped at * {@link DEFAULT_DECISION_TIMEOUT_MS} unless the caller set `timeoutMs`, so a * slow provider without the batch capability cannot hold one entry for 30 s. * * @param siteId - Stable call-site identifier. * @param entries - Requests with their heuristics. * @param opts - Same wiring as {@link decide}; `timeoutMs` covers the whole batch * (default {@link DEFAULT_BATCH_DECISION_TIMEOUT_MS}). * @returns One outcome per entry, in order. */ export declare function decideBatch(siteId: string, entries: readonly DecisionBatchEntry[], opts?: DecideOptions): Promise; //# sourceMappingURL=client.d.ts.map