import { type LLMHistoryContextEntry, type LLMHistoryEntryTypes, type LLMHistoryToolCallEntry, type LLMHistoryToolResultEntry, type LLMInstanceOptions, type LLMProviders, type LLMPurposes, type LLMToolGateResult, type LLMToolResult } from '../LLMService.typedefs'; import { type LLMGateway } from '../LLMGateway'; import { type LLMLoggerInterface } from '../utilities/logger'; import { type LLMReporterInterface } from '../utilities/reporter'; import { type InferSchema, type LLMSchemaInterface } from '../utilities/schema'; import { type LLMTraceContext } from '../utilities/llmTracing/llmTracing.typedefs'; import { type LLMGatewayPromptClient } from '../client/promptClientPort.typedefs'; import { type LLMPromptBinding, type LLMPromptRegistry } from '../client/defineLLMPrompts'; import { type LLMPromptSnapshot, type LLMVariableValue } from '../client/promptSnapshot.typedefs'; import { type PromptRegistryRuntime, type ResolvedPrompt } from '../client/promptRegistry.runtime'; import { type LLMAgent, type LLMAgentDefinition } from '../client/LLMAgent'; import { type LLMAgentInlineRunOptions, type LLMAgentRunOptions, type LLMAgentRunResult } from '../client/agentRun.typedefs'; export type LLMClientMode = 'production' | 'experiment'; /** * The explicit opt-out for the reporter pipeline; unlike tracing/prompts, * reporting has no safe silent default, so tests must opt out loudly. Passed * where `reporting` is expected to disable the Reporter/DWHSync wiring (tests, * scripts). */ export declare const ReportingDisabled: unique symbol; export type ReportingDisabledToken = typeof ReportingDisabled; /** * Resolves provider credentials for one call, scoped by provider + prompt + * product, preserving the existing per-feature Secrets Manager layout. */ export type LLMCredentialsResolver = (scope: { provider: Provider; promptName: string; product?: string; }) => Promise; export interface LLMClientStaticContext { product?: string; appEnvironment?: string; [key: string]: string | undefined; } export interface LLMClientCallContext { userId?: string | number | null; /** * The product this one call belongs to, when the caller knows it better than * the client does. A client is built once per process, so its static context * can only carry the product of whatever built it — the request host, or * nothing at all on a message-bus or WebSocket entry point. A call site that * resolves the product from its own subject (the user's domain, say) passes * it here, and it wins over the static one for both credentials resolution * and the reporter's payload. */ product?: string; [key: string]: string | number | boolean | null | undefined; } /** Identifies one agent invocation within a run tree for reporter contexts. */ export interface LLMReporterInvocation { agentName: string; invocationId: number; parentInvocationId: number | null; } /** * What the client knows about one call when it asks the composition root to * build the reporter's context: the resolved prompt, the per-call context the * feature passed, the static context the client was built with, and the context * of the trace scope the call runs inside. */ export interface LLMReporterContextScope { promptName: string; promptVersion: number; callContext: LLMClientCallContext | undefined; staticContext: LLMClientStaticContext; /** * What the surrounding operation declared when it opened its trace scope, or * `undefined` outside a scope. It is the reporter's level-1 context: a call * that names nothing of its own still belongs to a user and a session, and * this is where the composition root reads them from. */ scopeContext: LLMTraceContext | undefined; invocation?: LLMReporterInvocation; } /** * What building the per-call trace context needs: the resolved prompt, the * per-call context, and the typed Langfuse trace fields the call site supplied. */ export interface LLMTraceContextScope { resolved: ResolvedPrompt; callContext: LLMClientCallContext | undefined; sessionId: string | undefined; traceTags: string[] | undefined; } export interface LLMReportingConfig = Record> { reporter: LLMReporterInterface; /** * Builds the reporter's own context for one call. Required, and owned by the * composition root on purpose: the reporter's payload is a downstream * contract (DWH columns, dashboards built on their exact values), and the * gateway must never guess its shape or invent its strings. The client * supplies only what it knows — the resolved prompt and the contexts it was * given — and the application maps that onto its own typed context. */ buildContext: (scope: LLMReporterContextScope) => Context; } /** * What building a provider service for one call needs: the resolved prompt and * the context that decides whose credentials the provider is built with. */ export interface LLMProviderServiceScope { resolved: ResolvedPrompt; callContext: LLMClientCallContext | undefined; } /** * One tool call's outcome, as it stands before the client hands it to the * model. `tool` is the tool (or subagent delegation) name, `agent` the run that * called it, `input` the arguments the model supplied, and `result` the value * the tool returned after the package's own coercion. */ export interface LLMToolResultContext { tool: string; agent: string; input: unknown; result: LLMToolResult; } /** * A client-wide hook that rewrites every tool result before it reaches the * model. It runs after the package coerces the raw return value, so it always * receives a well-formed result; its own return is coerced again. Use it to * apply one truncation, redaction, or envelope policy across every tool * instead of repeating it in each `execute`. */ export type LLMToolResultNormalizer = (context: LLMToolResultContext) => LLMToolResult; /** * The precondition a delegation tool may declare, checked before the subagent * starts. The `subagents` list erases the input schema's generic, so the * arguments arrive untyped here: they are the subagent's `inputSchema` shape * when it declares one, and `{ prompt }` otherwise. */ export type LLMDelegationGate = (args: Record) => Promise | LLMToolGateResult; /** * What makes a delegated agent's report worth reading. A specialist whose value * is the evidence it gathered — a researcher, a source checker — writes the * same confident prose whether it read real sources or answered from the * model's own knowledge, and the parent model cannot tell the two apart. The * runner dispatches every call the specialist makes, so the base requirement * is met by counting executions of the named tools rather than by reading the * report for citations — that floor alone cannot catch a report that grounded * itself with one real search and then went on to cite nine other sources it * never opened, which is what `citations` below is for. */ export interface LLMAgentGroundingRequirement { /** Executing any one of these grounds the delegation's report. */ groundingToolNames: string[]; /** Prepended to the report when the run executed none of them. */ ungroundedReportNote: string; /** * The stricter, optional check: a source the report cites must be one the * run actually read. Absent when the caller only wants the execution-count * floor above. */ citations?: LLMAgentCitationRequirement; } /** * What proves a citation in the report was actually read. The gateway is a * generic package and must not learn a content-editor tool's name or * argument shape, so the caller — the one who defined the `web_fetch` tool — * declares which tool reads a url and which of its arguments carries it. */ export interface LLMAgentCitationRequirement { /** The tool whose successful calls read a url, e.g. `web_fetch`. */ sourceToolName: string; /** The argument on that tool's schema that carries the url read. */ sourceUrlArgument: string; /** * Prepended to the report, followed by the unread urls it cited, when the * report cites at least one source `sourceToolName` never successfully * read. */ unreadCitationsNote: string; } /** * `ReporterContext` ties the reporter to the mapper that feeds it: declare it * and a `LLMReporterInterface` forces `buildContext` to return exactly `T`, * so a mapper that drops or mistypes a DWH column fails the build instead of * corrupting a dashboard. It is inferred from `reporting` when the call site * passes no explicit type arguments; TypeScript has no partial inference, so a * call site that spells out `Registry`/`VariableMap` must spell this out too. * The factory defaults it to `never`, so a partially explicit call cannot * silently fall back to an unchecked reporter context. */ export interface CreateLLMClientConfig = Record> { gateway: LLMGateway; registry: Registry; snapshot: LLMPromptSnapshot; credentials: LLMCredentialsResolver; reporting: LLMReportingConfig | ReportingDisabledToken; logger?: LLMLoggerInterface; context?: LLMClientStaticContext; mode?: LLMClientMode; promptClient?: LLMGatewayPromptClient; defaultLabel?: string; cacheTtlSeconds?: number; normalizeToolResult?: LLMToolResultNormalizer; } export interface LLMClientEngineConfig { gateway: LLMGateway; registry: Registry; runtime: PromptRegistryRuntime; credentials: LLMCredentialsResolver; reporter: LLMReporterInterface | undefined; buildReporterContext: ((scope: LLMReporterContextScope) => unknown) | undefined; logger: LLMLoggerInterface | undefined; staticContext: LLMClientStaticContext; mode: LLMClientMode; normalizeToolResult: LLMToolResultNormalizer | undefined; } /** * The variables a prompt requires. Required keys come exclusively from the * code-owned prompt registry. Extras remain tolerated so callers can migrate a * variable in more than one deployment. */ export type LLMPromptVariables = Record & Record; /** * Where a subagent's prompt variables come from. Without an `inputSchema` the * parent model supplies only a task string, so the definition must carry every * variable the prompt requires. With one, the model fills the rest at call * time — the definition's own variables become a partial base and the runtime * validation gate is what proves the set complete. */ export type LLMSubagentVariableSource = { inputSchema?: undefined; variables: LLMPromptVariables; } | { inputSchema: LLMSchemaInterface; variables?: Partial>; }; /** * The return type of `generate` for one prompt: the bound schema's inferred type * when a schema is present, else the raw completion string. */ export type LLMGenerateResult = Binding extends { schema: infer Schema; } ? Schema extends LLMSchemaInterface ? InferSchema : string : string; export interface LLMGenerateOptions, Schema extends LLMSchemaInterface = never> { variables: Variables; context?: LLMClientCallContext; history?: LLMPlainMessage[]; abortSignal?: AbortSignal; overrides?: LLMCallOverrides; /** * Groups the traces of one conversation, review, or agent run into a single * Langfuse session. It is a typed Langfuse trace field, not metadata, so it * never reaches the reporter's payload. Top level rather than nested under a * trace object because a session is understandable without knowing what a * trace is. Keep the anchor stable — changing it splits a session's history * in two. */ sessionId?: string; /** * Langfuse trace tags. Typed Langfuse trace fields, not metadata. The `trace` * prefix is deliberate: a bare `tags` says nothing about which system reads * them. */ traceTags?: string[]; /** * Per-call structured-output schema. Overrides the registry binding's `schema` * for this call and drives the return type (`InferSchema`). Use it when * the output contract is dynamic per call (e.g. a schema whose shape depends on * runtime input). The schema is the code-owned output contract, not a routing * control, so it is honored in every mode. */ schema?: Schema; } export interface LLMCallOverrides { model?: string; provider?: LLMProviders; promptVersion?: number; promptLabel?: string; } export interface LLMPromptSelectionByLabel { promptLabel?: string; promptVersion?: never; } export interface LLMPromptSelectionByVersion { promptVersion?: number; promptLabel?: never; } export type LLMPromptSelectionOverrides = LLMPromptSelectionByLabel | LLMPromptSelectionByVersion; export interface LLMCompilePromptOptions> { variables: Variables; overrides?: LLMPromptSelectionOverrides; purpose?: LLMPurposes; } export type LLMPlainMessageRole = 'user' | 'assistant'; export interface LLMPlainMessage { role: LLMPlainMessageRole; text: string; type?: LLMHistoryEntryTypes.Message; } /** * One entry of the conversation an agent run restores. A plain message is a * turn of it; a tool call and its result are the round a previous run actually * ran, carried as the provider's own tool call and tool result rather than * rendered into prose; a context entry is what belongs to the conversation * without being a turn of it, such as a carried plan. * * The union is additive — an `LLMPlainMessage[]` is already an * `LLMHistoryMessage[]` — so a caller that never had tools keeps its history * unchanged. */ export type LLMHistoryMessage = LLMPlainMessage | LLMHistoryToolCallEntry | LLMHistoryToolResultEntry | LLMHistoryContextEntry; /** * Maps each registry key to the union of variable names declared by code. The * Langfuse prompt body and generated fallback snapshot do not participate in * call-site typing. */ export type LLMPromptVariableMap = { [Key in keyof Registry]: Registry[Key] extends { variables: readonly (infer VariableName extends string)[]; } ? VariableName : never; }; export interface LLMClient = LLMPromptVariableMap> { generate(prompt: Key, options: LLMGenerateOptions & Record, Schema>): Promise<[ Schema ] extends [never] ? LLMGenerateResult : InferSchema>; countTokens(prompt: Key, options: { variables: Record; history?: LLMPlainMessage[]; }): Promise; defineAgent(definition: LLMAgentDefinition): LLMAgent; runAgent(prompt: Key, options: LLMAgentInlineRunOptions & Record, Registry, VariableMap>): Promise>>; runAgent(agent: LLMAgent, options: LLMAgentRunOptions & Record>): Promise>>; describePrompt(prompt: Key): Promise; compilePrompt(prompt: Key, options: LLMCompilePromptOptions & Record>): Promise; } /** * Read-only routing metadata for a prompt — the `{ provider, model, * promptVersion }` the runtime would serve right now (same resolve/cache/fallback * path `generate` uses). Lets analytics events record the resolved model without * coupling to `generate`'s data-only return. `isFallback` flags the snapshot * config was used because the live fetch was unreachable or failed the gate. */ export interface LLMPromptDescription { provider: LLMProviders; model: string; promptVersion: number; isFallback: boolean; }