/** * Concrete stateful LLM session implementation. * * Owns per-conversation history, OAuth credential refresh (pre-call check * against expiresAt < 60s), RateLimitGuard pre-call check, prompt-cache * breakpoint injection, and exponential-backoff retry. * * @module llm/concrete-session * @task T9287 * @task T9293 (W4a — wire classifyError into retry path) * @task T9297 (W4e — wire CredentialPool + RateLimitGuard into retry) * @epic T9261 T-LLM-CRED-CENTRALIZATION * @see ADR-072 §Decision §"LlmSession — session level" */ import type { LlmSession, NormalizedDelta, RetryPolicy, SendOptions } from '@cleocode/contracts/llm/interfaces.js'; import type { LlmTransport, NormalizedResponse, TransportMessage } from '@cleocode/contracts/llm/normalized-response.js'; import type { ProviderProfile } from '@cleocode/contracts/llm/provider-profile.js'; import type { ResolvedCredential } from '@cleocode/contracts/llm/resolved-credential.js'; import type { CredentialPool } from './credential-pool.js'; import type { StoredCredential } from './credentials-store.js'; /** * Convert a {@link StoredCredential} from the pool to the {@link ResolvedCredential} * contract used by transports and session. * * Maps `accessToken` → `token` and fills optional fields with safe defaults. * * @param stored - Pool entry from CredentialPool.pick(). * @returns A fully-typed ResolvedCredential ready for transport construction. * * @task T9297 */ export declare function storedToResolved(stored: StoredCredential): ResolvedCredential; /** * Thrown when classifyError returns `reason === 'context_overflow'` so that * ConcreteExecutor can detect this case and trigger context compression. * * @task T9293 */ export declare class ContextOverflowError extends Error { /** Stable error code for instanceof / code-based detection. */ readonly code = "E_LLM_CONTEXT_OVERFLOW"; constructor(cause: unknown); } /** * Options accepted by {@link ConcreteSession}. */ export interface ConcreteSessionOptions { /** Wire-level transport used for completions. */ readonly transport: LlmTransport; /** Model identifier bound to this session. */ readonly model: string; /** Resolved credential — used for OAuth refresh checks and rate-limit keying. */ readonly credential: ResolvedCredential; /** Initial conversation history. Defaults to empty. */ readonly history?: TransportMessage[]; /** Retry policy for transient errors. Defaults to {@link DEFAULT_RETRY_POLICY}. */ readonly retryPolicy?: RetryPolicy; /** * Optional credential pool for automatic rotation on 401/429. * * When provided, `classifyError().shouldRotateCredential === true` triggers a * `pool.pick()` call, the session's active credential is replaced, and the * request is retried with the new credential. * * @task T9297 */ readonly credentialPool?: CredentialPool; /** * Provider profile resolved for this session. * * When present and `providerProfile.supportsThinkingBudget === true`, the * session automatically computes and injects a `thinkingBudgetTokens` value * into each `TransportRequest` via {@link computeThinkingBudget} — unless the * caller already set `thinkingBudgetTokens` explicitly. * * @task T9303 */ readonly providerProfile?: ProviderProfile; /** * Factory that rebuilds the transport from a new credential after pool rotation. * * Required when `credentialPool` is set. Called with the newly-picked * `ResolvedCredential`; should return a fresh `LlmTransport` instance * authenticated with the new credential. * * @task T9297 */ readonly transportFactory?: (credential: ResolvedCredential) => LlmTransport; } /** * Stateful per-conversation LLM session. * * Implements {@link LlmSession}. Callers should obtain instances via * {@link DefaultLlmSessionFactory} rather than constructing directly. * * @example * ```ts * const session = new ConcreteSession({ transport, model, credential }); * const response = await session.send([{ role: 'user', content: 'Hello!' }]); * console.log(response.content); * ``` */ export declare class ConcreteSession implements LlmSession { /** Underlying wire-level transport. */ transport: LlmTransport; /** Model identifier this session is bound to. */ readonly model: string; private _credential; private _history; private readonly _retryPolicy; private readonly _credentialPool; private readonly _transportFactory; private readonly _providerProfile; /** * @param opts - Session construction options. */ constructor(opts: ConcreteSessionOptions); /** * Returns a defensive copy of the current conversation history. * * The returned array is read-only — mutate history via {@link append} or * {@link truncateHistory}. */ history(): readonly TransportMessage[]; /** * Appends a message to the conversation history. * * @param message - The message to append. */ append(message: TransportMessage): void; /** * Truncates the conversation history, keeping `keepFirst` messages from the * start and `keepLast` messages from the end. * * If `keepFirst + keepLast >= history.length` the history is left unchanged. * * @param keepFirst - Number of messages to preserve from the start. * @param keepLast - Number of messages to preserve from the end. */ truncateHistory(keepFirst: number, keepLast: number): void; /** * Executes a single completion call with the supplied messages. * * Pre-call lifecycle: * 1. OAuth expiry check → refresh if expiring within 60s. * 2. RateLimitGuard pre-call check → throws `Error('Rate limit active')` when blocked. * 3. Cache-breakpoint injection when `opts.cacheStrategy` is set. * 4. Retry loop with exponential backoff (transient 429/5xx only). * * The supplied `messages` are NOT appended to history automatically. * * @param messages - Messages to send. * @param opts - Optional per-call overrides. * @returns Normalized provider response. */ send(messages: TransportMessage[], opts?: SendOptions): Promise; /** * Streaming variant of {@link send}. * * Yields {@link NormalizedDelta} chunks as they arrive. The caller is * responsible for appending the final assistant message to history if needed. * * Pre-call lifecycle is identical to {@link send} (OAuth refresh, rate-limit * guard). Streaming retries are NOT supported — a mid-stream error propagates * to the caller as an async iterator throw. * * @param messages - Messages to send. * @param opts - Optional per-call overrides. * @returns An async iterable of normalized delta chunks. */ stream(messages: TransportMessage[], opts?: SendOptions): AsyncIterable; /** * Refreshes the OAuth credential bound to this session. * * Called by `_preCallChecks` when the proactive refresh window is entered. * Delegates to `CredentialPool.proactiveRefresh` when a pool is configured — * the pool dispatches on `profile.oauth.mode` (`pkce` or `device-code`). * Without a pool, callers rely on 401-triggered rotation from the retry loop. * * No-op for `api_key` and `aws_sdk` credentials. * * @task T9302 (PKCE dispatch via pool) * @task T9323 (device-code dispatch via pool) */ refreshCredential(): Promise; /** * Pre-call guard: OAuth proactive refresh + RateLimitGuard check. * * OAuth refresh is attempted when remaining lifetime falls below * `max(remaining * 0.5, 300s)`. Both `anthropic` and `kimi-code` refresh * via `CredentialPool.proactiveRefresh()` → `_refreshTokenViaEndpoint()`. * * @task T9297 (W4e): RateLimitGuard pre-call check is verified here. * @task T9323: Proactive refresh at 50% lifetime / 300s floor. */ private _preCallChecks; /** * Attempt to rotate the active credential via the pool when a * `shouldRotateCredential` error is received. * * No-op when no pool or transportFactory is configured. * * @param errorCode - HTTP status code that triggered the rotation. * @task T9297 */ private _tryRotateCredential; /** Build a {@link TransportContext} for this call. */ private _buildContext; /** Assemble a {@link TransportRequest} from messages and call-level opts. */ private _buildRequest; } //# sourceMappingURL=concrete-session.d.ts.map